mirror of
https://github.com/home-assistant/frontend.git
synced 2026-09-17 07:30:24 +00:00
Compare commits
86
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68bdb6d229 | ||
|
|
3d540709aa | ||
|
|
69a0bc214c | ||
|
|
9ca04290bf | ||
|
|
5b55b408e6 | ||
|
|
d5c64b78d9 | ||
|
|
60b09dd57d | ||
|
|
abfec31cfe | ||
|
|
90e3112ae1 | ||
|
|
380509cf57 | ||
|
|
88139f64ab | ||
|
|
f90e04274b | ||
|
|
886d14bcf6 | ||
|
|
64c831e91f | ||
|
|
8e53019cf2 | ||
|
|
daab076bc1 | ||
|
|
37a970f195 | ||
|
|
c5ffb4cb4e | ||
|
|
8c28fa747d | ||
|
|
322d01196f | ||
|
|
9ec77c88b1 | ||
|
|
29a1167782 | ||
|
|
d61a77f2d9 | ||
|
|
b9bde1960b | ||
|
|
a12c2eea5d | ||
|
|
b5c717a559 | ||
|
|
3adbc4cfaf | ||
|
|
dd11fb1b99 | ||
|
|
bf0d102c86 | ||
|
|
dad2b92d2e | ||
|
|
d027ec0018 | ||
|
|
0c038398aa | ||
|
|
5c3e0cc016 | ||
|
|
9bcd26ce57 | ||
|
|
3e8a6c418c | ||
|
|
279f3e1183 | ||
|
|
f77339ad85 | ||
|
|
da73b316ff | ||
|
|
82a49d2cbf | ||
|
|
05711b4636 | ||
|
|
2c2809573f | ||
|
|
bbbeafcc92 | ||
|
|
95c6adc739 | ||
|
|
7c2e0aea92 | ||
|
|
d05c76356f | ||
|
|
f1a0623447 | ||
|
|
41d02fdb72 | ||
|
|
52d45d482c | ||
|
|
a0fea94db2 | ||
|
|
c3975e48d9 | ||
|
|
f062e13921 | ||
|
|
08ca9c9064 | ||
|
|
667fd39147 | ||
|
|
b760e543b0 | ||
|
|
760ead4860 | ||
|
|
9a4cce74f0 | ||
|
|
7488eb782d | ||
|
|
b1e6935df9 | ||
|
|
df53364d16 | ||
|
|
777e6c4c72 | ||
|
|
e47a5effe6 | ||
|
|
62d3f74513 | ||
|
|
21e1fef0fb | ||
|
|
b3f8daa758 | ||
|
|
04f586721f | ||
|
|
8e22e41605 | ||
|
|
2770d1f36b | ||
|
|
403c042235 | ||
|
|
bdb3c04037 | ||
|
|
f1cb21e7fc | ||
|
|
a8486eda9f | ||
|
|
d5b98d306d | ||
|
|
bb2fe650ac | ||
|
|
b576c3de40 | ||
|
|
84533b8843 | ||
|
|
a8ff98b808 | ||
|
|
f0062b1e67 | ||
|
|
93f64de875 | ||
|
|
ec47e320d2 | ||
|
|
816d5ee594 | ||
|
|
588f5bd6b7 | ||
|
|
825ea93dba | ||
|
|
a690a1d7bf | ||
|
|
9fe4c79782 | ||
|
|
42613d6519 | ||
|
|
4b77910e4f |
@@ -22,17 +22,38 @@ const getMeta = () => {
|
||||
const svg = fs.readFileSync(`${ICON_PATH}/${icon.name}.svg`, {
|
||||
encoding,
|
||||
});
|
||||
return { path: svg.match(/ d="([^"]+)"/)[1], name: icon.name };
|
||||
return {
|
||||
path: svg.match(/ d="([^"]+)"/)[1],
|
||||
name: icon.name,
|
||||
tags: icon.tags,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const addRemovedMeta = (meta) => {
|
||||
const file = fs.readFileSync(REMOVED_ICONS_PATH, { encoding });
|
||||
const removed = JSON.parse(file);
|
||||
const combinedMeta = [...meta, ...removed];
|
||||
const removedMeta = removed.map((removeIcon) => ({
|
||||
path: removeIcon.path,
|
||||
name: removeIcon.name,
|
||||
tags: [],
|
||||
}));
|
||||
const combinedMeta = [...meta, ...removedMeta];
|
||||
return combinedMeta.sort((a, b) => a.name.localeCompare(b.name));
|
||||
};
|
||||
|
||||
const homeAutomationTag = "Home Automation";
|
||||
|
||||
const orderMeta = (meta) => {
|
||||
const homeAutomationMeta = meta.filter((icon) =>
|
||||
icon.tags.includes(homeAutomationTag)
|
||||
);
|
||||
const otherMeta = meta.filter(
|
||||
(icon) => !icon.tags.includes(homeAutomationTag)
|
||||
);
|
||||
return [...homeAutomationMeta, ...otherMeta];
|
||||
};
|
||||
|
||||
const splitBySize = (meta) => {
|
||||
const chunks = [];
|
||||
const CHUNK_SIZE = 50000;
|
||||
@@ -77,8 +98,10 @@ const findDifferentiator = (curString, prevString) => {
|
||||
};
|
||||
|
||||
gulp.task("gen-icons-json", (done) => {
|
||||
const meta = addRemovedMeta(getMeta());
|
||||
const split = splitBySize(meta);
|
||||
const meta = getMeta();
|
||||
|
||||
const metaAndRemoved = addRemovedMeta(meta);
|
||||
const split = splitBySize(metaAndRemoved);
|
||||
|
||||
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||
@@ -116,5 +139,10 @@ gulp.task("gen-icons-json", (done) => {
|
||||
JSON.stringify({ version: package.version, parts })
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.resolve(OUTPUT_DIR, "iconList.json"),
|
||||
JSON.stringify(orderMeta(meta).map((icon) => icon.name))
|
||||
);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiCast, mdiCastConnected } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { Auth, Connection } from "home-assistant-js-websocket";
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
import { atLeastVersion } from "../../../../src/common/config/version";
|
||||
import { toggleAttribute } from "../../../../src/common/dom/toggle_attribute";
|
||||
import "../../../../src/components/ha-icon";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import {
|
||||
getLegacyLovelaceCollection,
|
||||
getLovelaceCollection,
|
||||
@@ -73,7 +75,7 @@ class HcCast extends LitElement {
|
||||
? html`
|
||||
<p class="center-item">
|
||||
<mwc-button raised @click=${this._handleLaunch}>
|
||||
<ha-icon icon="hass:cast"></ha-icon>
|
||||
<ha-svg-icon .path=${mdiCast}></ha-svg-icon>
|
||||
Start Casting
|
||||
</mwc-button>
|
||||
</p>
|
||||
@@ -111,7 +113,7 @@ class HcCast extends LitElement {
|
||||
${this.castManager.status
|
||||
? html`
|
||||
<mwc-button @click=${this._handleLaunch}>
|
||||
<ha-icon icon="hass:cast-connected"></ha-icon>
|
||||
<ha-svg-icon .path=${mdiCastConnected}></ha-svg-icon>
|
||||
Manage
|
||||
</mwc-button>
|
||||
`
|
||||
@@ -233,7 +235,7 @@ class HcCast extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
mwc-button ha-icon {
|
||||
mwc-button ha-svg-icon {
|
||||
margin-right: 8px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import { mdiCastConnected, mdiCast } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
Auth,
|
||||
@@ -19,7 +20,7 @@ import {
|
||||
loadTokens,
|
||||
saveTokens,
|
||||
} from "../../../../src/common/auth/token_storage";
|
||||
import "../../../../src/components/ha-icon";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import "../../../../src/layouts/hass-loading-screen";
|
||||
import { registerServiceWorker } from "../../../../src/util/register-service-worker";
|
||||
import "./hc-layout";
|
||||
@@ -127,11 +128,11 @@ export class HcConnect extends LitElement {
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._handleDemo}>
|
||||
Show Demo
|
||||
<ha-icon
|
||||
.icon=${this.castManager.castState === "CONNECTED"
|
||||
? "hass:cast-connected"
|
||||
: "hass:cast"}
|
||||
></ha-icon>
|
||||
<ha-svg-icon
|
||||
.path=${this.castManager.castState === "CONNECTED"
|
||||
? mdiCastConnected
|
||||
: mdiCast}
|
||||
></ha-svg-icon>
|
||||
</mwc-button>
|
||||
<div class="spacer"></div>
|
||||
<mwc-button @click=${this._handleConnect}>Authorize</mwc-button>
|
||||
@@ -307,7 +308,7 @@ export class HcConnect extends LitElement {
|
||||
color: darkred;
|
||||
}
|
||||
|
||||
mwc-button ha-icon {
|
||||
mwc-button ha-svg-icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { mdiTelevision } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { CastManager } from "../../../src/cast/cast_manager";
|
||||
@@ -27,7 +28,7 @@ class CastDemoRow extends LitElement implements LovelaceRow {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<ha-icon icon="hademo:television"></ha-icon>
|
||||
<ha-svg-icon .path=${mdiTelevision}></ha-svg-icon>
|
||||
<div class="flex">
|
||||
<div class="name">Show Chromecast interface</div>
|
||||
<google-cast-launcher></google-cast-launcher>
|
||||
@@ -72,7 +73,7 @@ class CastDemoRow extends LitElement implements LovelaceRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
ha-icon {
|
||||
ha-svg-icon {
|
||||
padding: 8px;
|
||||
color: var(--paper-item-icon-color);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,177 @@
|
||||
import { mdiHomeAssistant } from "@mdi/js";
|
||||
import "../../../src/components/ha-switch";
|
||||
import "../../../src/components/ha-formfield";
|
||||
import { mdiClose, mdiHomeAssistant } from "@mdi/js";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-chip";
|
||||
import "../../../src/components/ha-chip-set";
|
||||
import type { HaChipSetItem } from "../../../src/components/ha-chip-set";
|
||||
import "../../../src/components/ha-svg-icon";
|
||||
|
||||
const chips: {
|
||||
icon?: string;
|
||||
content?: string;
|
||||
}[] = [
|
||||
const chips: HaChipSetItem[] = [
|
||||
{
|
||||
leadingIcon: mdiHomeAssistant,
|
||||
},
|
||||
{
|
||||
label: "Demo chip",
|
||||
},
|
||||
{
|
||||
leadingIcon: mdiHomeAssistant,
|
||||
label: "Demo chip",
|
||||
},
|
||||
{
|
||||
leadingIcon: mdiHomeAssistant,
|
||||
label: "Demo chip",
|
||||
active: true,
|
||||
},
|
||||
{},
|
||||
{
|
||||
icon: mdiHomeAssistant,
|
||||
trailingIcon: mdiClose,
|
||||
label: "Demo chip",
|
||||
},
|
||||
{
|
||||
content: "Content",
|
||||
label: "Automation",
|
||||
},
|
||||
{
|
||||
icon: mdiHomeAssistant,
|
||||
content: "Content",
|
||||
label: "Blueprint",
|
||||
},
|
||||
{
|
||||
label: "Script",
|
||||
},
|
||||
{
|
||||
label: "Scene",
|
||||
},
|
||||
{
|
||||
label: "Person",
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-ha-chip")
|
||||
export class DemoHaChip extends LitElement {
|
||||
@state() standaloneIsDisabled = false;
|
||||
|
||||
@state() standaloneIsOutlined = false;
|
||||
|
||||
@state() standaloneIsActive = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-card header="ha-chip demo">
|
||||
<div class="filters">
|
||||
<ha-formfield label="Disable standalone chips">
|
||||
<ha-switch @change=${this._toggleDisable}></ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Outline standalone chips">
|
||||
<ha-switch @change=${this._toggleOutline}></ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Activate standalone chips">
|
||||
<ha-switch @change=${this._toggleActive}></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
<ha-card header="Standalone ha-chip demo">
|
||||
<div class="card-content">
|
||||
${chips.map(
|
||||
(chip) => html`
|
||||
<ha-chip .hasIcon=${chip.icon !== undefined}>
|
||||
${chip.icon
|
||||
? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
|
||||
</ha-svg-icon>`
|
||||
: ""}
|
||||
${chip.content}
|
||||
</ha-chip>
|
||||
`
|
||||
)}
|
||||
<div class="standalone">
|
||||
<span>Simple:</span>
|
||||
<ha-chip
|
||||
.active=${this.standaloneIsActive}
|
||||
.outlined=${this.standaloneIsOutlined}
|
||||
?disabled=${this.standaloneIsDisabled}
|
||||
>Demo chip</ha-chip
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="standalone">
|
||||
<span>Label property:</span>
|
||||
<ha-chip
|
||||
.active=${this.standaloneIsActive}
|
||||
.outlined=${this.standaloneIsOutlined}
|
||||
?disabled=${this.standaloneIsDisabled}
|
||||
label="Demo chip"
|
||||
></ha-chip>
|
||||
</div>
|
||||
|
||||
<div class="standalone">
|
||||
<span>With leadingIcon:</span>
|
||||
<ha-chip
|
||||
.active=${this.standaloneIsActive}
|
||||
.leadingIcon=${mdiHomeAssistant}
|
||||
.outlined=${this.standaloneIsOutlined}
|
||||
?disabled=${this.standaloneIsDisabled}
|
||||
>Demo chip</ha-chip
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="standalone">
|
||||
<span>With trailingIcon property:</span>
|
||||
<ha-chip
|
||||
.active=${this.standaloneIsActive}
|
||||
.trailingIcon=${mdiHomeAssistant}
|
||||
.outlined=${this.standaloneIsOutlined}
|
||||
?disabled=${this.standaloneIsDisabled}
|
||||
>Demo chip</ha-chip
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="standalone">
|
||||
<span>With trailing-icon slot:</span>
|
||||
<ha-chip
|
||||
.active=${this.standaloneIsActive}
|
||||
.outlined=${this.standaloneIsOutlined}
|
||||
?disabled=${this.standaloneIsDisabled}
|
||||
>
|
||||
Demo chip
|
||||
<ha-svg-icon
|
||||
slot="trailing-icon"
|
||||
class="trailing"
|
||||
.path=${mdiHomeAssistant}
|
||||
></ha-svg-icon>
|
||||
</ha-chip>
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
<ha-card header="ha-chip-set demo">
|
||||
<div class="card-content">
|
||||
<ha-chip-set .items=${chips}> </ha-chip-set>
|
||||
<ha-chip-set
|
||||
.items=${chips.map((chip) => ({ ...chip, outlined: true }))}
|
||||
>
|
||||
</ha-chip-set>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _toggleActive() {
|
||||
this.standaloneIsActive = !this.standaloneIsActive;
|
||||
}
|
||||
|
||||
private _toggleDisable() {
|
||||
this.standaloneIsDisabled = !this.standaloneIsDisabled;
|
||||
}
|
||||
|
||||
private _toggleOutline() {
|
||||
this.standaloneIsOutlined = !this.standaloneIsOutlined;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
.standalone {
|
||||
margin: 4px 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.filters {
|
||||
margin: 16px;
|
||||
}
|
||||
ha-formfield {
|
||||
margin: 12px 0;
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +181,24 @@ class HaGallery extends PolymerElement {
|
||||
this.$.notifications.showDialog({ message: "Alert action clicked" })
|
||||
);
|
||||
|
||||
this.addEventListener("chip-clicked", (ev) =>
|
||||
this.$.notifications.showDialog({
|
||||
message: `Chip ${ev.detail.index} clicked `,
|
||||
})
|
||||
);
|
||||
|
||||
this.addEventListener("chip-clicked", () =>
|
||||
this.$.notifications.showDialog({
|
||||
message: "Chip clicked",
|
||||
})
|
||||
);
|
||||
|
||||
this.addEventListener("chip-clicked-trailing", () =>
|
||||
this.$.notifications.showDialog({
|
||||
message: "Chip trailing icon clicked",
|
||||
})
|
||||
);
|
||||
|
||||
this.addEventListener("hass-more-info", (ev) => {
|
||||
if (ev.detail.entityId) {
|
||||
this.$.notifications.showDialog({
|
||||
|
||||
@@ -78,6 +78,18 @@ class HassioAddonConfig extends LitElement {
|
||||
this.addon.translations.en?.configuration?.[entry.name].name ||
|
||||
entry.name;
|
||||
|
||||
private _schema = memoizeOne((schema: HaFormSchema[]): HaFormSchema[] =>
|
||||
// @ts-expect-error supervisor does not implement [string, string] for select.options[]
|
||||
schema.map((entry) =>
|
||||
entry.type === "select"
|
||||
? {
|
||||
...entry,
|
||||
options: entry.options.map((option) => [option, option]),
|
||||
}
|
||||
: entry
|
||||
)
|
||||
);
|
||||
|
||||
private _filteredShchema = memoizeOne(
|
||||
(options: Record<string, unknown>, schema: HaFormSchema[]) =>
|
||||
schema.filter((entry) => entry.name in options || entry.required)
|
||||
@@ -128,12 +140,14 @@ class HassioAddonConfig extends LitElement {
|
||||
.data=${this._options!}
|
||||
@value-changed=${this._configChanged}
|
||||
.computeLabel=${this.computeLabel}
|
||||
.schema=${this._showOptional
|
||||
? this.addon.schema!
|
||||
: this._filteredShchema(
|
||||
this.addon.options,
|
||||
this.addon.schema!
|
||||
)}
|
||||
.schema=${this._schema(
|
||||
this._showOptional
|
||||
? this.addon.schema!
|
||||
: this._filteredShchema(
|
||||
this.addon.options,
|
||||
this.addon.schema!
|
||||
)
|
||||
)}
|
||||
></ha-form>`
|
||||
: html` <ha-yaml-editor
|
||||
@value-changed=${this._configChanged}
|
||||
|
||||
@@ -11,6 +11,12 @@ import {
|
||||
mdiHomeAssistant,
|
||||
mdiKey,
|
||||
mdiNetwork,
|
||||
mdiNumeric1,
|
||||
mdiNumeric2,
|
||||
mdiNumeric3,
|
||||
mdiNumeric4,
|
||||
mdiNumeric5,
|
||||
mdiNumeric6,
|
||||
mdiPound,
|
||||
mdiShield,
|
||||
} 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/ha-alert";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-label-badge";
|
||||
import "../../../../src/components/ha-chip-set";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
@@ -73,6 +79,15 @@ const STAGE_ICON = {
|
||||
deprecated: mdiExclamationThick,
|
||||
};
|
||||
|
||||
const RATING_ICON = {
|
||||
1: mdiNumeric1,
|
||||
2: mdiNumeric2,
|
||||
3: mdiNumeric3,
|
||||
4: mdiNumeric4,
|
||||
5: mdiNumeric5,
|
||||
6: mdiNumeric6,
|
||||
};
|
||||
|
||||
@customElement("hassio-addon-info")
|
||||
class HassioAddonInfo extends LitElement {
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
@@ -110,6 +125,137 @@ class HassioAddonInfo extends LitElement {
|
||||
)}`,
|
||||
},
|
||||
];
|
||||
const capabilities = [
|
||||
{
|
||||
id: "rating",
|
||||
icon: RATING_ICON[this.addon.rating],
|
||||
class: [6, 5].includes(Number(this.addon.rating))
|
||||
? "green"
|
||||
: [3, 4].includes(Number(this.addon.rating))
|
||||
? "yellow"
|
||||
: "red",
|
||||
label: this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.rating"
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
if (this.addon.stage !== "stable") {
|
||||
capabilities.push({
|
||||
id: "rating",
|
||||
icon: STAGE_ICON[this.addon.stage],
|
||||
class:
|
||||
this.addon.stage === "experimental"
|
||||
? "yellow"
|
||||
: this.addon.stage === "deprecated"
|
||||
? "red"
|
||||
: "",
|
||||
label: this.supervisor.localize(
|
||||
`addon.dashboard.capability.stages.${this.addon.stage}`
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (this.addon.host_network) {
|
||||
capabilities.push({
|
||||
id: "host_network",
|
||||
icon: mdiNetwork,
|
||||
class: "",
|
||||
label: this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.host"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (this.addon.full_access) {
|
||||
capabilities.push({
|
||||
id: "full_access",
|
||||
icon: mdiChip,
|
||||
class: "",
|
||||
label: this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.hardware"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (this.addon.homeassistant_api) {
|
||||
capabilities.push({
|
||||
id: "homeassistant_api",
|
||||
icon: mdiHomeAssistant,
|
||||
class: "",
|
||||
label: this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.core"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (this._computeHassioApi) {
|
||||
capabilities.push({
|
||||
id: "hassio_api",
|
||||
icon: mdiHomeAssistant,
|
||||
class: "",
|
||||
label:
|
||||
this.supervisor.localize(
|
||||
`addon.dashboard.capability.role.${this.addon.hassio_role}`
|
||||
) || this.addon.hassio_role,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.addon.docker_api) {
|
||||
capabilities.push({
|
||||
id: "docker_api",
|
||||
icon: mdiDocker,
|
||||
class: "",
|
||||
label: this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.docker"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (this.addon.host_pid) {
|
||||
capabilities.push({
|
||||
id: "host_pid",
|
||||
icon: mdiPound,
|
||||
class: "",
|
||||
label: this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.host_pid"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (this.addon.apparmor !== "default") {
|
||||
capabilities.push({
|
||||
id: "apparmor",
|
||||
icon: mdiShield,
|
||||
class: this._computeApparmorClassName,
|
||||
label: this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.apparmor"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (this.addon.auth_api) {
|
||||
capabilities.push({
|
||||
id: "auth_api",
|
||||
icon: mdiKey,
|
||||
class: "",
|
||||
label: this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.auth"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (this.addon.ingress) {
|
||||
capabilities.push({
|
||||
id: "ingress",
|
||||
icon: mdiCursorDefaultClickOutline,
|
||||
class: "",
|
||||
label: this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.ingress"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.addon.update_available
|
||||
? html`
|
||||
@@ -180,24 +326,21 @@ class HassioAddonInfo extends LitElement {
|
||||
: ""}
|
||||
${!this.addon.protected
|
||||
? html`
|
||||
<ha-card class="warning">
|
||||
<h1 class="card-header">${this.supervisor.localize(
|
||||
"addon.dashboard.protection_mode.title"
|
||||
)}
|
||||
</h1>
|
||||
<div class="card-content">
|
||||
${this.supervisor.localize("addon.dashboard.protection_mode.content")}
|
||||
</div>
|
||||
<div class="card-actions protection-enable">
|
||||
<mwc-button @click=${this._protectionToggled}>
|
||||
${this.supervisor.localize(
|
||||
<ha-alert
|
||||
alert-type="error"
|
||||
.title=${this.supervisor.localize(
|
||||
"addon.dashboard.protection_mode.title"
|
||||
)}
|
||||
.actionText=${this.supervisor.localize(
|
||||
"addon.dashboard.protection_mode.enable"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
@alert-action-clicked=${this._protectionToggled}
|
||||
>
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.protection_mode.content"
|
||||
)}
|
||||
</ha-alert>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<ha-card>
|
||||
@@ -249,6 +392,21 @@ class HassioAddonInfo extends LitElement {
|
||||
>`}
|
||||
</div>
|
||||
|
||||
<div class="capabilities">
|
||||
<ha-chip-set>
|
||||
${capabilities.map(
|
||||
(capability) => html`<ha-chip
|
||||
@click=${this._showMoreInfo}
|
||||
.leadingIcon=${capability.icon}
|
||||
.label=${capability.label}
|
||||
.id=${capability.id}
|
||||
class=${capability.class}
|
||||
outlined
|
||||
></ha-chip>`
|
||||
)}
|
||||
</ha-chip-set>
|
||||
</div>
|
||||
|
||||
<div class="description light-color">
|
||||
${this.addon.description}.<br />
|
||||
${this.supervisor.localize(
|
||||
@@ -269,172 +427,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
|
||||
? html`
|
||||
<div
|
||||
@@ -1178,34 +1170,29 @@ class HassioAddonInfo extends LitElement {
|
||||
.description a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-chip {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.red {
|
||||
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
|
||||
--ha-chip-background-color: var(--label-badge-red, #df4c1e);
|
||||
}
|
||||
.blue {
|
||||
--ha-label-badge-color: var(--label-badge-blue, #039be5);
|
||||
--ha-chip-background-color: var(--label-badge-blue, #039be5);
|
||||
}
|
||||
.green {
|
||||
--ha-label-badge-color: var(--label-badge-green, #0da035);
|
||||
--ha-chip-background-color: var(--label-badge-green, #0da035);
|
||||
}
|
||||
.yellow {
|
||||
--ha-label-badge-color: var(--label-badge-yellow, #f4b400);
|
||||
--ha-chip-background-color: var(--label-badge-yellow, #f4b400);
|
||||
}
|
||||
.security {
|
||||
margin-bottom: 16px;
|
||||
.capabilities {
|
||||
margin: -16px 0 8px;
|
||||
}
|
||||
.card-actions {
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
}
|
||||
.security h3 {
|
||||
margin-bottom: 8px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.security ha-label-badge {
|
||||
cursor: pointer;
|
||||
margin-right: 4px;
|
||||
--ha-label-badge-padding: 8px 0 0 0;
|
||||
}
|
||||
.changelog {
|
||||
display: contents;
|
||||
}
|
||||
@@ -1245,6 +1232,9 @@ class HassioAddonInfo extends LitElement {
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
ha-chip {
|
||||
line-height: 36px;
|
||||
}
|
||||
.addon-options {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@@ -141,6 +141,9 @@ class HassioBackupDialog
|
||||
flex-shrink: 0;
|
||||
display: block;
|
||||
}
|
||||
ha-icon-button {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -47,11 +47,6 @@ class HassioMarkdownDialog extends LitElement {
|
||||
haStyleDialog,
|
||||
hassioStyle,
|
||||
css`
|
||||
ha-paper-dialog {
|
||||
min-width: 350px;
|
||||
font-size: 14px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
app-toolbar {
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
@@ -62,19 +57,6 @@ class HassioMarkdownDialog extends LitElement {
|
||||
margin-left: 16px;
|
||||
}
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
ha-paper-dialog {
|
||||
max-height: 100%;
|
||||
}
|
||||
ha-paper-dialog::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
z-index: -1;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
background-color: inherit;
|
||||
}
|
||||
app-toolbar {
|
||||
color: var(--text-primary-color);
|
||||
background-color: var(--primary-color);
|
||||
|
||||
@@ -113,12 +113,6 @@ export class HassioMain extends SupervisorBaseElement {
|
||||
: this.hass.themes.default_theme);
|
||||
|
||||
themeSettings = this.hass.selectedTheme;
|
||||
if (themeSettings?.dark === undefined) {
|
||||
themeSettings = {
|
||||
...this.hass.selectedTheme,
|
||||
dark: this.hass.themes.darkMode,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
themeName =
|
||||
(this.hass.selectedTheme as unknown as string) ||
|
||||
|
||||
@@ -31,29 +31,9 @@ import { documentationUrl } from "../../../src/util/documentation-url";
|
||||
import "../components/supervisor-metric";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
const UNSUPPORTED_REASON_URL = {
|
||||
apparmor: "/more-info/unsupported/apparmor",
|
||||
container: "/more-info/unsupported/container",
|
||||
content_trust: "/more-info/unsupported/content_trust",
|
||||
dbus: "/more-info/unsupported/dbus",
|
||||
docker_configuration: "/more-info/unsupported/docker_configuration",
|
||||
docker_version: "/more-info/unsupported/docker_version",
|
||||
job_conditions: "/more-info/unsupported/job_conditions",
|
||||
lxc: "/more-info/unsupported/lxc",
|
||||
network_manager: "/more-info/unsupported/network_manager",
|
||||
os_agent: "/more-info/unsupported/os_agent",
|
||||
os: "/more-info/unsupported/os",
|
||||
privileged: "/more-info/unsupported/privileged",
|
||||
source_mods: "/more-info/unsupported/source_mods",
|
||||
systemd: "/more-info/unsupported/systemd",
|
||||
};
|
||||
|
||||
const UNSUPPORTED_REASON_URL = {};
|
||||
const UNHEALTHY_REASON_URL = {
|
||||
privileged: "/more-info/unsupported/privileged",
|
||||
supervisor: "/more-info/unhealthy/supervisor",
|
||||
setup: "/more-info/unhealthy/setup",
|
||||
docker: "/more-info/unhealthy/docker",
|
||||
untrusted: "/more-info/unhealthy/untrusted",
|
||||
};
|
||||
|
||||
@customElement("hassio-supervisor-info")
|
||||
@@ -425,20 +405,19 @@ class HassioSupervisorInfo extends LitElement {
|
||||
${this.supervisor.resolution.unsupported.map(
|
||||
(reason) => html`
|
||||
<li>
|
||||
${UNSUPPORTED_REASON_URL[reason]
|
||||
? html`<a
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
UNSUPPORTED_REASON_URL[reason]
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.supervisor.localize(
|
||||
`system.supervisor.unsupported_reason.${reason}`
|
||||
) || reason}
|
||||
</a>`
|
||||
: reason}
|
||||
<a
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
UNSUPPORTED_REASON_URL[reason] ||
|
||||
`/more-info/unsupported/${reason}`
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.supervisor.localize(
|
||||
`system.supervisor.unsupported_reason.${reason}`
|
||||
) || reason}
|
||||
</a>
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
@@ -456,20 +435,19 @@ class HassioSupervisorInfo extends LitElement {
|
||||
${this.supervisor.resolution.unhealthy.map(
|
||||
(reason) => html`
|
||||
<li>
|
||||
${UNHEALTHY_REASON_URL[reason]
|
||||
? html`<a
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
UNHEALTHY_REASON_URL[reason]
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.supervisor.localize(
|
||||
`system.supervisor.unhealthy_reason.${reason}`
|
||||
) || reason}
|
||||
</a>`
|
||||
: reason}
|
||||
<a
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
UNHEALTHY_REASON_URL[reason] ||
|
||||
`/more-info/unhealthy/${reason}`
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.supervisor.localize(
|
||||
`system.supervisor.unhealthy_reason.${reason}`
|
||||
) || reason}
|
||||
</a>
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
|
||||
+2
-12
@@ -67,27 +67,17 @@
|
||||
"@material/mwc-tab-bar": "0.25.2",
|
||||
"@material/mwc-textfield": "0.25.2",
|
||||
"@material/top-app-bar": "14.0.0-canary.353ca7e9f.0",
|
||||
"@mdi/js": "6.2.95",
|
||||
"@mdi/svg": "6.2.95",
|
||||
"@mdi/js": "6.4.95",
|
||||
"@mdi/svg": "6.4.95",
|
||||
"@polymer/app-layout": "^3.1.0",
|
||||
"@polymer/iron-flex-layout": "^3.0.1",
|
||||
"@polymer/iron-icon": "^3.0.1",
|
||||
"@polymer/iron-input": "^3.0.1",
|
||||
"@polymer/iron-overlay-behavior": "^3.0.3",
|
||||
"@polymer/iron-resizable-behavior": "^3.0.1",
|
||||
"@polymer/paper-checkbox": "^3.1.0",
|
||||
"@polymer/paper-dialog": "^3.0.1",
|
||||
"@polymer/paper-dialog-behavior": "^3.0.1",
|
||||
"@polymer/paper-dialog-scrollable": "^3.0.1",
|
||||
"@polymer/paper-dropdown-menu": "^3.2.0",
|
||||
"@polymer/paper-input": "^3.2.1",
|
||||
"@polymer/paper-item": "^3.0.1",
|
||||
"@polymer/paper-listbox": "^3.0.1",
|
||||
"@polymer/paper-menu-button": "^3.1.0",
|
||||
"@polymer/paper-progress": "^3.0.1",
|
||||
"@polymer/paper-radio-button": "^3.0.1",
|
||||
"@polymer/paper-radio-group": "^3.0.1",
|
||||
"@polymer/paper-ripple": "^3.0.2",
|
||||
"@polymer/paper-slider": "^3.0.1",
|
||||
"@polymer/paper-styles": "^3.0.1",
|
||||
"@polymer/paper-tabs": "^3.1.0",
|
||||
|
||||
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20211014.0",
|
||||
version="20211020.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/frontend",
|
||||
author="The Home Assistant Authors",
|
||||
|
||||
+130
-72
@@ -1,88 +1,146 @@
|
||||
/** Constants to be used in the frontend. */
|
||||
|
||||
import {
|
||||
mdiAccount,
|
||||
mdiAirFilter,
|
||||
mdiAlert,
|
||||
mdiAngleAcute,
|
||||
mdiAppleSafari,
|
||||
mdiBell,
|
||||
mdiBookmark,
|
||||
mdiBrightness5,
|
||||
mdiBullhorn,
|
||||
mdiCalendar,
|
||||
mdiCalendarClock,
|
||||
mdiCash,
|
||||
mdiClock,
|
||||
mdiCloudUpload,
|
||||
mdiCog,
|
||||
mdiCommentAlert,
|
||||
mdiCounter,
|
||||
mdiCurrentAc,
|
||||
mdiEye,
|
||||
mdiFan,
|
||||
mdiFlash,
|
||||
mdiFlower,
|
||||
mdiFormatListBulleted,
|
||||
mdiFormTextbox,
|
||||
mdiGasCylinder,
|
||||
mdiGauge,
|
||||
mdiGoogleAssistant,
|
||||
mdiGoogleCirclesCommunities,
|
||||
mdiHomeAssistant,
|
||||
mdiHomeAutomation,
|
||||
mdiImageFilterFrames,
|
||||
mdiLightbulb,
|
||||
mdiLightningBolt,
|
||||
mdiMailbox,
|
||||
mdiMapMarkerRadius,
|
||||
mdiMolecule,
|
||||
mdiMoleculeCo,
|
||||
mdiMoleculeCo2,
|
||||
mdiPalette,
|
||||
mdiRayVertex,
|
||||
mdiRemote,
|
||||
mdiRobot,
|
||||
mdiRobotVacuum,
|
||||
mdiScriptText,
|
||||
mdiSineWave,
|
||||
mdiTextToSpeech,
|
||||
mdiThermometer,
|
||||
mdiThermostat,
|
||||
mdiTimerOutline,
|
||||
mdiToggleSwitchOutline,
|
||||
mdiVideo,
|
||||
mdiWaterPercent,
|
||||
mdiWeatherCloudy,
|
||||
mdiWhiteBalanceSunny,
|
||||
mdiWifi,
|
||||
} from "@mdi/js";
|
||||
|
||||
// Constants should be alphabetically sorted by name.
|
||||
// Arrays with values should be alphabetically sorted if order doesn't matter.
|
||||
// Each constant should have a description what it is supposed to be used for.
|
||||
|
||||
/** Icon to use when no icon specified for domain. */
|
||||
export const DEFAULT_DOMAIN_ICON = "hass:bookmark";
|
||||
export const DEFAULT_DOMAIN_ICON = mdiBookmark;
|
||||
|
||||
/** Icons for each domain */
|
||||
export const FIXED_DOMAIN_ICONS = {
|
||||
alert: "hass:alert",
|
||||
alexa: "hass:amazon-alexa",
|
||||
air_quality: "hass:air-filter",
|
||||
automation: "hass:robot",
|
||||
calendar: "hass:calendar",
|
||||
camera: "hass:video",
|
||||
climate: "hass:thermostat",
|
||||
configurator: "hass:cog",
|
||||
conversation: "hass:text-to-speech",
|
||||
counter: "hass:counter",
|
||||
device_tracker: "hass:account",
|
||||
fan: "hass:fan",
|
||||
google_assistant: "hass:google-assistant",
|
||||
group: "hass:google-circles-communities",
|
||||
homeassistant: "hass:home-assistant",
|
||||
homekit: "hass:home-automation",
|
||||
image_processing: "hass:image-filter-frames",
|
||||
input_boolean: "hass:toggle-switch-outline",
|
||||
input_datetime: "hass:calendar-clock",
|
||||
input_number: "hass:ray-vertex",
|
||||
input_select: "hass:format-list-bulleted",
|
||||
input_text: "hass:form-textbox",
|
||||
light: "hass:lightbulb",
|
||||
mailbox: "hass:mailbox",
|
||||
notify: "hass:comment-alert",
|
||||
number: "hass:ray-vertex",
|
||||
persistent_notification: "hass:bell",
|
||||
person: "hass:account",
|
||||
plant: "hass:flower",
|
||||
proximity: "hass:apple-safari",
|
||||
remote: "hass:remote",
|
||||
scene: "hass:palette",
|
||||
script: "hass:script-text",
|
||||
select: "hass:format-list-bulleted",
|
||||
sensor: "hass:eye",
|
||||
simple_alarm: "hass:bell",
|
||||
sun: "hass:white-balance-sunny",
|
||||
switch: "hass:flash",
|
||||
timer: "hass:timer-outline",
|
||||
updater: "hass:cloud-upload",
|
||||
vacuum: "hass:robot-vacuum",
|
||||
water_heater: "hass:thermometer",
|
||||
weather: "hass:weather-cloudy",
|
||||
zone: "hass:map-marker-radius",
|
||||
alert: mdiAlert,
|
||||
air_quality: mdiAirFilter,
|
||||
automation: mdiRobot,
|
||||
calendar: mdiCalendar,
|
||||
camera: mdiVideo,
|
||||
climate: mdiThermostat,
|
||||
configurator: mdiCog,
|
||||
conversation: mdiTextToSpeech,
|
||||
counter: mdiCounter,
|
||||
device_tracker: mdiAccount,
|
||||
fan: mdiFan,
|
||||
google_assistant: mdiGoogleAssistant,
|
||||
group: mdiGoogleCirclesCommunities,
|
||||
homeassistant: mdiHomeAssistant,
|
||||
homekit: mdiHomeAutomation,
|
||||
image_processing: mdiImageFilterFrames,
|
||||
input_boolean: mdiToggleSwitchOutline,
|
||||
input_datetime: mdiCalendarClock,
|
||||
input_number: mdiRayVertex,
|
||||
input_select: mdiFormatListBulleted,
|
||||
input_text: mdiFormTextbox,
|
||||
light: mdiLightbulb,
|
||||
mailbox: mdiMailbox,
|
||||
notify: mdiCommentAlert,
|
||||
number: mdiRayVertex,
|
||||
persistent_notification: mdiBell,
|
||||
person: mdiAccount,
|
||||
plant: mdiFlower,
|
||||
proximity: mdiAppleSafari,
|
||||
remote: mdiRemote,
|
||||
scene: mdiPalette,
|
||||
script: mdiScriptText,
|
||||
select: mdiFormatListBulleted,
|
||||
sensor: mdiEye,
|
||||
siren: mdiBullhorn,
|
||||
simple_alarm: mdiBell,
|
||||
sun: mdiWhiteBalanceSunny,
|
||||
switch: mdiFlash,
|
||||
timer: mdiTimerOutline,
|
||||
updater: mdiCloudUpload,
|
||||
vacuum: mdiRobotVacuum,
|
||||
water_heater: mdiThermometer,
|
||||
weather: mdiWeatherCloudy,
|
||||
zone: mdiMapMarkerRadius,
|
||||
};
|
||||
|
||||
export const FIXED_DEVICE_CLASS_ICONS = {
|
||||
aqi: "hass:air-filter",
|
||||
// battery: "hass:battery", => not included by design since `sensorIcon()` will dynamically determine the icon
|
||||
carbon_dioxide: "mdi:molecule-co2",
|
||||
carbon_monoxide: "mdi:molecule-co",
|
||||
current: "hass:current-ac",
|
||||
date: "hass:calendar",
|
||||
energy: "hass:lightning-bolt",
|
||||
gas: "hass:gas-cylinder",
|
||||
humidity: "hass:water-percent",
|
||||
illuminance: "hass:brightness-5",
|
||||
monetary: "mdi:cash",
|
||||
nitrogen_dioxide: "mdi:molecule",
|
||||
nitrogen_monoxide: "mdi:molecule",
|
||||
nitrous_oxide: "mdi:molecule",
|
||||
ozone: "mdi:molecule",
|
||||
pm1: "mdi:molecule",
|
||||
pm10: "mdi:molecule",
|
||||
pm25: "mdi:molecule",
|
||||
power: "hass:flash",
|
||||
power_factor: "hass:angle-acute",
|
||||
pressure: "hass:gauge",
|
||||
signal_strength: "hass:wifi",
|
||||
sulphur_dioxide: "mdi:molecule",
|
||||
temperature: "hass:thermometer",
|
||||
timestamp: "hass:clock",
|
||||
volatile_organic_compounds: "mdi:molecule",
|
||||
voltage: "hass:sine-wave",
|
||||
aqi: mdiAirFilter,
|
||||
// battery: mdiBattery, => not included by design since `sensorIcon()` will dynamically determine the icon
|
||||
carbon_dioxide: mdiMoleculeCo2,
|
||||
carbon_monoxide: mdiMoleculeCo,
|
||||
current: mdiCurrentAc,
|
||||
date: mdiCalendar,
|
||||
energy: mdiLightningBolt,
|
||||
gas: mdiGasCylinder,
|
||||
humidity: mdiWaterPercent,
|
||||
illuminance: mdiBrightness5,
|
||||
monetary: mdiCash,
|
||||
nitrogen_dioxide: mdiMolecule,
|
||||
nitrogen_monoxide: mdiMolecule,
|
||||
nitrous_oxide: mdiMolecule,
|
||||
ozone: mdiMolecule,
|
||||
pm1: mdiMolecule,
|
||||
pm10: mdiMolecule,
|
||||
pm25: mdiMolecule,
|
||||
power: mdiFlash,
|
||||
power_factor: mdiAngleAcute,
|
||||
pressure: mdiGauge,
|
||||
signal_strength: mdiWifi,
|
||||
sulphur_dioxide: mdiMolecule,
|
||||
temperature: mdiThermometer,
|
||||
timestamp: mdiClock,
|
||||
volatile_organic_compounds: mdiMolecule,
|
||||
voltage: mdiSineWave,
|
||||
};
|
||||
|
||||
/** Domains that have a state card. */
|
||||
|
||||
@@ -36,55 +36,62 @@ export const applyThemesOnElement = (
|
||||
let cacheKey = selectedTheme;
|
||||
let themeRules: Partial<ThemeVars> = {};
|
||||
|
||||
if (themeSettings) {
|
||||
if (themeSettings.dark) {
|
||||
cacheKey = `${cacheKey}__dark`;
|
||||
themeRules = { ...darkStyles };
|
||||
// If there is no explicitly desired dark mode provided, we automatically
|
||||
// use the active one from hass.themes.
|
||||
if (!themeSettings || themeSettings?.dark === undefined) {
|
||||
themeSettings = {
|
||||
...themeSettings,
|
||||
dark: themes.darkMode,
|
||||
};
|
||||
}
|
||||
|
||||
if (themeSettings.dark) {
|
||||
cacheKey = `${cacheKey}__dark`;
|
||||
themeRules = { ...darkStyles };
|
||||
}
|
||||
|
||||
if (selectedTheme === "default") {
|
||||
// Determine the primary and accent colors from the current settings.
|
||||
// Fallbacks are implicitly the HA default blue and orange or the
|
||||
// derived "darkStyles" values, depending on the light vs dark mode.
|
||||
const primaryColor = themeSettings.primaryColor;
|
||||
const accentColor = themeSettings.accentColor;
|
||||
|
||||
if (themeSettings.dark && primaryColor) {
|
||||
themeRules["app-header-background-color"] = hexBlend(
|
||||
primaryColor,
|
||||
"#121212",
|
||||
8
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedTheme === "default") {
|
||||
// Determine the primary and accent colors from the current settings.
|
||||
// Fallbacks are implicitly the HA default blue and orange or the
|
||||
// derived "darkStyles" values, depending on the light vs dark mode.
|
||||
const primaryColor = themeSettings.primaryColor;
|
||||
const accentColor = themeSettings.accentColor;
|
||||
if (primaryColor) {
|
||||
cacheKey = `${cacheKey}__primary_${primaryColor}`;
|
||||
const rgbPrimaryColor = hex2rgb(primaryColor);
|
||||
const labPrimaryColor = rgb2lab(rgbPrimaryColor);
|
||||
themeRules["primary-color"] = primaryColor;
|
||||
const rgbLightPrimaryColor = lab2rgb(labBrighten(labPrimaryColor));
|
||||
themeRules["light-primary-color"] = rgb2hex(rgbLightPrimaryColor);
|
||||
themeRules["dark-primary-color"] = lab2hex(labDarken(labPrimaryColor));
|
||||
themeRules["text-primary-color"] =
|
||||
rgbContrast(rgbPrimaryColor, [33, 33, 33]) < 6 ? "#fff" : "#212121";
|
||||
themeRules["text-light-primary-color"] =
|
||||
rgbContrast(rgbLightPrimaryColor, [33, 33, 33]) < 6
|
||||
? "#fff"
|
||||
: "#212121";
|
||||
themeRules["state-icon-color"] = themeRules["dark-primary-color"];
|
||||
}
|
||||
if (accentColor) {
|
||||
cacheKey = `${cacheKey}__accent_${accentColor}`;
|
||||
themeRules["accent-color"] = accentColor;
|
||||
const rgbAccentColor = hex2rgb(accentColor);
|
||||
themeRules["text-accent-color"] =
|
||||
rgbContrast(rgbAccentColor, [33, 33, 33]) < 6 ? "#fff" : "#212121";
|
||||
}
|
||||
|
||||
if (themeSettings.dark && primaryColor) {
|
||||
themeRules["app-header-background-color"] = hexBlend(
|
||||
primaryColor,
|
||||
"#121212",
|
||||
8
|
||||
);
|
||||
}
|
||||
|
||||
if (primaryColor) {
|
||||
cacheKey = `${cacheKey}__primary_${primaryColor}`;
|
||||
const rgbPrimaryColor = hex2rgb(primaryColor);
|
||||
const labPrimaryColor = rgb2lab(rgbPrimaryColor);
|
||||
themeRules["primary-color"] = primaryColor;
|
||||
const rgbLightPrimaryColor = lab2rgb(labBrighten(labPrimaryColor));
|
||||
themeRules["light-primary-color"] = rgb2hex(rgbLightPrimaryColor);
|
||||
themeRules["dark-primary-color"] = lab2hex(labDarken(labPrimaryColor));
|
||||
themeRules["text-primary-color"] =
|
||||
rgbContrast(rgbPrimaryColor, [33, 33, 33]) < 6 ? "#fff" : "#212121";
|
||||
themeRules["text-light-primary-color"] =
|
||||
rgbContrast(rgbLightPrimaryColor, [33, 33, 33]) < 6
|
||||
? "#fff"
|
||||
: "#212121";
|
||||
themeRules["state-icon-color"] = themeRules["dark-primary-color"];
|
||||
}
|
||||
if (accentColor) {
|
||||
cacheKey = `${cacheKey}__accent_${accentColor}`;
|
||||
themeRules["accent-color"] = accentColor;
|
||||
const rgbAccentColor = hex2rgb(accentColor);
|
||||
themeRules["text-accent-color"] =
|
||||
rgbContrast(rgbAccentColor, [33, 33, 33]) < 6 ? "#fff" : "#212121";
|
||||
}
|
||||
|
||||
// Nothing was changed
|
||||
if (element._themes?.cacheKey === cacheKey) {
|
||||
return;
|
||||
}
|
||||
// Nothing was changed
|
||||
if (element._themes?.cacheKey === cacheKey) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,36 @@
|
||||
/** Return an icon representing a alarm panel state. */
|
||||
|
||||
import {
|
||||
mdiShieldLock,
|
||||
mdiShieldAirplane,
|
||||
mdiShieldHome,
|
||||
mdiShieldMoon,
|
||||
mdiSecurity,
|
||||
mdiShieldOutline,
|
||||
mdiBellRing,
|
||||
mdiShieldOff,
|
||||
mdiShield,
|
||||
} from "@mdi/js";
|
||||
|
||||
export const alarmPanelIcon = (state?: string) => {
|
||||
switch (state) {
|
||||
case "armed_away":
|
||||
return "hass:shield-lock";
|
||||
return mdiShieldLock;
|
||||
case "armed_vacation":
|
||||
return "hass:shield-airplane";
|
||||
return mdiShieldAirplane;
|
||||
case "armed_home":
|
||||
return "hass:shield-home";
|
||||
return mdiShieldHome;
|
||||
case "armed_night":
|
||||
return "hass:shield-moon";
|
||||
return mdiShieldMoon;
|
||||
case "armed_custom_bypass":
|
||||
return "hass:security";
|
||||
return mdiSecurity;
|
||||
case "pending":
|
||||
return "hass:shield-outline";
|
||||
return mdiShieldOutline;
|
||||
case "triggered":
|
||||
return "hass:bell-ring";
|
||||
return mdiBellRing;
|
||||
case "disarmed":
|
||||
return "hass:shield-off";
|
||||
return mdiShieldOff;
|
||||
default:
|
||||
return "hass:shield";
|
||||
return mdiShield;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,35 +1,92 @@
|
||||
/** Return an icon representing a battery state. */
|
||||
import {
|
||||
mdiBattery,
|
||||
mdiBattery10,
|
||||
mdiBattery20,
|
||||
mdiBattery30,
|
||||
mdiBattery40,
|
||||
mdiBattery50,
|
||||
mdiBattery60,
|
||||
mdiBattery70,
|
||||
mdiBattery80,
|
||||
mdiBattery90,
|
||||
mdiBatteryAlert,
|
||||
mdiBatteryAlertVariantOutline,
|
||||
mdiBatteryCharging,
|
||||
mdiBatteryCharging10,
|
||||
mdiBatteryCharging20,
|
||||
mdiBatteryCharging30,
|
||||
mdiBatteryCharging40,
|
||||
mdiBatteryCharging50,
|
||||
mdiBatteryCharging60,
|
||||
mdiBatteryCharging70,
|
||||
mdiBatteryCharging80,
|
||||
mdiBatteryCharging90,
|
||||
mdiBatteryChargingOutline,
|
||||
mdiBatteryUnknown,
|
||||
} from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
|
||||
export const batteryIcon = (
|
||||
const BATTERY_ICONS = {
|
||||
10: mdiBattery10,
|
||||
20: mdiBattery20,
|
||||
30: mdiBattery30,
|
||||
40: mdiBattery40,
|
||||
50: mdiBattery50,
|
||||
60: mdiBattery60,
|
||||
70: mdiBattery70,
|
||||
80: mdiBattery80,
|
||||
90: mdiBattery90,
|
||||
100: mdiBattery,
|
||||
};
|
||||
const BATTERY_CHARGING_ICONS = {
|
||||
10: mdiBatteryCharging10,
|
||||
20: mdiBatteryCharging20,
|
||||
30: mdiBatteryCharging30,
|
||||
40: mdiBatteryCharging40,
|
||||
50: mdiBatteryCharging50,
|
||||
60: mdiBatteryCharging60,
|
||||
70: mdiBatteryCharging70,
|
||||
80: mdiBatteryCharging80,
|
||||
90: mdiBatteryCharging90,
|
||||
100: mdiBatteryCharging,
|
||||
};
|
||||
|
||||
export const batteryStateIcon = (
|
||||
batteryState: HassEntity,
|
||||
batteryChargingState?: HassEntity
|
||||
) => {
|
||||
const battery = Number(batteryState.state);
|
||||
const battery_charging =
|
||||
const battery = batteryState.state;
|
||||
const batteryCharging =
|
||||
batteryChargingState && batteryChargingState.state === "on";
|
||||
let icon = "hass:battery";
|
||||
|
||||
if (isNaN(battery)) {
|
||||
if (batteryState.state === "off") {
|
||||
icon += "-full";
|
||||
} else if (batteryState.state === "on") {
|
||||
icon += "-alert";
|
||||
} else {
|
||||
icon += "-unknown";
|
||||
return batteryIcon(battery, batteryCharging);
|
||||
};
|
||||
|
||||
export const batteryIcon = (
|
||||
batteryState: number | string,
|
||||
batteryCharging?: boolean
|
||||
) => {
|
||||
const batteryValue = Number(batteryState);
|
||||
if (isNaN(batteryValue)) {
|
||||
if (batteryState === "off") {
|
||||
return mdiBattery;
|
||||
}
|
||||
if (batteryState === "on") {
|
||||
return mdiBatteryAlert;
|
||||
}
|
||||
return icon;
|
||||
return mdiBatteryUnknown;
|
||||
}
|
||||
|
||||
const batteryRound = Math.round(battery / 10) * 10;
|
||||
if (battery_charging && battery > 10) {
|
||||
icon += `-charging-${batteryRound}`;
|
||||
} else if (battery_charging) {
|
||||
icon += "-outline";
|
||||
} else if (battery <= 5) {
|
||||
icon += "-alert";
|
||||
} else if (battery > 5 && battery < 95) {
|
||||
icon += `-${batteryRound}`;
|
||||
const batteryRound = Math.round(batteryValue / 10) * 10;
|
||||
if (batteryCharging && batteryValue >= 10) {
|
||||
return BATTERY_CHARGING_ICONS[batteryRound];
|
||||
}
|
||||
if (batteryCharging) {
|
||||
return mdiBatteryChargingOutline;
|
||||
}
|
||||
if (batteryValue <= 5) {
|
||||
return mdiBatteryAlertVariantOutline;
|
||||
}
|
||||
return icon;
|
||||
return BATTERY_ICONS[batteryRound];
|
||||
};
|
||||
|
||||
@@ -1,3 +1,46 @@
|
||||
import {
|
||||
mdiAlertCircle,
|
||||
mdiBattery,
|
||||
mdiBatteryCharging,
|
||||
mdiBatteryOutline,
|
||||
mdiBrightness5,
|
||||
mdiBrightness7,
|
||||
mdiCheckboxMarkedCircle,
|
||||
mdiCheckCircle,
|
||||
mdiCropPortrait,
|
||||
mdiDoorClosed,
|
||||
mdiDoorOpen,
|
||||
mdiFire,
|
||||
mdiGarage,
|
||||
mdiGarageOpen,
|
||||
mdiHome,
|
||||
mdiHomeOutline,
|
||||
mdiLock,
|
||||
mdiLockOpen,
|
||||
mdiMusicNote,
|
||||
mdiMusicNoteOff,
|
||||
mdiPackage,
|
||||
mdiPackageUp,
|
||||
mdiPlay,
|
||||
mdiPowerPlug,
|
||||
mdiPowerPlugOff,
|
||||
mdiRadioboxBlank,
|
||||
mdiRun,
|
||||
mdiServerNetwork,
|
||||
mdiServerNetworkOff,
|
||||
mdiSmoke,
|
||||
mdiSnowflake,
|
||||
mdiSquare,
|
||||
mdiSquareOutline,
|
||||
mdiStop,
|
||||
mdiThermometer,
|
||||
mdiVibrate,
|
||||
mdiWalk,
|
||||
mdiWater,
|
||||
mdiWaterOff,
|
||||
mdiWindowClosed,
|
||||
mdiWindowOpen,
|
||||
} from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
|
||||
/** Return an icon representing a binary sensor state. */
|
||||
@@ -6,53 +49,55 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
|
||||
const is_off = state === "off";
|
||||
switch (stateObj?.attributes.device_class) {
|
||||
case "battery":
|
||||
return is_off ? "hass:battery" : "hass:battery-outline";
|
||||
return is_off ? mdiBattery : mdiBatteryOutline;
|
||||
case "battery_charging":
|
||||
return is_off ? "hass:battery" : "hass:battery-charging";
|
||||
return is_off ? mdiBattery : mdiBatteryCharging;
|
||||
case "cold":
|
||||
return is_off ? "hass:thermometer" : "hass:snowflake";
|
||||
return is_off ? mdiThermometer : mdiSnowflake;
|
||||
case "connectivity":
|
||||
return is_off ? "hass:server-network-off" : "hass:server-network";
|
||||
return is_off ? mdiServerNetworkOff : mdiServerNetwork;
|
||||
case "door":
|
||||
return is_off ? "hass:door-closed" : "hass:door-open";
|
||||
return is_off ? mdiDoorClosed : mdiDoorOpen;
|
||||
case "garage_door":
|
||||
return is_off ? "hass:garage" : "hass:garage-open";
|
||||
return is_off ? mdiGarage : mdiGarageOpen;
|
||||
case "power":
|
||||
return is_off ? "hass:power-plug-off" : "hass:power-plug";
|
||||
return is_off ? mdiPowerPlugOff : mdiPowerPlug;
|
||||
case "gas":
|
||||
case "problem":
|
||||
case "safety":
|
||||
case "tamper":
|
||||
return is_off ? "hass:check-circle" : "hass:alert-circle";
|
||||
return is_off ? mdiCheckCircle : mdiAlertCircle;
|
||||
case "smoke":
|
||||
return is_off ? "hass:check-circle" : "hass:smoke";
|
||||
return is_off ? mdiCheckCircle : mdiSmoke;
|
||||
case "heat":
|
||||
return is_off ? "hass:thermometer" : "hass:fire";
|
||||
return is_off ? mdiThermometer : mdiFire;
|
||||
case "light":
|
||||
return is_off ? "hass:brightness-5" : "hass:brightness-7";
|
||||
return is_off ? mdiBrightness5 : mdiBrightness7;
|
||||
case "lock":
|
||||
return is_off ? "hass:lock" : "hass:lock-open";
|
||||
return is_off ? mdiLock : mdiLockOpen;
|
||||
case "moisture":
|
||||
return is_off ? "hass:water-off" : "hass:water";
|
||||
return is_off ? mdiWaterOff : mdiWater;
|
||||
case "motion":
|
||||
return is_off ? "hass:walk" : "hass:run";
|
||||
return is_off ? mdiWalk : mdiRun;
|
||||
case "occupancy":
|
||||
return is_off ? "hass:home-outline" : "hass:home";
|
||||
return is_off ? mdiHomeOutline : mdiHome;
|
||||
case "opening":
|
||||
return is_off ? "hass:square" : "hass:square-outline";
|
||||
return is_off ? mdiSquare : mdiSquareOutline;
|
||||
case "plug":
|
||||
return is_off ? "hass:power-plug-off" : "hass:power-plug";
|
||||
return is_off ? mdiPowerPlugOff : mdiPowerPlug;
|
||||
case "presence":
|
||||
return is_off ? "hass:home-outline" : "hass:home";
|
||||
return is_off ? mdiHomeOutline : mdiHome;
|
||||
case "running":
|
||||
return is_off ? mdiStop : mdiPlay;
|
||||
case "sound":
|
||||
return is_off ? "hass:music-note-off" : "hass:music-note";
|
||||
return is_off ? mdiMusicNoteOff : mdiMusicNote;
|
||||
case "update":
|
||||
return is_off ? "mdi:package" : "mdi:package-up";
|
||||
return is_off ? mdiPackage : mdiPackageUp;
|
||||
case "vibration":
|
||||
return is_off ? "hass:crop-portrait" : "hass:vibrate";
|
||||
return is_off ? mdiCropPortrait : mdiVibrate;
|
||||
case "window":
|
||||
return is_off ? "hass:window-closed" : "hass:window-open";
|
||||
return is_off ? mdiWindowClosed : mdiWindowOpen;
|
||||
default:
|
||||
return is_off ? "hass:radiobox-blank" : "hass:checkbox-marked-circle";
|
||||
return is_off ? mdiRadioboxBlank : mdiCheckboxMarkedCircle;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ export const computeStateDisplay = (
|
||||
const domain = computeStateDomain(stateObj);
|
||||
|
||||
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.
|
||||
// Attributes aren't available, we have to use `state`.
|
||||
try {
|
||||
@@ -63,7 +63,7 @@ export const computeStateDisplay = (
|
||||
}
|
||||
}
|
||||
return state;
|
||||
} catch {
|
||||
} catch (_e) {
|
||||
// Formatting methods may throw error if date parsing doesn't go well,
|
||||
// just return the state string in that case.
|
||||
return state;
|
||||
@@ -71,7 +71,17 @@ export const computeStateDisplay = (
|
||||
} else {
|
||||
// If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format.
|
||||
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(
|
||||
stateObj.attributes.year,
|
||||
stateObj.attributes.month - 1,
|
||||
@@ -79,20 +89,12 @@ export const computeStateDisplay = (
|
||||
);
|
||||
return formatDate(date, locale);
|
||||
}
|
||||
if (!stateObj.attributes.has_date) {
|
||||
if (stateObj.attributes.has_time) {
|
||||
date = new Date();
|
||||
date.setHours(stateObj.attributes.hour, stateObj.attributes.minute);
|
||||
return formatTime(date, locale);
|
||||
}
|
||||
|
||||
date = new Date(
|
||||
stateObj.attributes.year,
|
||||
stateObj.attributes.month - 1,
|
||||
stateObj.attributes.day,
|
||||
stateObj.attributes.hour,
|
||||
stateObj.attributes.minute
|
||||
);
|
||||
return formatDateTime(date, locale);
|
||||
return stateObj.state;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,30 @@
|
||||
/** Return an icon representing a cover state. */
|
||||
import {
|
||||
mdiArrowUpBox,
|
||||
mdiArrowDownBox,
|
||||
mdiGarage,
|
||||
mdiGarageOpen,
|
||||
mdiGateArrowRight,
|
||||
mdiGate,
|
||||
mdiGateOpen,
|
||||
mdiDoorOpen,
|
||||
mdiDoorClosed,
|
||||
mdiCircle,
|
||||
mdiWindowShutter,
|
||||
mdiWindowShutterOpen,
|
||||
mdiBlinds,
|
||||
mdiBlindsOpen,
|
||||
mdiWindowClosed,
|
||||
mdiWindowOpen,
|
||||
mdiArrowExpandHorizontal,
|
||||
mdiArrowUp,
|
||||
mdiArrowCollapseHorizontal,
|
||||
mdiArrowDown,
|
||||
mdiCircleSlice8,
|
||||
mdiArrowSplitVertical,
|
||||
mdiCurtains,
|
||||
mdiCurtainsClosed,
|
||||
} from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
|
||||
export const coverIcon = (state?: string, stateObj?: HassEntity): string => {
|
||||
@@ -8,74 +34,84 @@ export const coverIcon = (state?: string, stateObj?: HassEntity): string => {
|
||||
case "garage":
|
||||
switch (state) {
|
||||
case "opening":
|
||||
return "hass:arrow-up-box";
|
||||
return mdiArrowUpBox;
|
||||
case "closing":
|
||||
return "hass:arrow-down-box";
|
||||
return mdiArrowDownBox;
|
||||
case "closed":
|
||||
return "hass:garage";
|
||||
return mdiGarage;
|
||||
default:
|
||||
return "hass:garage-open";
|
||||
return mdiGarageOpen;
|
||||
}
|
||||
case "gate":
|
||||
switch (state) {
|
||||
case "opening":
|
||||
case "closing":
|
||||
return "hass:gate-arrow-right";
|
||||
return mdiGateArrowRight;
|
||||
case "closed":
|
||||
return "hass:gate";
|
||||
return mdiGate;
|
||||
default:
|
||||
return "hass:gate-open";
|
||||
return mdiGateOpen;
|
||||
}
|
||||
case "door":
|
||||
return open ? "hass:door-open" : "hass:door-closed";
|
||||
return open ? mdiDoorOpen : mdiDoorClosed;
|
||||
case "damper":
|
||||
return open ? "hass:circle" : "hass:circle-slice-8";
|
||||
return open ? mdiCircle : mdiCircleSlice8;
|
||||
case "shutter":
|
||||
switch (state) {
|
||||
case "opening":
|
||||
return "hass:arrow-up-box";
|
||||
return mdiArrowUpBox;
|
||||
case "closing":
|
||||
return "hass:arrow-down-box";
|
||||
return mdiArrowDownBox;
|
||||
case "closed":
|
||||
return "hass:window-shutter";
|
||||
return mdiWindowShutter;
|
||||
default:
|
||||
return "hass:window-shutter-open";
|
||||
return mdiWindowShutterOpen;
|
||||
}
|
||||
case "curtain":
|
||||
switch (state) {
|
||||
case "opening":
|
||||
return mdiArrowSplitVertical;
|
||||
case "closing":
|
||||
return mdiArrowCollapseHorizontal;
|
||||
case "closed":
|
||||
return mdiCurtainsClosed;
|
||||
default:
|
||||
return mdiCurtains;
|
||||
}
|
||||
case "blind":
|
||||
case "curtain":
|
||||
case "shade":
|
||||
switch (state) {
|
||||
case "opening":
|
||||
return "hass:arrow-up-box";
|
||||
return mdiArrowUpBox;
|
||||
case "closing":
|
||||
return "hass:arrow-down-box";
|
||||
return mdiArrowDownBox;
|
||||
case "closed":
|
||||
return "hass:blinds";
|
||||
return mdiBlinds;
|
||||
default:
|
||||
return "hass:blinds-open";
|
||||
return mdiBlindsOpen;
|
||||
}
|
||||
case "window":
|
||||
switch (state) {
|
||||
case "opening":
|
||||
return "hass:arrow-up-box";
|
||||
return mdiArrowUpBox;
|
||||
case "closing":
|
||||
return "hass:arrow-down-box";
|
||||
return mdiArrowDownBox;
|
||||
case "closed":
|
||||
return "hass:window-closed";
|
||||
return mdiWindowClosed;
|
||||
default:
|
||||
return "hass:window-open";
|
||||
return mdiWindowOpen;
|
||||
}
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case "opening":
|
||||
return "hass:arrow-up-box";
|
||||
return mdiArrowUpBox;
|
||||
case "closing":
|
||||
return "hass:arrow-down-box";
|
||||
return mdiArrowDownBox;
|
||||
case "closed":
|
||||
return "hass:window-closed";
|
||||
return mdiWindowClosed;
|
||||
default:
|
||||
return "hass:window-open";
|
||||
return mdiWindowOpen;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,9 +120,9 @@ export const computeOpenIcon = (stateObj: HassEntity): string => {
|
||||
case "awning":
|
||||
case "door":
|
||||
case "gate":
|
||||
return "hass:arrow-expand-horizontal";
|
||||
return mdiArrowExpandHorizontal;
|
||||
default:
|
||||
return "hass:arrow-up";
|
||||
return mdiArrowUp;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -95,8 +131,8 @@ export const computeCloseIcon = (stateObj: HassEntity): string => {
|
||||
case "awning":
|
||||
case "door":
|
||||
case "gate":
|
||||
return "hass:arrow-collapse-horizontal";
|
||||
return mdiArrowCollapseHorizontal;
|
||||
default:
|
||||
return "hass:arrow-down";
|
||||
return mdiArrowDown;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
import {
|
||||
mdiAirHumidifierOff,
|
||||
mdiAirHumidifier,
|
||||
mdiLockOpen,
|
||||
mdiLockAlert,
|
||||
mdiLockClock,
|
||||
mdiLock,
|
||||
mdiCastConnected,
|
||||
mdiCast,
|
||||
mdiEmoticonDead,
|
||||
mdiSleep,
|
||||
mdiTimerSand,
|
||||
mdiZWave,
|
||||
mdiClock,
|
||||
mdiCalendar,
|
||||
mdiWeatherNight,
|
||||
} from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
/**
|
||||
* Return the icon to be used for a domain.
|
||||
@@ -28,36 +45,34 @@ export const domainIcon = (
|
||||
return coverIcon(compareState, stateObj);
|
||||
|
||||
case "humidifier":
|
||||
return state && state === "off"
|
||||
? "hass:air-humidifier-off"
|
||||
: "hass:air-humidifier";
|
||||
return state && state === "off" ? mdiAirHumidifierOff : mdiAirHumidifier;
|
||||
|
||||
case "lock":
|
||||
switch (compareState) {
|
||||
case "unlocked":
|
||||
return "hass:lock-open";
|
||||
return mdiLockOpen;
|
||||
case "jammed":
|
||||
return "hass:lock-alert";
|
||||
return mdiLockAlert;
|
||||
case "locking":
|
||||
case "unlocking":
|
||||
return "hass:lock-clock";
|
||||
return mdiLockClock;
|
||||
default:
|
||||
return "hass:lock";
|
||||
return mdiLock;
|
||||
}
|
||||
|
||||
case "media_player":
|
||||
return compareState === "playing" ? "hass:cast-connected" : "hass:cast";
|
||||
return compareState === "playing" ? mdiCastConnected : mdiCast;
|
||||
|
||||
case "zwave":
|
||||
switch (compareState) {
|
||||
case "dead":
|
||||
return "hass:emoticon-dead";
|
||||
return mdiEmoticonDead;
|
||||
case "sleeping":
|
||||
return "hass:sleep";
|
||||
return mdiSleep;
|
||||
case "initializing":
|
||||
return "hass:timer-sand";
|
||||
return mdiTimerSand;
|
||||
default:
|
||||
return "hass:z-wave";
|
||||
return mdiZWave;
|
||||
}
|
||||
|
||||
case "sensor": {
|
||||
@@ -71,17 +86,17 @@ export const domainIcon = (
|
||||
|
||||
case "input_datetime":
|
||||
if (!stateObj?.attributes.has_date) {
|
||||
return "hass:clock";
|
||||
return mdiClock;
|
||||
}
|
||||
if (!stateObj.attributes.has_time) {
|
||||
return "hass:calendar";
|
||||
return mdiCalendar;
|
||||
}
|
||||
break;
|
||||
|
||||
case "sun":
|
||||
return stateObj?.state === "above_horizon"
|
||||
? FIXED_DOMAIN_ICONS[domain]
|
||||
: "hass:weather-night";
|
||||
: mdiWeatherNight;
|
||||
}
|
||||
|
||||
if (domain in FIXED_DOMAIN_ICONS) {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/** Return an icon representing a sensor state. */
|
||||
import { mdiBattery, mdiThermometer } from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { FIXED_DEVICE_CLASS_ICONS, UNIT_C, UNIT_F } from "../const";
|
||||
import { batteryIcon } from "./battery_icon";
|
||||
import { SENSOR_DEVICE_CLASS_BATTERY } from "../../data/sensor";
|
||||
import { FIXED_DEVICE_CLASS_ICONS, UNIT_C, UNIT_F } from "../const";
|
||||
import { batteryStateIcon } from "./battery_icon";
|
||||
|
||||
export const sensorIcon = (stateObj?: HassEntity): string | undefined => {
|
||||
const dclass = stateObj?.attributes.device_class;
|
||||
@@ -12,12 +13,12 @@ export const sensorIcon = (stateObj?: HassEntity): string | undefined => {
|
||||
}
|
||||
|
||||
if (dclass === SENSOR_DEVICE_CLASS_BATTERY) {
|
||||
return stateObj ? batteryIcon(stateObj) : "hass:battery";
|
||||
return stateObj ? batteryStateIcon(stateObj) : mdiBattery;
|
||||
}
|
||||
|
||||
const unit = stateObj?.attributes.unit_of_measurement;
|
||||
if (unit === UNIT_C || unit === UNIT_F) {
|
||||
return "hass:thermometer";
|
||||
return mdiThermometer;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
@@ -4,13 +4,9 @@ import { DEFAULT_DOMAIN_ICON } from "../const";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
import { domainIcon } from "./domain_icon";
|
||||
|
||||
export const stateIcon = (state?: HassEntity) => {
|
||||
export const stateIconPath = (state?: HassEntity) => {
|
||||
if (!state) {
|
||||
return DEFAULT_DOMAIN_ICON;
|
||||
}
|
||||
if (state.attributes.icon) {
|
||||
return state.attributes.icon;
|
||||
}
|
||||
|
||||
return domainIcon(computeDomain(state.entity_id), state);
|
||||
};
|
||||
@@ -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(/&/g, `${delimiter}and${delimiter}`) // Replace & with 'and'
|
||||
.replace(/[^\w-]+/g, "") // Remove all non-word characters
|
||||
.replace(/-/, delimiter) // Replace - with delimiter
|
||||
.replace(new RegExp(`/${delimiter}${delimiter}+/`, "g"), delimiter) // Replace multiple delimiters with single delimiter
|
||||
.replace(new RegExp(`/^${delimiter}+/`), "") // Trim delimiter from start of text
|
||||
.replace(new RegExp(`/-+$/`), ""); // Trim delimiter from end of text
|
||||
.replace(/-/g, delimiter) // Replace - with delimiter
|
||||
.replace(new RegExp(`(${delimiter})\\1+`, "g"), "$1") // Replace multiple delimiters with single delimiter
|
||||
.replace(new RegExp(`^${delimiter}+`), "") // Trim delimiter from start of text
|
||||
.replace(new RegExp(`${delimiter}+$`), ""); // Trim delimiter from end of text
|
||||
};
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
import { css } from "lit";
|
||||
|
||||
export const iconColorCSS = css`
|
||||
ha-icon[data-domain="alert"][data-state="on"],
|
||||
ha-icon[data-domain="automation"][data-state="on"],
|
||||
ha-icon[data-domain="binary_sensor"][data-state="on"],
|
||||
ha-icon[data-domain="calendar"][data-state="on"],
|
||||
ha-icon[data-domain="camera"][data-state="streaming"],
|
||||
ha-icon[data-domain="cover"][data-state="open"],
|
||||
ha-icon[data-domain="fan"][data-state="on"],
|
||||
ha-icon[data-domain="humidifier"][data-state="on"],
|
||||
ha-icon[data-domain="light"][data-state="on"],
|
||||
ha-icon[data-domain="input_boolean"][data-state="on"],
|
||||
ha-icon[data-domain="lock"][data-state="unlocked"],
|
||||
ha-icon[data-domain="media_player"][data-state="on"],
|
||||
ha-icon[data-domain="media_player"][data-state="paused"],
|
||||
ha-icon[data-domain="media_player"][data-state="playing"],
|
||||
ha-icon[data-domain="script"][data-state="on"],
|
||||
ha-icon[data-domain="sun"][data-state="above_horizon"],
|
||||
ha-icon[data-domain="switch"][data-state="on"],
|
||||
ha-icon[data-domain="timer"][data-state="active"],
|
||||
ha-icon[data-domain="vacuum"][data-state="cleaning"],
|
||||
ha-icon[data-domain="group"][data-state="on"],
|
||||
ha-icon[data-domain="group"][data-state="home"],
|
||||
ha-icon[data-domain="group"][data-state="open"],
|
||||
ha-icon[data-domain="group"][data-state="locked"],
|
||||
ha-icon[data-domain="group"][data-state="problem"] {
|
||||
ha-state-icon[data-domain="alert"][data-state="on"],
|
||||
ha-state-icon[data-domain="automation"][data-state="on"],
|
||||
ha-state-icon[data-domain="binary_sensor"][data-state="on"],
|
||||
ha-state-icon[data-domain="calendar"][data-state="on"],
|
||||
ha-state-icon[data-domain="camera"][data-state="streaming"],
|
||||
ha-state-icon[data-domain="cover"][data-state="open"],
|
||||
ha-state-icon[data-domain="fan"][data-state="on"],
|
||||
ha-state-icon[data-domain="humidifier"][data-state="on"],
|
||||
ha-state-icon[data-domain="light"][data-state="on"],
|
||||
ha-state-icon[data-domain="input_boolean"][data-state="on"],
|
||||
ha-state-icon[data-domain="lock"][data-state="unlocked"],
|
||||
ha-state-icon[data-domain="media_player"][data-state="on"],
|
||||
ha-state-icon[data-domain="media_player"][data-state="paused"],
|
||||
ha-state-icon[data-domain="media_player"][data-state="playing"],
|
||||
ha-state-icon[data-domain="script"][data-state="on"],
|
||||
ha-state-icon[data-domain="sun"][data-state="above_horizon"],
|
||||
ha-state-icon[data-domain="switch"][data-state="on"],
|
||||
ha-state-icon[data-domain="timer"][data-state="active"],
|
||||
ha-state-icon[data-domain="vacuum"][data-state="cleaning"],
|
||||
ha-state-icon[data-domain="group"][data-state="on"],
|
||||
ha-state-icon[data-domain="group"][data-state="home"],
|
||||
ha-state-icon[data-domain="group"][data-state="open"],
|
||||
ha-state-icon[data-domain="group"][data-state="locked"],
|
||||
ha-state-icon[data-domain="group"][data-state="problem"] {
|
||||
color: var(--paper-item-icon-active-color, #fdd835);
|
||||
}
|
||||
|
||||
ha-icon[data-domain="climate"][data-state="cooling"] {
|
||||
ha-state-icon[data-domain="climate"][data-state="cooling"] {
|
||||
color: var(--cool-color, var(--state-climate-cool-color));
|
||||
}
|
||||
|
||||
ha-icon[data-domain="climate"][data-state="heating"] {
|
||||
ha-state-icon[data-domain="climate"][data-state="heating"] {
|
||||
color: var(--heat-color, var(--state-climate-heat-color));
|
||||
}
|
||||
|
||||
ha-icon[data-domain="climate"][data-state="drying"] {
|
||||
ha-state-icon[data-domain="climate"][data-state="drying"] {
|
||||
color: var(--dry-color, var(--state-climate-dry-color));
|
||||
}
|
||||
|
||||
ha-icon[data-domain="alarm_control_panel"] {
|
||||
ha-state-icon[data-domain="alarm_control_panel"] {
|
||||
color: var(--alarm-color-armed, var(--label-badge-red));
|
||||
}
|
||||
ha-icon[data-domain="alarm_control_panel"][data-state="disarmed"] {
|
||||
ha-state-icon[data-domain="alarm_control_panel"][data-state="disarmed"] {
|
||||
color: var(--alarm-color-disarmed, var(--label-badge-green));
|
||||
}
|
||||
ha-icon[data-domain="alarm_control_panel"][data-state="pending"],
|
||||
ha-icon[data-domain="alarm_control_panel"][data-state="arming"] {
|
||||
ha-state-icon[data-domain="alarm_control_panel"][data-state="pending"],
|
||||
ha-state-icon[data-domain="alarm_control_panel"][data-state="arming"] {
|
||||
color: var(--alarm-color-pending, var(--label-badge-yellow));
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
ha-icon[data-domain="alarm_control_panel"][data-state="triggered"] {
|
||||
ha-state-icon[data-domain="alarm_control_panel"][data-state="triggered"] {
|
||||
color: var(--alarm-color-triggered, var(--label-badge-red));
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
@@ -68,13 +68,13 @@ export const iconColorCSS = css`
|
||||
}
|
||||
}
|
||||
|
||||
ha-icon[data-domain="plant"][data-state="problem"],
|
||||
ha-icon[data-domain="zwave"][data-state="dead"] {
|
||||
ha-state-icon[data-domain="plant"][data-state="problem"],
|
||||
ha-state-icon[data-domain="zwave"][data-state="dead"] {
|
||||
color: var(--state-icon-error-color);
|
||||
}
|
||||
|
||||
/* Color the icon if unavailable */
|
||||
ha-icon[data-state="unavailable"] {
|
||||
ha-state-icon[data-state="unavailable"] {
|
||||
color: var(--state-unavailable-color);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -12,22 +12,19 @@ import { HomeAssistant } from "../../types";
|
||||
import "./ha-chart-base";
|
||||
import type { TimeLineData } from "./timeline-chart/const";
|
||||
|
||||
/** Binary sensor device classes for which the static colors for on/off need to be inverted.
|
||||
* List the ones were "off" = good or normal state = should be rendered "green".
|
||||
/** Binary sensor device classes for which the static colors for on/off are NOT inverted.
|
||||
* List the ones were "on" = good or normal state => should be rendered "green".
|
||||
* Note: It is now a "not inverted" list (compared to the past) since we now have more inverted ones.
|
||||
*/
|
||||
const BINARY_SENSOR_DEVICE_CLASS_COLOR_INVERTED = new Set([
|
||||
"battery",
|
||||
"door",
|
||||
"garage_door",
|
||||
"gas",
|
||||
"lock",
|
||||
"motion",
|
||||
"opening",
|
||||
"problem",
|
||||
"safety",
|
||||
"smoke",
|
||||
"tamper",
|
||||
"window",
|
||||
const BINARY_SENSOR_DEVICE_CLASS_COLOR_NOT_INVERTED = new Set([
|
||||
"battery_charging",
|
||||
"connectivity",
|
||||
"light",
|
||||
"moving",
|
||||
"plug",
|
||||
"power",
|
||||
"presence",
|
||||
"update",
|
||||
]);
|
||||
|
||||
const STATIC_STATE_COLORS = new Set([
|
||||
@@ -48,7 +45,7 @@ const invertOnOff = (entityState?: HassEntity) =>
|
||||
entityState &&
|
||||
computeDomain(entityState.entity_id) === "binary_sensor" &&
|
||||
"device_class" in entityState.attributes &&
|
||||
BINARY_SENSOR_DEVICE_CLASS_COLOR_INVERTED.has(
|
||||
!BINARY_SENSOR_DEVICE_CLASS_COLOR_NOT_INVERTED.has(
|
||||
entityState.attributes.device_class!
|
||||
);
|
||||
|
||||
|
||||
@@ -747,10 +747,16 @@ export class HaDataTable extends LitElement {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell--icon:first-child ha-icon {
|
||||
.mdc-data-table__cell--icon:first-child ha-icon,
|
||||
.mdc-data-table__cell--icon:first-child ha-state-icon,
|
||||
.mdc-data-table__cell--icon:first-child ha-svg-icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
:host([dir="rtl"]) .mdc-data-table__cell--icon:first-child ha-icon {
|
||||
:host([dir="rtl"]) .mdc-data-table__cell--icon:first-child ha-icon,
|
||||
:host([dir="rtl"])
|
||||
.mdc-data-table__cell--icon:first-child
|
||||
ha-state-icon,
|
||||
:host([dir="rtl"]) .mdc-data-table__cell--icon:first-child ha-svg-icon {
|
||||
margin-left: auto;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at
|
||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||
part of the polymer project is also subject to an additional IP rights grant
|
||||
found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
/*
|
||||
Fixes issue with not using shadow dom properly in iron-overlay-behavior/icon-focusables-helper.js
|
||||
*/
|
||||
import { IronFocusablesHelper } from "@polymer/iron-overlay-behavior/iron-focusables-helper";
|
||||
import { dom } from "@polymer/polymer/lib/legacy/polymer.dom";
|
||||
|
||||
export const HaIronFocusablesHelper = {
|
||||
/**
|
||||
* Returns a sorted array of tabbable nodes, including the root node.
|
||||
* It searches the tabbable nodes in the light and shadow dom of the chidren,
|
||||
* sorting the result by tabindex.
|
||||
* @param {!Node} node
|
||||
* @return {!Array<!HTMLElement>}
|
||||
*/
|
||||
getTabbableNodes: function (node) {
|
||||
const result = [];
|
||||
// If there is at least one element with tabindex > 0, we need to sort
|
||||
// the final array by tabindex.
|
||||
const needsSortByTabIndex = this._collectTabbableNodes(node, result);
|
||||
if (needsSortByTabIndex) {
|
||||
return IronFocusablesHelper._sortByTabIndex(result);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Searches for nodes that are tabbable and adds them to the `result` array.
|
||||
* Returns if the `result` array needs to be sorted by tabindex.
|
||||
* @param {!Node} node The starting point for the search; added to `result`
|
||||
* if tabbable.
|
||||
* @param {!Array<!HTMLElement>} result
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
_collectTabbableNodes: function (node, result) {
|
||||
// If not an element or not visible, no need to explore children.
|
||||
if (
|
||||
node.nodeType !== Node.ELEMENT_NODE ||
|
||||
!IronFocusablesHelper._isVisible(node)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const element = /** @type {!HTMLElement} */ (node);
|
||||
const tabIndex = IronFocusablesHelper._normalizedTabIndex(element);
|
||||
let needsSort = tabIndex > 0;
|
||||
if (tabIndex >= 0) {
|
||||
result.push(element);
|
||||
}
|
||||
|
||||
// In ShadowDOM v1, tab order is affected by the order of distrubution.
|
||||
// E.g. getTabbableNodes(#root) in ShadowDOM v1 should return [#A, #B];
|
||||
// in ShadowDOM v0 tab order is not affected by the distrubution order,
|
||||
// in fact getTabbableNodes(#root) returns [#B, #A].
|
||||
// <div id="root">
|
||||
// <!-- shadow -->
|
||||
// <slot name="a">
|
||||
// <slot name="b">
|
||||
// <!-- /shadow -->
|
||||
// <input id="A" slot="a">
|
||||
// <input id="B" slot="b" tabindex="1">
|
||||
// </div>
|
||||
// TODO(valdrin) support ShadowDOM v1 when upgrading to Polymer v2.0.
|
||||
let children;
|
||||
if (element.localName === "content" || element.localName === "slot") {
|
||||
children = dom(element).getDistributedNodes();
|
||||
} else {
|
||||
// /////////////////////////
|
||||
// Use shadow root if possible, will check for distributed nodes.
|
||||
// THIS IS THE CHANGED LINE
|
||||
children = dom(element.shadowRoot || element.root || element).children;
|
||||
// /////////////////////////
|
||||
}
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
// Ensure method is always invoked to collect tabbable children.
|
||||
needsSort = this._collectTabbableNodes(children[i], result) || needsSort;
|
||||
}
|
||||
return needsSort;
|
||||
},
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
import "@polymer/paper-dialog/paper-dialog";
|
||||
import type { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
|
||||
import { mixinBehaviors } from "@polymer/polymer/lib/legacy/class";
|
||||
import type { Constructor } from "../../types";
|
||||
import { HaIronFocusablesHelper } from "./ha-iron-focusables-helper";
|
||||
|
||||
const paperDialogClass = customElements.get(
|
||||
"paper-dialog"
|
||||
) as Constructor<PaperDialogElement>;
|
||||
|
||||
// behavior that will override existing iron-overlay-behavior and call the fixed implementation
|
||||
const haTabFixBehaviorImpl = {
|
||||
get _focusableNodes() {
|
||||
return HaIronFocusablesHelper.getTabbableNodes(this);
|
||||
},
|
||||
};
|
||||
|
||||
// paper-dialog that uses the haTabFixBehaviorImpl behavior
|
||||
// export class HaPaperDialog extends paperDialogClass {}
|
||||
// @ts-ignore
|
||||
export class HaPaperDialog
|
||||
extends mixinBehaviors([haTabFixBehaviorImpl], paperDialogClass)
|
||||
implements PaperDialogElement {}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-paper-dialog": HaPaperDialog;
|
||||
}
|
||||
}
|
||||
// @ts-ignore
|
||||
customElements.define("ha-paper-dialog", HaPaperDialog);
|
||||
@@ -1,7 +1,7 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { batteryIcon } from "../../common/entity/battery_icon";
|
||||
import "../ha-icon";
|
||||
import { batteryStateIcon } from "../../common/entity/battery_icon";
|
||||
import "../ha-svg-icon";
|
||||
|
||||
@customElement("ha-battery-icon")
|
||||
export class HaBatteryIcon extends LitElement {
|
||||
@@ -11,9 +11,18 @@ export class HaBatteryIcon extends LitElement {
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-icon
|
||||
.icon=${batteryIcon(this.batteryStateObj, this.batteryChargingStateObj)}
|
||||
></ha-icon>
|
||||
<ha-svg-icon
|
||||
.path=${batteryStateIcon(
|
||||
this.batteryStateObj,
|
||||
this.batteryChargingStateObj
|
||||
)}
|
||||
></ha-svg-icon>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-battery-icon": HaBatteryIcon;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,19 +14,18 @@ import secondsToDuration from "../../common/datetime/seconds_to_duration";
|
||||
import { computeStateDisplay } from "../../common/entity/compute_state_display";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { stateIcon } from "../../common/entity/state_icon";
|
||||
import { formatNumber } from "../../common/number/format_number";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||
import { timerTimeRemaining } from "../../data/timer";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-label-badge";
|
||||
import "../ha-icon";
|
||||
import "../ha-state-icon";
|
||||
|
||||
@customElement("ha-state-label-badge")
|
||||
export class HaStateLabelBadge extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property() public state?: HassEntity;
|
||||
@property({ attribute: false }) public state?: HassEntity;
|
||||
|
||||
@property() public name?: string;
|
||||
|
||||
@@ -69,16 +68,23 @@ export class HaStateLabelBadge extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
// Rendering priority inside badge:
|
||||
// 1. Icon directly defined in badge config
|
||||
// 2. Image directly defined in badge config
|
||||
// 3. Image taken from entity picture
|
||||
// 4. Icon determined via entity state
|
||||
// 5. Value string as fallback
|
||||
const domain = computeStateDomain(entityState);
|
||||
|
||||
const value = this._computeValue(domain, entityState);
|
||||
const icon = this.icon ? this.icon : this._computeIcon(domain, entityState);
|
||||
const showIcon = this.icon || this._computeShowIcon(domain, entityState);
|
||||
const image = this.icon
|
||||
? ""
|
||||
: this.image
|
||||
? this.image
|
||||
: entityState.attributes.entity_picture_local ||
|
||||
entityState.attributes.entity_picture;
|
||||
const value =
|
||||
!image && !showIcon ? this._computeValue(domain, entityState) : undefined;
|
||||
|
||||
return html`
|
||||
<ha-label-badge
|
||||
@@ -95,8 +101,13 @@ export class HaStateLabelBadge extends LitElement {
|
||||
)}
|
||||
.description=${this.name ?? computeStateName(entityState)}
|
||||
>
|
||||
${!image && icon ? html`<ha-icon .icon=${icon}></ha-icon>` : ""}
|
||||
${value && (this.icon || !this.image)
|
||||
${!image && showIcon
|
||||
? html`<ha-state-icon
|
||||
.icon=${this.icon}
|
||||
.state=${entityState}
|
||||
></ha-state-icon>`
|
||||
: ""}
|
||||
${value && !image && !showIcon
|
||||
? html`<span class=${value && value.length > 4 ? "big" : ""}
|
||||
>${value}</span
|
||||
>`
|
||||
@@ -144,9 +155,9 @@ export class HaStateLabelBadge extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _computeIcon(domain: string, entityState: HassEntity) {
|
||||
private _computeShowIcon(domain: string, entityState: HassEntity): boolean {
|
||||
if (entityState.state === UNAVAILABLE) {
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
switch (domain) {
|
||||
case "alarm_control_panel":
|
||||
@@ -156,17 +167,13 @@ export class HaStateLabelBadge extends LitElement {
|
||||
case "person":
|
||||
case "scene":
|
||||
case "sun":
|
||||
return stateIcon(entityState);
|
||||
return true;
|
||||
case "timer":
|
||||
return entityState.state === "active"
|
||||
? "hass:timer-outline"
|
||||
: "hass:timer-off-outline";
|
||||
return true;
|
||||
case "sensor":
|
||||
return entityState.attributes.device_class === "moon__phase"
|
||||
? stateIcon(entityState)
|
||||
: null;
|
||||
return entityState.attributes.device_class === "moon__phase";
|
||||
default:
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { mdiAlert } from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
@@ -12,10 +13,9 @@ import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeActiveState } from "../../common/entity/compute_active_state";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { stateIcon } from "../../common/entity/state_icon";
|
||||
import { iconColorCSS } from "../../common/style/icon_color_css";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-icon";
|
||||
import "../ha-state-icon";
|
||||
|
||||
export class StateBadge extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
@@ -39,7 +39,7 @@ export class StateBadge extends LitElement {
|
||||
// We either need a `stateObj` or one override
|
||||
if (!stateObj && !this.overrideIcon && !this.overrideImage) {
|
||||
return html`<div class="missing">
|
||||
<ha-icon icon="hass:alert"></ha-icon>
|
||||
<ha-svg-icon .path=${mdiAlert}></ha-svg-icon>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -49,18 +49,17 @@ export class StateBadge extends LitElement {
|
||||
|
||||
const domain = stateObj ? computeStateDomain(stateObj) : undefined;
|
||||
|
||||
return html`
|
||||
<ha-icon
|
||||
style=${styleMap(this._iconStyle)}
|
||||
data-domain=${ifDefined(
|
||||
this.stateColor || (domain === "light" && this.stateColor !== false)
|
||||
? domain
|
||||
: undefined
|
||||
)}
|
||||
data-state=${stateObj ? computeActiveState(stateObj) : ""}
|
||||
.icon=${this.overrideIcon || (stateObj ? stateIcon(stateObj) : "")}
|
||||
></ha-icon>
|
||||
`;
|
||||
return html`<ha-state-icon
|
||||
style=${styleMap(this._iconStyle)}
|
||||
data-domain=${ifDefined(
|
||||
this.stateColor || (domain === "light" && this.stateColor !== false)
|
||||
? domain
|
||||
: undefined
|
||||
)}
|
||||
data-state=${stateObj ? computeActiveState(stateObj) : ""}
|
||||
.icon=${this.overrideIcon}
|
||||
.state=${stateObj}
|
||||
></ha-state-icon>`;
|
||||
}
|
||||
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
@@ -154,7 +153,7 @@ export class StateBadge extends LitElement {
|
||||
:host([icon]:focus) {
|
||||
background: var(--divider-color);
|
||||
}
|
||||
ha-icon {
|
||||
ha-state-icon {
|
||||
transition: color 0.3s ease-in-out, filter 0.3s ease-in-out;
|
||||
}
|
||||
.missing {
|
||||
|
||||
@@ -53,6 +53,7 @@ class StateInfo extends LitElement {
|
||||
<ha-relative-time
|
||||
.hass=${this.hass}
|
||||
.datetime=${this.stateObj.last_changed}
|
||||
capitalize
|
||||
></ha-relative-time>
|
||||
</div>
|
||||
<div class="row">
|
||||
@@ -64,6 +65,7 @@ class StateInfo extends LitElement {
|
||||
<ha-relative-time
|
||||
.hass=${this.hass}
|
||||
.datetime=${this.stateObj.last_updated}
|
||||
capitalize
|
||||
></ha-relative-time>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -52,7 +52,7 @@ class HaCameraStream extends LitElement {
|
||||
this.stateObj &&
|
||||
(changedProps.get("stateObj") as CameraEntity | undefined)?.entity_id !==
|
||||
this.stateObj.entity_id &&
|
||||
this.stateObj!.attributes.stream_type === STREAM_TYPE_HLS
|
||||
this.stateObj!.attributes.frontend_stream_type === STREAM_TYPE_HLS
|
||||
) {
|
||||
this._forceMJPEG = undefined;
|
||||
this._url = undefined;
|
||||
@@ -84,7 +84,7 @@ class HaCameraStream extends LitElement {
|
||||
.alt=${`Preview of the ${computeStateName(this.stateObj)} camera.`}
|
||||
/>`;
|
||||
}
|
||||
if (this.stateObj.attributes.stream_type === STREAM_TYPE_HLS && true) {
|
||||
if (this.stateObj.attributes.frontend_stream_type === STREAM_TYPE_HLS) {
|
||||
return this._url
|
||||
? html` <ha-hls-player
|
||||
autoplay
|
||||
@@ -97,7 +97,7 @@ class HaCameraStream extends LitElement {
|
||||
></ha-hls-player>`
|
||||
: html``;
|
||||
}
|
||||
if (this.stateObj.attributes.stream_type === STREAM_TYPE_WEB_RTC) {
|
||||
if (this.stateObj.attributes.frontend_stream_type === STREAM_TYPE_WEB_RTC) {
|
||||
return html` <ha-web-rtc-player
|
||||
autoplay
|
||||
playsinline
|
||||
@@ -123,7 +123,7 @@ class HaCameraStream extends LitElement {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
this.stateObj!.attributes.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
|
||||
|
||||
@@ -9,49 +9,47 @@ import {
|
||||
unsafeCSS,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import "./ha-chip";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"chip-clicked": { index: string };
|
||||
}
|
||||
export interface HaChipSetItem {
|
||||
label?: string;
|
||||
leadingIcon?: string;
|
||||
trailingIcon?: string;
|
||||
outlined?: boolean;
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
@customElement("ha-chip-set")
|
||||
export class HaChipSet extends LitElement {
|
||||
@property() public items = [];
|
||||
@property({ attribute: false }) public items?: HaChipSetItem[];
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this.items.length === 0) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<div class="mdc-chip-set">
|
||||
${this.items.map(
|
||||
(item, idx) =>
|
||||
html`
|
||||
<ha-chip .index=${idx} @click=${this._handleClick}>
|
||||
${item}
|
||||
</ha-chip>
|
||||
`
|
||||
)}
|
||||
${this.items
|
||||
? this.items.map(
|
||||
(item, idx) =>
|
||||
html`
|
||||
<ha-chip
|
||||
.index=${idx}
|
||||
.active=${item.active || false}
|
||||
.label=${item.label}
|
||||
.leadingIcon=${item.leadingIcon}
|
||||
.trailingIcon=${item.trailingIcon}
|
||||
?outlined=${item.outlined}
|
||||
>
|
||||
</ha-chip>
|
||||
`
|
||||
)
|
||||
: html`<slot></slot>`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleClick(ev): void {
|
||||
fireEvent(this, "chip-clicked", {
|
||||
index: ev.currentTarget.index,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
${unsafeCSS(chipStyles)}
|
||||
|
||||
ha-chip {
|
||||
.mdc-chip-set > ha-chip, slot::slotted(ha-chip) {
|
||||
margin: 4px;
|
||||
}
|
||||
`;
|
||||
|
||||
+137
-19
@@ -1,4 +1,5 @@
|
||||
// @ts-ignore
|
||||
import "./ha-circular-progress";
|
||||
import "./ha-svg-icon"; // @ts-ignore
|
||||
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
|
||||
import {
|
||||
css,
|
||||
@@ -9,56 +10,173 @@ import {
|
||||
unsafeCSS,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"chip-clicked": { index: string };
|
||||
"chip-clicked": { index: number | undefined };
|
||||
"chip-clicked-trailing": { index: number | undefined };
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ha-chip")
|
||||
export class HaChip extends LitElement {
|
||||
@property() public index = 0;
|
||||
@property({ type: Number }) public index?: number;
|
||||
|
||||
@property({ type: Boolean }) public hasIcon = false;
|
||||
@property({ type: Boolean }) public outlined = false;
|
||||
|
||||
@property({ type: Boolean }) public active = false;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public leadingIcon?: string;
|
||||
|
||||
@property() public trailingIcon?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="mdc-chip" .index=${this.index}>
|
||||
${this.hasIcon
|
||||
? html`<div class="mdc-chip__icon mdc-chip__icon--leading">
|
||||
<slot name="icon"></slot>
|
||||
</div>`
|
||||
: null}
|
||||
<div
|
||||
class="mdc-chip ${this.outlined ? "outlined" : ""}"
|
||||
@click=${this._handleClick}
|
||||
>
|
||||
${this.leadingIcon
|
||||
? html`<span role="gridcell">
|
||||
<span role="button" tabindex="0" class="mdc-chip__primary-action">
|
||||
${this.active
|
||||
? html`<ha-circular-progress alt="active" size="tiny" active>
|
||||
</ha-circular-progress>`
|
||||
: html`<ha-svg-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
.path=${this.leadingIcon}
|
||||
>
|
||||
</ha-svg-icon>`}
|
||||
</span>
|
||||
</span>`
|
||||
: ""}
|
||||
<div class="mdc-chip__ripple"></div>
|
||||
<span role="gridcell">
|
||||
<span role="button" tabindex="0" class="mdc-chip__primary-action">
|
||||
<span class="mdc-chip__text"><slot></slot></span>
|
||||
<span role="row" tabindex="0" class="mdc-chip__primary-action">
|
||||
<span class="mdc-chip__text">
|
||||
${this.label || html`<slot></slot>`}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span role="gridcell">
|
||||
<span role="button" tabindex="-1" class="mdc-chip__primary-action">
|
||||
${this.trailingIcon
|
||||
? html`<ha-svg-icon
|
||||
@click=${this._handleTrailingClick}
|
||||
class="mdc-chip__icon mdc-chip__icon--trailing"
|
||||
.path=${this.trailingIcon}
|
||||
>
|
||||
</ha-svg-icon>`
|
||||
: html`<slot name="trailing-icon"></slot>`}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleClick(): void {
|
||||
fireEvent(this, "chip-clicked", { index: this.index });
|
||||
}
|
||||
|
||||
private _handleTrailingClick(): void {
|
||||
fireEvent(this, "chip-clicked-trailing", {
|
||||
index: this.index,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
${unsafeCSS(chipStyles)}
|
||||
.mdc-chip {
|
||||
background-color: var(
|
||||
--ha-chip-background-color,
|
||||
rgba(var(--rgb-primary-text-color), 0.15)
|
||||
);
|
||||
color: var(--ha-chip-text-color, var(--primary-text-color));
|
||||
background-color: var(--ha-chip-background-color, var(--primary-color));
|
||||
color: var(--ha-chip-text-color, var(--text-primary-color));
|
||||
}
|
||||
|
||||
.mdc-chip:hover {
|
||||
color: var(--ha-chip-text-color, var(--primary-text-color));
|
||||
color: var(--ha-chip-text-color, var(--text-primary-color));
|
||||
}
|
||||
|
||||
.mdc-chip.outlined {
|
||||
margin: 0 -1px !important;
|
||||
border: 1px solid var(--ha-chip-background-color, var(--primary-color));
|
||||
background-color: var(
|
||||
--ha-chip-outlined-background-color,
|
||||
var(--card-background-color)
|
||||
);
|
||||
color: var(--ha-chip-outlined-text-color, var(--primary-text-color));
|
||||
}
|
||||
|
||||
.mdc-chip:not(.outlined) .mdc-chip__icon.mdc-chip__icon--leading {
|
||||
margin-left: -12px !important;
|
||||
margin-right: -2px;
|
||||
color: var(--ha-chip-icon-color, var(--text-primary-color));
|
||||
}
|
||||
|
||||
ha-circular-progress {
|
||||
--mdc-theme-primary: var(
|
||||
--ha-chip-icon-color,
|
||||
var(--text-primary-color)
|
||||
);
|
||||
padding: 8px;
|
||||
margin-left: -12px !important;
|
||||
margin-right: -2px;
|
||||
}
|
||||
|
||||
.mdc-chip.outlined ha-circular-progress {
|
||||
border-radius: 50%;
|
||||
margin-right: 4px;
|
||||
background-color: var(--ha-chip-background-color, var(--primary-color));
|
||||
--mdc-theme-primary: var(
|
||||
--ha-chip-icon-color,
|
||||
var(--text-primary-color)
|
||||
);
|
||||
}
|
||||
|
||||
.mdc-chip.outlined ha-svg-icon,
|
||||
slot[name="trailing-icon"]::slotted(ha-svg-icon) {
|
||||
border-radius: 50%;
|
||||
background-color: var(--ha-chip-background-color, var(--primary-color));
|
||||
color: var(--ha-chip-icon-color, var(--text-primary-color));
|
||||
}
|
||||
|
||||
.mdc-chip.outlined .mdc-chip__icon.mdc-chip__icon--leading {
|
||||
margin-left: -13px !important;
|
||||
color: var(--ha-chip-icon-color, var(--text-primary-color));
|
||||
}
|
||||
|
||||
.mdc-chip__icon.mdc-chip__icon--trailing,
|
||||
slot[name="trailing-icon"]::slotted(ha-svg-icon) {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
font-size: 18px;
|
||||
padding: 2px;
|
||||
color: var(--ha-chip-icon-color, var(--text-primary-color));
|
||||
margin-right: -8px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
--mdc-icon-size: 12px;
|
||||
}
|
||||
|
||||
slot[name="trailing-icon"]::slotted(ha-svg-icon) {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.mdc-chip__icon--leading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: inherit;
|
||||
border-radius: 50%;
|
||||
padding: 6px;
|
||||
--mdc-icon-size: 20px;
|
||||
color: var(--ha-chip-icon-color, var(--ha-chip-text-color));
|
||||
}
|
||||
:host([disabled]) .mdc-chip {
|
||||
opacity: var(--light-disabled-opacity);
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import { computeCloseIcon, computeOpenIcon } from "../common/entity/cover_icon";
|
||||
import { UNAVAILABLE } from "../data/entity";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import CoverEntity from "../util/cover-model";
|
||||
import "./ha-icon";
|
||||
import "./ha-icon-button";
|
||||
|
||||
@customElement("ha-cover-controls")
|
||||
@@ -49,8 +48,8 @@ class HaCoverControls extends LitElement {
|
||||
)}
|
||||
@click=${this._onOpenTap}
|
||||
.disabled=${this._computeOpenDisabled()}
|
||||
.path=${computeOpenIcon(this.stateObj)}
|
||||
>
|
||||
<ha-icon .icon=${computeOpenIcon(this.stateObj)}></ha-icon>
|
||||
</ha-icon-button>
|
||||
<ha-icon-button
|
||||
class=${classMap({
|
||||
@@ -72,8 +71,8 @@ class HaCoverControls extends LitElement {
|
||||
)}
|
||||
@click=${this._onCloseTap}
|
||||
.disabled=${this._computeClosedDisabled()}
|
||||
.path=${computeCloseIcon(this.stateObj)}
|
||||
>
|
||||
<ha-icon .icon=${computeCloseIcon(this.stateObj)}></ha-icon>
|
||||
</ha-icon-button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -24,7 +24,7 @@ export const createCloseHeading = (
|
||||
// @ts-expect-error
|
||||
export class HaDialog extends Dialog {
|
||||
public scrollToPos(x: number, y: number) {
|
||||
this.contentElement.scrollTo(x, y);
|
||||
this.contentElement?.scrollTo(x, y);
|
||||
}
|
||||
|
||||
protected renderHeading() {
|
||||
@@ -44,6 +44,12 @@ export class HaDialog extends Dialog {
|
||||
justify-content: var(--justify-action-buttons, flex-end);
|
||||
padding-bottom: max(env(safe-area-inset-bottom), 8px);
|
||||
}
|
||||
.mdc-dialog__actions span:nth-child(1) {
|
||||
flex: var(--secondary-action-button-flex, unset);
|
||||
}
|
||||
.mdc-dialog__actions span:nth-child(2) {
|
||||
flex: var(--primary-action-button-flex, unset);
|
||||
}
|
||||
.mdc-dialog__container {
|
||||
align-items: var(--vertial-align-dialog, center);
|
||||
}
|
||||
|
||||
@@ -84,15 +84,20 @@ export class HaFileUpload extends LitElement {
|
||||
${this.value}
|
||||
</iron-input>
|
||||
${this.value
|
||||
? html`<ha-icon-button
|
||||
slot="suffix"
|
||||
@click=${this._clearValue}
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>`
|
||||
: html`<ha-icon-button slot="suffix">
|
||||
.path=${this.icon} ></ha-icon-button
|
||||
>`}
|
||||
? html`
|
||||
<ha-icon-button
|
||||
slot="suffix"
|
||||
@click=${this._clearValue}
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: html`
|
||||
<ha-icon-button
|
||||
slot="suffix"
|
||||
.path=${this.icon}
|
||||
></ha-icon-button>
|
||||
`}
|
||||
</paper-input-container>
|
||||
</label>
|
||||
`}
|
||||
|
||||
@@ -20,7 +20,7 @@ export class HaFormBoolean extends LitElement implements HaFormElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@query("paper-checkbox", true) private _input?: HTMLElement;
|
||||
@query("ha-checkbox", true) private _input?: HTMLElement;
|
||||
|
||||
public focus() {
|
||||
if (this._input) {
|
||||
|
||||
@@ -43,7 +43,7 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
@query("paper-menu-button", true) private _input?: HTMLElement;
|
||||
@query("ha-button-menu") private _input?: HTMLElement;
|
||||
|
||||
public focus(): void {
|
||||
if (this._input) {
|
||||
|
||||
@@ -52,6 +52,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
return html`
|
||||
<mwc-select
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
.label=${this.label}
|
||||
.value=${this.data}
|
||||
.disabled=${this.disabled}
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { nextRender } from "../common/util/render-status";
|
||||
import { getExternalConfig } from "../external_app/external_config";
|
||||
import type { HomeAssistant } from "../types";
|
||||
@@ -65,7 +64,6 @@ class HaHLSPlayer extends LitElement {
|
||||
.muted=${this.muted}
|
||||
?playsinline=${this.playsInline}
|
||||
?controls=${this.controls}
|
||||
@loadeddata=${this._elementResized}
|
||||
></video>
|
||||
`;
|
||||
}
|
||||
@@ -191,6 +189,7 @@ class HaHLSPlayer extends LitElement {
|
||||
fragLoadingTimeOut: 30000,
|
||||
manifestLoadingTimeOut: 30000,
|
||||
levelLoadingTimeOut: 30000,
|
||||
maxLiveSyncPlaybackRate: 2,
|
||||
});
|
||||
this._hlsPolyfillInstance = hls;
|
||||
hls.attachMedia(videoEl);
|
||||
@@ -206,10 +205,6 @@ class HaHLSPlayer extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _elementResized() {
|
||||
fireEvent(this, "iron-resize");
|
||||
}
|
||||
|
||||
private _cleanUp() {
|
||||
if (this._hlsPolyfillInstance) {
|
||||
this._hlsPolyfillInstance.destroy();
|
||||
|
||||
@@ -28,8 +28,9 @@ export class HaIconButton extends LitElement {
|
||||
.title=${this.hideTitle ? "" : this.label}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.path ? html`<ha-svg-icon .path=${this.path}></ha-svg-icon>` : ""}
|
||||
<slot></slot>
|
||||
${this.path
|
||||
? html`<ha-svg-icon .path=${this.path}></ha-svg-icon>`
|
||||
: html`<slot></slot>`}
|
||||
</mwc-icon-button>
|
||||
`;
|
||||
}
|
||||
@@ -47,9 +48,6 @@ export class HaIconButton extends LitElement {
|
||||
--mdc-theme-on-primary: currentColor;
|
||||
--mdc-theme-text-disabled-on-light: var(--disabled-text-color);
|
||||
}
|
||||
ha-icon {
|
||||
--ha-icon-display: inline;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import "./ha-icon";
|
||||
|
||||
@customElement("ha-icon-input")
|
||||
export class HaIconInput extends LitElement {
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property({ attribute: "error-message" }) public errorMessage?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<paper-input
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.placeholder=${this.placeholder}
|
||||
@value-changed=${this._valueChanged}
|
||||
.disabled=${this.disabled}
|
||||
auto-validate
|
||||
.errorMessage=${this.errorMessage}
|
||||
pattern="^\\S+:\\S+$"
|
||||
>
|
||||
${this.value || this.placeholder
|
||||
? html`
|
||||
<ha-icon .icon=${this.value || this.placeholder} slot="suffix">
|
||||
</ha-icon>
|
||||
`
|
||||
: ""}
|
||||
</paper-input>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
this.value = ev.detail.value;
|
||||
fireEvent(
|
||||
this,
|
||||
"value-changed",
|
||||
{ value: ev.detail.value },
|
||||
{
|
||||
bubbles: false,
|
||||
composed: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-icon {
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
right: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
import { mdiCheck, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { PolymerChangedEvent } from "../polymer-types";
|
||||
import "./ha-icon";
|
||||
import "./ha-icon-button";
|
||||
|
||||
let mdiIconList: string[] = [];
|
||||
|
||||
// eslint-disable-next-line lit/prefer-static-styles
|
||||
const rowRenderer: ComboBoxLitRenderer<string> = (item) => html`<style>
|
||||
paper-icon-item {
|
||||
padding: 0;
|
||||
margin: -8px;
|
||||
}
|
||||
#content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
ha-svg-icon {
|
||||
padding-left: 2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
:host(:not([selected])) ha-svg-icon {
|
||||
display: none;
|
||||
}
|
||||
:host([selected]) paper-icon-item {
|
||||
margin-left: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
||||
<paper-icon-item>
|
||||
<ha-icon .icon=${item} slot="item-icon"></ha-icon>
|
||||
<paper-item-body>${item}</paper-item-body>
|
||||
</paper-icon-item>`;
|
||||
|
||||
@customElement("ha-icon-picker")
|
||||
export class HaIconPicker extends LitElement {
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property() public fallbackPath?: string;
|
||||
|
||||
@property({ attribute: "error-message" }) public errorMessage?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
@query("vaadin-combo-box-light", true) private comboBox!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<vaadin-combo-box-light
|
||||
item-value-path="icon"
|
||||
item-label-path="icon"
|
||||
.value=${this._value}
|
||||
allow-custom-value
|
||||
.filteredItems=${mdiIconList}
|
||||
${comboBoxRenderer(rowRenderer)}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
@filter-changed=${this._filterChanged}
|
||||
>
|
||||
<paper-input
|
||||
.label=${this.label}
|
||||
.placeholder=${this.placeholder}
|
||||
.disabled=${this.disabled}
|
||||
class="input"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
>
|
||||
${this._value || this.placeholder
|
||||
? html`
|
||||
<ha-icon .icon=${this._value || this.placeholder} slot="prefix">
|
||||
</ha-icon>
|
||||
`
|
||||
: this.fallbackPath
|
||||
? html`<ha-svg-icon
|
||||
.path=${this.fallbackPath}
|
||||
slot="prefix"
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
<ha-icon-button
|
||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
||||
slot="suffix"
|
||||
class="toggle-button"
|
||||
></ha-icon-button>
|
||||
</paper-input>
|
||||
</vaadin-combo-box-light>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
||||
this._opened = ev.detail.value;
|
||||
if (this._opened && !mdiIconList.length) {
|
||||
const iconList = await import("../../build/mdi/iconList.json");
|
||||
mdiIconList = iconList.default.map((icon) => `mdi:${icon}`);
|
||||
(this.comboBox as any).filteredItems = mdiIconList;
|
||||
}
|
||||
}
|
||||
|
||||
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
||||
this._setValue(ev.detail.value);
|
||||
}
|
||||
|
||||
private _setValue(value: string) {
|
||||
this.value = value;
|
||||
fireEvent(
|
||||
this,
|
||||
"value-changed",
|
||||
{ value },
|
||||
{
|
||||
bubbles: false,
|
||||
composed: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _filterChanged(ev: CustomEvent): void {
|
||||
const filterString = ev.detail.value.toLowerCase();
|
||||
const characterCount = filterString.length;
|
||||
if (characterCount >= 2) {
|
||||
const filteredItems = mdiIconList.filter((icon) =>
|
||||
icon.includes(filterString)
|
||||
);
|
||||
if (filteredItems.length > 0) {
|
||||
(this.comboBox as any).filteredItems = filteredItems;
|
||||
} else {
|
||||
(this.comboBox as any).filteredItems = [filterString];
|
||||
}
|
||||
} else {
|
||||
(this.comboBox as any).filteredItems = mdiIconList;
|
||||
}
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
return this.value || "";
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-icon,
|
||||
ha-svg-icon {
|
||||
position: relative;
|
||||
bottom: 2px;
|
||||
}
|
||||
*[slot="prefix"] {
|
||||
margin-right: 8px;
|
||||
}
|
||||
paper-input > ha-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
padding: 2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-icon-picker": HaIconPicker;
|
||||
}
|
||||
}
|
||||
@@ -52,18 +52,6 @@ const mdiDeprecatedIcons: DeprecatedIcon = {
|
||||
newName: "cast-variant",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
application: {
|
||||
newName: "application-outline",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"application-cog": {
|
||||
newName: "application-cog-outline",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"application-settings": {
|
||||
newName: "application-settings-outline",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
bandcamp: {
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
@@ -77,14 +65,6 @@ const mdiDeprecatedIcons: DeprecatedIcon = {
|
||||
newName: "cross-bolnisi",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"boom-gate-up": {
|
||||
newName: "boom-gate-arrow-up",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"boom-gate-up-outline": {
|
||||
newName: "boom-gate-arrow-up-outline",
|
||||
removeIn: "2021.12",
|
||||
},
|
||||
"boom-gate-down": {
|
||||
newName: "boom-gate-arrow-down",
|
||||
removeIn: "2021.12",
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
import { mdiStar } from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
nothing,
|
||||
LitElement,
|
||||
nothing,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, state, property } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import {
|
||||
Adapter,
|
||||
NetworkConfig,
|
||||
IPv6ConfiguredAddress,
|
||||
IPv4ConfiguredAddress,
|
||||
IPv6ConfiguredAddress,
|
||||
NetworkConfig,
|
||||
} from "../data/network";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-checkbox";
|
||||
import type { HaCheckbox } from "./ha-checkbox";
|
||||
import "./ha-settings-row";
|
||||
import "./ha-icon";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
const format_addresses = (
|
||||
addresses: IPv6ConfiguredAddress[] | IPv4ConfiguredAddress[]
|
||||
@@ -92,7 +93,8 @@ export class HaNetwork extends LitElement {
|
||||
<span slot="heading">
|
||||
Adapter: ${adapter.name}
|
||||
${adapter.default
|
||||
? html`<ha-icon .icon="hass:star"></ha-icon> (Default)`
|
||||
? html`<ha-svg-icon .path=${mdiStar}></ha-svg-icon>
|
||||
(Default)`
|
||||
: ""}
|
||||
</span>
|
||||
<span slot="description">
|
||||
|
||||
@@ -16,7 +16,7 @@ export class HaBooleanSelector extends LitElement {
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected render() {
|
||||
return html` <ha-formfield alignEnd spaceBetween .label=${this.label}>
|
||||
return html`<ha-formfield alignEnd spaceBetween .label=${this.label}>
|
||||
<ha-switch
|
||||
.checked=${this.value}
|
||||
@change=${this._handleChange}
|
||||
|
||||
@@ -25,6 +25,11 @@ import "./ha-settings-row";
|
||||
import "./ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "./ha-yaml-editor";
|
||||
|
||||
const showOptionalToggle = (field) =>
|
||||
field.selector &&
|
||||
!field.required &&
|
||||
!("boolean" in field.selector && field.default);
|
||||
|
||||
interface ExtHassService extends Omit<HassService, "fields"> {
|
||||
fields: {
|
||||
key: string;
|
||||
@@ -186,7 +191,7 @@ export class HaServiceControl extends LitElement {
|
||||
|
||||
const hasOptional = Boolean(
|
||||
!shouldRenderServiceDataYaml &&
|
||||
serviceData?.fields.some((field) => field.selector && !field.required)
|
||||
serviceData?.fields.some((field) => showOptionalToggle(field))
|
||||
);
|
||||
|
||||
return html`<ha-service-picker
|
||||
@@ -253,14 +258,15 @@ export class HaServiceControl extends LitElement {
|
||||
.defaultValue=${this._value?.data}
|
||||
@value-changed=${this._dataChanged}
|
||||
></ha-yaml-editor>`
|
||||
: serviceData?.fields.map((dataField) =>
|
||||
dataField.selector &&
|
||||
(!dataField.advanced ||
|
||||
this.showAdvanced ||
|
||||
(this._value?.data &&
|
||||
this._value.data[dataField.key] !== undefined))
|
||||
: serviceData?.fields.map((dataField) => {
|
||||
const showOptional = showOptionalToggle(dataField);
|
||||
return dataField.selector &&
|
||||
(!dataField.advanced ||
|
||||
this.showAdvanced ||
|
||||
(this._value?.data &&
|
||||
this._value.data[dataField.key] !== undefined))
|
||||
? html`<ha-settings-row .narrow=${this.narrow}>
|
||||
${dataField.required
|
||||
${!showOptional
|
||||
? hasOptional
|
||||
? html`<div slot="prefix" class="checkbox-spacer"></div>`
|
||||
: ""
|
||||
@@ -273,9 +279,9 @@ export class HaServiceControl extends LitElement {
|
||||
slot="prefix"
|
||||
></ha-checkbox>`}
|
||||
<span slot="heading">${dataField.name || dataField.key}</span>
|
||||
<span slot="description">${dataField?.description}</span
|
||||
><ha-selector
|
||||
.disabled=${!dataField.required &&
|
||||
<span slot="description">${dataField?.description}</span>
|
||||
<ha-selector
|
||||
.disabled=${showOptional &&
|
||||
!this._checkedKeys.has(dataField.key) &&
|
||||
(!this._value?.data ||
|
||||
this._value.data[dataField.key] === undefined)}
|
||||
@@ -287,23 +293,35 @@ export class HaServiceControl extends LitElement {
|
||||
this._value.data[dataField.key] !== undefined
|
||||
? this._value.data[dataField.key]
|
||||
: dataField.default}
|
||||
></ha-selector
|
||||
></ha-settings-row>`
|
||||
: ""
|
||||
)} `;
|
||||
></ha-selector>
|
||||
</ha-settings-row>`
|
||||
: "";
|
||||
})}`;
|
||||
}
|
||||
|
||||
private _checkboxChanged(ev) {
|
||||
const checked = ev.currentTarget.checked;
|
||||
const key = ev.currentTarget.key;
|
||||
let data;
|
||||
|
||||
if (checked) {
|
||||
this._checkedKeys.add(key);
|
||||
const defaultValue = this._getServiceInfo(
|
||||
this._value?.service,
|
||||
this.hass.services
|
||||
)?.fields.find((field) => field.key === key)?.default;
|
||||
if (defaultValue) {
|
||||
data = {
|
||||
...this._value?.data,
|
||||
[key]: defaultValue,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
this._checkedKeys.delete(key);
|
||||
const data = { ...this._value?.data };
|
||||
|
||||
data = { ...this._value?.data };
|
||||
delete data[key];
|
||||
|
||||
}
|
||||
if (data) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this._value,
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import {
|
||||
mdiBell,
|
||||
mdiCalendar,
|
||||
mdiCart,
|
||||
mdiCellphoneCog,
|
||||
mdiChartBox,
|
||||
mdiClose,
|
||||
mdiCog,
|
||||
mdiFormatListBulletedType,
|
||||
mdiHammer,
|
||||
mdiHomeAssistant,
|
||||
mdiLightningBolt,
|
||||
mdiMenu,
|
||||
mdiMenuOpen,
|
||||
mdiPlayBoxMultiple,
|
||||
mdiPlus,
|
||||
mdiTooltipAccount,
|
||||
mdiViewDashboard,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
@@ -62,6 +72,20 @@ const SORT_VALUE_URL_PATHS = {
|
||||
config: 11,
|
||||
};
|
||||
|
||||
const PANEL_ICONS = {
|
||||
calendar: mdiCalendar,
|
||||
config: mdiCog,
|
||||
"developer-tools": mdiHammer,
|
||||
energy: mdiLightningBolt,
|
||||
hassio: mdiHomeAssistant,
|
||||
history: mdiChartBox,
|
||||
logbook: mdiFormatListBulletedType,
|
||||
lovelace: mdiViewDashboard,
|
||||
map: mdiTooltipAccount,
|
||||
"media-browser": mdiPlayBoxMultiple,
|
||||
"shopping-list": mdiCart,
|
||||
};
|
||||
|
||||
const panelSorter = (
|
||||
reverseSort: string[],
|
||||
defaultPanel: string,
|
||||
@@ -348,6 +372,60 @@ class HaSidebar extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderPanels(panels: PanelInfo[]) {
|
||||
return panels.map((panel) =>
|
||||
this._renderPanel(
|
||||
panel.url_path,
|
||||
panel.url_path === this.hass.defaultPanel
|
||||
? panel.title || this.hass.localize("panel.states")
|
||||
: this.hass.localize(`panel.${panel.title}`) || panel.title,
|
||||
panel.icon,
|
||||
panel.url_path === this.hass.defaultPanel && !panel.icon
|
||||
? PANEL_ICONS.lovelace
|
||||
: panel.url_path in PANEL_ICONS
|
||||
? PANEL_ICONS[panel.url_path]
|
||||
: undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _renderPanel(
|
||||
urlPath: string,
|
||||
title: string | null,
|
||||
icon?: string | null,
|
||||
iconPath?: string | null
|
||||
) {
|
||||
return html`
|
||||
<a
|
||||
aria-role="option"
|
||||
href=${`/${urlPath}`}
|
||||
data-panel=${urlPath}
|
||||
tabindex="-1"
|
||||
@mouseenter=${this._itemMouseEnter}
|
||||
@mouseleave=${this._itemMouseLeave}
|
||||
>
|
||||
<paper-icon-item>
|
||||
${iconPath
|
||||
? html`<ha-svg-icon
|
||||
slot="item-icon"
|
||||
.path=${iconPath}
|
||||
></ha-svg-icon>`
|
||||
: html`<ha-icon slot="item-icon" .icon=${icon}></ha-icon>`}
|
||||
<span class="item-text">${title}</span>
|
||||
</paper-icon-item>
|
||||
${this.editMode
|
||||
? html`<ha-icon-button
|
||||
.label=${this.hass.localize("ui.sidebar.hide_panel")}
|
||||
.path=${mdiClose}
|
||||
class="hide-panel"
|
||||
.panel=${urlPath}
|
||||
@click=${this._hidePanel}
|
||||
></ha-icon-button>`
|
||||
: ""}
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderPanelsEdit(beforeSpacer: PanelInfo[]) {
|
||||
// prettier-ignore
|
||||
return html`<div id="sortable">
|
||||
@@ -360,7 +438,7 @@ class HaSidebar extends LitElement {
|
||||
}
|
||||
|
||||
private _renderHiddenPanels() {
|
||||
return html` ${this._hiddenPanels.length
|
||||
return html`${this._hiddenPanels.length
|
||||
? html`${this._hiddenPanels.map((url) => {
|
||||
const panel = this.hass.panels[url];
|
||||
if (!panel) {
|
||||
@@ -371,12 +449,17 @@ class HaSidebar extends LitElement {
|
||||
class="hidden-panel"
|
||||
.panel=${url}
|
||||
>
|
||||
<ha-icon
|
||||
slot="item-icon"
|
||||
.icon=${panel.url_path === this.hass.defaultPanel
|
||||
? "mdi:view-dashboard"
|
||||
: panel.icon}
|
||||
></ha-icon>
|
||||
${panel.url_path === this.hass.defaultPanel && !panel.icon
|
||||
? html`<ha-svg-icon
|
||||
slot="item-icon"
|
||||
.path=${PANEL_ICONS.lovelace}
|
||||
></ha-svg-icon>`
|
||||
: panel.url_path in PANEL_ICONS
|
||||
? html`<ha-svg-icon
|
||||
slot="item-icon"
|
||||
.path=${PANEL_ICONS[panel.url_path]}
|
||||
></ha-svg-icon>`
|
||||
: html`<ha-icon slot="item-icon" .icon=${panel.icon}></ha-icon>`}
|
||||
<span class="item-text"
|
||||
>${panel.url_path === this.hass.defaultPanel
|
||||
? this.hass.localize("panel.states")
|
||||
@@ -412,7 +495,7 @@ class HaSidebar extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
return html` <div
|
||||
return html`<div
|
||||
class="notifications-container"
|
||||
@mouseenter=${this._itemMouseEnter}
|
||||
@mouseleave=${this._itemMouseLeave}
|
||||
@@ -682,58 +765,6 @@ class HaSidebar extends LitElement {
|
||||
fireEvent(this, "hass-toggle-menu");
|
||||
}
|
||||
|
||||
private _renderPanels(panels: PanelInfo[]) {
|
||||
return panels.map((panel) =>
|
||||
this._renderPanel(
|
||||
panel.url_path,
|
||||
panel.url_path === this.hass.defaultPanel
|
||||
? panel.title || this.hass.localize("panel.states")
|
||||
: this.hass.localize(`panel.${panel.title}`) || panel.title,
|
||||
panel.icon,
|
||||
panel.url_path === this.hass.defaultPanel && !panel.icon
|
||||
? mdiViewDashboard
|
||||
: undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _renderPanel(
|
||||
urlPath: string,
|
||||
title: string | null,
|
||||
icon?: string | null,
|
||||
iconPath?: string | null
|
||||
) {
|
||||
return html`
|
||||
<a
|
||||
aria-role="option"
|
||||
href=${`/${urlPath}`}
|
||||
data-panel=${urlPath}
|
||||
tabindex="-1"
|
||||
@mouseenter=${this._itemMouseEnter}
|
||||
@mouseleave=${this._itemMouseLeave}
|
||||
>
|
||||
<paper-icon-item>
|
||||
${iconPath
|
||||
? html`<ha-svg-icon
|
||||
slot="item-icon"
|
||||
.path=${iconPath}
|
||||
></ha-svg-icon>`
|
||||
: html`<ha-icon slot="item-icon" .icon=${icon}></ha-icon>`}
|
||||
<span class="item-text">${title}</span>
|
||||
</paper-icon-item>
|
||||
${this.editMode
|
||||
? html`<ha-icon-button
|
||||
.label=${this.hass.localize("ui.sidebar.hide_panel")}
|
||||
.path=${mdiClose}
|
||||
class="hide-panel"
|
||||
.panel=${urlPath}
|
||||
@click=${this._hidePanel}
|
||||
></ha-icon-button>`
|
||||
: ""}
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleScrollbar,
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { stateIconPath } from "../common/entity/state_icon_path";
|
||||
import "./ha-icon";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-state-icon")
|
||||
export class HaStateIcon extends LitElement {
|
||||
@property({ attribute: false }) public state?: HassEntity;
|
||||
|
||||
@property() public icon?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this.icon || this.state?.attributes.icon) {
|
||||
return html`<ha-icon
|
||||
.icon=${this.icon || this.state?.attributes.icon}
|
||||
></ha-icon>`;
|
||||
}
|
||||
return html`<ha-svg-icon .path=${stateIconPath(this.state)}></ha-svg-icon>`;
|
||||
}
|
||||
}
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-state-icon": HaStateIcon;
|
||||
}
|
||||
}
|
||||
+108
-196
@@ -1,5 +1,3 @@
|
||||
// @ts-ignore
|
||||
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import {
|
||||
mdiClose,
|
||||
@@ -10,17 +8,17 @@ import {
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
HassEntity,
|
||||
HassServiceTarget,
|
||||
UnsubscribeFunc,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, unsafeCSS } from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { ensureArray } from "../common/ensure-array";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { stateIcon } from "../common/entity/state_icon";
|
||||
import { stateIconPath } from "../common/entity/state_icon_path";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
@@ -41,15 +39,15 @@ import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
import "./entity/ha-entity-picker";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker";
|
||||
import "./ha-area-picker";
|
||||
import "./ha-icon";
|
||||
import "./ha-chip-set";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-target-picker")
|
||||
export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public value?: HassServiceTarget;
|
||||
@property({ attribute: false }) public value?: HassServiceTarget;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@@ -115,7 +113,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
if (!this._areas || !this._devices || !this._entities) {
|
||||
return html``;
|
||||
}
|
||||
return html`<div class="mdc-chip-set items">
|
||||
return html`<ha-chip-set>
|
||||
${this.value?.area_id
|
||||
? ensureArray(this.value.area_id).map((area_id) => {
|
||||
const area = this._areas![area_id];
|
||||
@@ -147,74 +145,43 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
"entity_id",
|
||||
entity_id,
|
||||
entity ? computeStateName(entity) : entity_id,
|
||||
entity ? stateIcon(entity) : undefined
|
||||
entity
|
||||
);
|
||||
})
|
||||
: ""}
|
||||
</div>
|
||||
</ha-chip-set>
|
||||
${this._renderPicker()}
|
||||
<div class="mdc-chip-set">
|
||||
<div
|
||||
class="mdc-chip area_id add"
|
||||
<ha-chip-set>
|
||||
<ha-chip
|
||||
class="area_id add"
|
||||
.type=${"area_id"}
|
||||
@click=${this._showPicker}
|
||||
.leadingIcon=${mdiPlus}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.target-picker.add_area_id"
|
||||
)}
|
||||
>
|
||||
<div class="mdc-chip__ripple"></div>
|
||||
<ha-svg-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
.path=${mdiPlus}
|
||||
></ha-svg-icon>
|
||||
<span role="gridcell">
|
||||
<span role="button" tabindex="0" class="mdc-chip__primary-action">
|
||||
<span class="mdc-chip__text"
|
||||
>${this.hass.localize(
|
||||
"ui.components.target-picker.add_area_id"
|
||||
)}</span
|
||||
>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mdc-chip device_id add"
|
||||
</ha-chip>
|
||||
<ha-chip
|
||||
class="device_id add"
|
||||
.type=${"device_id"}
|
||||
@click=${this._showPicker}
|
||||
.leadingIcon=${mdiPlus}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.target-picker.add_device_id"
|
||||
)}
|
||||
>
|
||||
<div class="mdc-chip__ripple"></div>
|
||||
<ha-svg-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
.path=${mdiPlus}
|
||||
></ha-svg-icon>
|
||||
<span role="gridcell">
|
||||
<span role="button" tabindex="0" class="mdc-chip__primary-action">
|
||||
<span class="mdc-chip__text"
|
||||
>${this.hass.localize(
|
||||
"ui.components.target-picker.add_device_id"
|
||||
)}</span
|
||||
>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mdc-chip entity_id add"
|
||||
</ha-chip>
|
||||
<ha-chip
|
||||
class="entity_id add"
|
||||
.type=${"entity_id"}
|
||||
@click=${this._showPicker}
|
||||
>
|
||||
<div class="mdc-chip__ripple"></div>
|
||||
<ha-svg-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
.path=${mdiPlus}
|
||||
></ha-svg-icon>
|
||||
<span role="gridcell">
|
||||
<span role="button" tabindex="0" class="mdc-chip__primary-action">
|
||||
<span class="mdc-chip__text"
|
||||
>${this.hass.localize(
|
||||
"ui.components.target-picker.add_entity_id"
|
||||
)}</span
|
||||
>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>`;
|
||||
.leadingIcon=${mdiPlus}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.target-picker.add_entity_id"
|
||||
)}
|
||||
></ha-chip>
|
||||
</ha-chip-set>`;
|
||||
}
|
||||
|
||||
private async _showPicker(ev) {
|
||||
@@ -230,73 +197,63 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
type: string,
|
||||
id: string,
|
||||
name: string,
|
||||
icon?: string,
|
||||
entityState?: HassEntity,
|
||||
iconPath?: string
|
||||
) {
|
||||
return html`
|
||||
<div
|
||||
class="mdc-chip ${classMap({
|
||||
[type]: true,
|
||||
})}"
|
||||
<ha-chip
|
||||
class=${type}
|
||||
.id=${id}
|
||||
.leadingIcon=${iconPath ||
|
||||
entityState?.attributes.icon ||
|
||||
stateIconPath(entityState)}
|
||||
.label=${name}
|
||||
outlined
|
||||
>
|
||||
${iconPath
|
||||
? html`<ha-svg-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
.path=${iconPath}
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
${icon
|
||||
? html`<ha-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
.icon=${icon}
|
||||
></ha-icon>`
|
||||
: ""}
|
||||
<span role="gridcell">
|
||||
<span role="button" tabindex="0" class="mdc-chip__primary-action">
|
||||
<span class="mdc-chip__text">${name}</span>
|
||||
<div slot="trailing-icon">
|
||||
${type === "entity_id"
|
||||
? ""
|
||||
: html`
|
||||
<span>
|
||||
<ha-icon-button
|
||||
class="expand-btn"
|
||||
tabindex="-1"
|
||||
role="button"
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.target-picker.expand"
|
||||
)}
|
||||
.path=${mdiUnfoldMoreVertical}
|
||||
hideTitle
|
||||
.id=${id}
|
||||
.type=${type}
|
||||
@click=${this._handleExpand}
|
||||
></ha-icon-button>
|
||||
<paper-tooltip class="expand" animation-delay="0"
|
||||
>${this.hass.localize(
|
||||
`ui.components.target-picker.expand_${type}`
|
||||
)}</paper-tooltip
|
||||
>
|
||||
</span>
|
||||
`}
|
||||
<span>
|
||||
<ha-icon-button
|
||||
tabindex="-1"
|
||||
role="button"
|
||||
.label=${this.hass.localize("ui.components.target-picker.expand")}
|
||||
.path=${mdiClose}
|
||||
hideTitle
|
||||
.id=${id}
|
||||
.type=${type}
|
||||
@click=${this._handleRemove}
|
||||
></ha-icon-button>
|
||||
<paper-tooltip animation-delay="0"
|
||||
>${this.hass.localize(
|
||||
`ui.components.target-picker.remove_${type}`
|
||||
)}</paper-tooltip
|
||||
>
|
||||
</span>
|
||||
</span>
|
||||
${type === "entity_id"
|
||||
? ""
|
||||
: html` <span role="gridcell">
|
||||
<ha-icon-button
|
||||
class="expand-btn mdc-chip__icon mdc-chip__icon--trailing"
|
||||
tabindex="-1"
|
||||
role="button"
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.target-picker.expand"
|
||||
)}
|
||||
.path=${mdiUnfoldMoreVertical}
|
||||
hideTooltip
|
||||
.id=${id}
|
||||
.type=${type}
|
||||
@click=${this._handleExpand}
|
||||
></ha-icon-button>
|
||||
<paper-tooltip class="expand" animation-delay="0"
|
||||
>${this.hass.localize(
|
||||
`ui.components.target-picker.expand_${type}`
|
||||
)}</paper-tooltip
|
||||
>
|
||||
</span>`}
|
||||
<span role="gridcell">
|
||||
<ha-icon-button
|
||||
class="mdc-chip__icon mdc-chip__icon--trailing"
|
||||
tabindex="-1"
|
||||
role="button"
|
||||
.label=${this.hass.localize("ui.components.target-picker.expand")}
|
||||
.path=${mdiClose}
|
||||
hideTooltip
|
||||
.id=${id}
|
||||
.type=${type}
|
||||
@click=${this._handleRemove}
|
||||
></ha-icon-button>
|
||||
<paper-tooltip animation-delay="0"
|
||||
>${this.hass.localize(
|
||||
`ui.components.target-picker.remove_${type}`
|
||||
)}</paper-tooltip
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</ha-chip>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -529,84 +486,39 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
${unsafeCSS(chipStyles)}
|
||||
.mdc-chip {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.items {
|
||||
z-index: 2;
|
||||
}
|
||||
.mdc-chip-set {
|
||||
padding: 4px 0;
|
||||
}
|
||||
.mdc-chip.add {
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
.mdc-chip:not(.add) {
|
||||
cursor: default;
|
||||
}
|
||||
.mdc-chip ha-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
outline: none;
|
||||
}
|
||||
.mdc-chip ha-icon-button ha-svg-icon {
|
||||
border-radius: 50%;
|
||||
background: var(--secondary-text-color);
|
||||
}
|
||||
.mdc-chip__icon.mdc-chip__icon--trailing {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
--mdc-icon-size: 14px;
|
||||
color: var(--card-background-color);
|
||||
}
|
||||
.mdc-chip__icon--leading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
--mdc-icon-size: 20px;
|
||||
border-radius: 50%;
|
||||
padding: 6px;
|
||||
margin-left: -14px !important;
|
||||
}
|
||||
.expand-btn {
|
||||
margin-right: 0;
|
||||
}
|
||||
.mdc-chip.area_id:not(.add) {
|
||||
border: 2px solid #fed6a4;
|
||||
background: var(--card-background-color);
|
||||
ha-chip {
|
||||
--ha-chip-icon-color: var(--secondary-text-color);
|
||||
--ha-chip-text-color: var(--primary-text-color);
|
||||
}
|
||||
.mdc-chip.area_id:not(.add) .mdc-chip__icon--leading,
|
||||
.mdc-chip.area_id.add {
|
||||
background: #fed6a4;
|
||||
}
|
||||
.mdc-chip.device_id:not(.add) {
|
||||
border: 2px solid #a8e1fb;
|
||||
background: var(--card-background-color);
|
||||
}
|
||||
.mdc-chip.device_id:not(.add) .mdc-chip__icon--leading,
|
||||
.mdc-chip.device_id.add {
|
||||
background: #a8e1fb;
|
||||
}
|
||||
.mdc-chip.entity_id:not(.add) {
|
||||
border: 2px solid #d2e7b9;
|
||||
background: var(--card-background-color);
|
||||
}
|
||||
.mdc-chip.entity_id:not(.add) .mdc-chip__icon--leading,
|
||||
.mdc-chip.entity_id.add {
|
||||
background: #d2e7b9;
|
||||
}
|
||||
.mdc-chip:hover {
|
||||
ha-chip:hover {
|
||||
z-index: 5;
|
||||
}
|
||||
ha-icon-button {
|
||||
--mdc-icon-size: 18px;
|
||||
--mdc-icon-button-size: 24px;
|
||||
color: var(--secondary-text-color);
|
||||
outline: none;
|
||||
}
|
||||
ha-chip > div {
|
||||
margin-right: -8px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
ha-chip.area_id {
|
||||
--ha-chip-background-color: #fed6a4;
|
||||
}
|
||||
ha-chip.device_id {
|
||||
--ha-chip-background-color: #a8e1fb;
|
||||
}
|
||||
ha-chip.entity_id {
|
||||
--ha-chip-background-color: #d2e7b9;
|
||||
}
|
||||
paper-tooltip.expand {
|
||||
min-width: 200px;
|
||||
}
|
||||
:host([disabled]) .mdc-chip {
|
||||
opacity: var(--light-disabled-opacity);
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,10 @@ class HaWebRtcPlayer extends LitElement {
|
||||
// don't cache this, as we remove it on disconnects
|
||||
@query("#remote-stream") private _videoEl!: HTMLVideoElement;
|
||||
|
||||
private _peerConnection?: RTCPeerConnection;
|
||||
|
||||
private _remoteStream?: MediaStream;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this._error) {
|
||||
return html`<ha-alert alert-type="error">${this._error}</ha-alert>`;
|
||||
@@ -71,6 +75,7 @@ class HaWebRtcPlayer extends LitElement {
|
||||
|
||||
private async _startWebRtc(): Promise<void> {
|
||||
this._error = undefined;
|
||||
|
||||
const peerConnection = new RTCPeerConnection();
|
||||
// Some cameras (such as nest) require a data channel to establish a stream
|
||||
// however, not used by any integrations.
|
||||
@@ -93,6 +98,7 @@ class HaWebRtcPlayer extends LitElement {
|
||||
);
|
||||
} catch (err: any) {
|
||||
this._error = "Failed to start WebRTC stream: " + err.message;
|
||||
peerConnection.close();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -102,21 +108,39 @@ class HaWebRtcPlayer extends LitElement {
|
||||
remoteStream.addTrack(event.track);
|
||||
this._videoEl.srcObject = remoteStream;
|
||||
});
|
||||
this._remoteStream = remoteStream;
|
||||
|
||||
// Initiate the stream with the remote device
|
||||
const remoteDesc = new RTCSessionDescription({
|
||||
type: "answer",
|
||||
sdp: webRtcAnswer.answer,
|
||||
});
|
||||
await peerConnection.setRemoteDescription(remoteDesc);
|
||||
try {
|
||||
await peerConnection.setRemoteDescription(remoteDesc);
|
||||
} catch (err: any) {
|
||||
this._error = "Failed to connect WebRTC stream: " + err.message;
|
||||
peerConnection.close();
|
||||
return;
|
||||
}
|
||||
this._peerConnection = peerConnection;
|
||||
}
|
||||
|
||||
private _cleanUp() {
|
||||
if (this._remoteStream) {
|
||||
this._remoteStream.getTracks().forEach((track) => {
|
||||
track.stop();
|
||||
});
|
||||
this._remoteStream = undefined;
|
||||
}
|
||||
if (this._videoEl) {
|
||||
const videoEl = this._videoEl;
|
||||
videoEl.removeAttribute("src");
|
||||
videoEl.load();
|
||||
}
|
||||
if (this._peerConnection) {
|
||||
this._peerConnection.close();
|
||||
this._peerConnection = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
||||
@@ -194,10 +194,10 @@ export interface NumericStateCondition extends BaseCondition {
|
||||
|
||||
export interface SunCondition extends BaseCondition {
|
||||
condition: "sun";
|
||||
after_offset: number;
|
||||
before_offset: number;
|
||||
after: "sunrise" | "sunset";
|
||||
before: "sunrise" | "sunset";
|
||||
after_offset?: number;
|
||||
before_offset?: number;
|
||||
after?: "sunrise" | "sunset";
|
||||
before?: "sunrise" | "sunset";
|
||||
}
|
||||
|
||||
export interface ZoneCondition extends BaseCondition {
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ interface CameraEntityAttributes extends HassEntityAttributeBase {
|
||||
access_token: string;
|
||||
brand: string;
|
||||
motion_detection: boolean;
|
||||
stream_type: string;
|
||||
frontend_stream_type: string;
|
||||
}
|
||||
|
||||
export interface CameraEntity extends HassEntityBase {
|
||||
|
||||
@@ -62,6 +62,8 @@ export type CloudStatus = CloudStatusNotLoggedIn | CloudStatusLoggedIn;
|
||||
|
||||
export interface SubscriptionInfo {
|
||||
human_description: string;
|
||||
provider: string;
|
||||
plan_renewal_date?: number;
|
||||
}
|
||||
|
||||
export interface CloudWebhook {
|
||||
@@ -76,6 +78,39 @@ export interface ThingTalkConversion {
|
||||
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) =>
|
||||
hass.callWS<CloudStatus>({ type: "cloud/status" });
|
||||
|
||||
|
||||
@@ -77,12 +77,24 @@ export interface StatisticsMetaData {
|
||||
}
|
||||
|
||||
export type StatisticsValidationResult =
|
||||
| StatisticsValidationResultNoState
|
||||
| StatisticsValidationResultEntityNotRecorded
|
||||
| StatisticsValidationResultEntityNoLongerRecorded
|
||||
| StatisticsValidationResultUnsupportedStateClass
|
||||
| StatisticsValidationResultUnitsChanged
|
||||
| StatisticsValidationResultUnsupportedUnitMetadata
|
||||
| StatisticsValidationResultUnsupportedUnitState;
|
||||
|
||||
export interface StatisticsValidationResultNoState {
|
||||
type: "no_state";
|
||||
data: { statistic_id: string };
|
||||
}
|
||||
|
||||
export interface StatisticsValidationResultEntityNoLongerRecorded {
|
||||
type: "entity_no_longer_recorded";
|
||||
data: { statistic_id: string };
|
||||
}
|
||||
|
||||
export interface StatisticsValidationResultEntityNotRecorded {
|
||||
type: "entity_not_recorded";
|
||||
data: { statistic_id: string };
|
||||
|
||||
@@ -10,8 +10,15 @@ import {
|
||||
mdiImage,
|
||||
mdiMovie,
|
||||
mdiMusic,
|
||||
mdiPause,
|
||||
mdiPlay,
|
||||
mdiPlaylistMusic,
|
||||
mdiPlayPause,
|
||||
mdiPodcast,
|
||||
mdiPower,
|
||||
mdiSkipNext,
|
||||
mdiSkipPrevious,
|
||||
mdiStop,
|
||||
mdiTelevisionClassic,
|
||||
mdiVideo,
|
||||
mdiWeb,
|
||||
@@ -246,7 +253,7 @@ export const computeMediaControls = (
|
||||
return supportsFeature(stateObj, SUPPORT_TURN_ON)
|
||||
? [
|
||||
{
|
||||
icon: "hass:power",
|
||||
icon: mdiPower,
|
||||
action: "turn_on",
|
||||
},
|
||||
]
|
||||
@@ -257,7 +264,7 @@ export const computeMediaControls = (
|
||||
|
||||
if (supportsFeature(stateObj, SUPPORT_TURN_OFF)) {
|
||||
buttons.push({
|
||||
icon: "hass:power",
|
||||
icon: mdiPower,
|
||||
action: "turn_off",
|
||||
});
|
||||
}
|
||||
@@ -267,7 +274,7 @@ export const computeMediaControls = (
|
||||
supportsFeature(stateObj, SUPPORT_PREVIOUS_TRACK)
|
||||
) {
|
||||
buttons.push({
|
||||
icon: "hass:skip-previous",
|
||||
icon: mdiSkipPrevious,
|
||||
action: "media_previous_track",
|
||||
});
|
||||
}
|
||||
@@ -285,12 +292,12 @@ export const computeMediaControls = (
|
||||
buttons.push({
|
||||
icon:
|
||||
state === "on"
|
||||
? "hass:play-pause"
|
||||
? mdiPlayPause
|
||||
: state !== "playing"
|
||||
? "hass:play"
|
||||
? mdiPlay
|
||||
: supportsFeature(stateObj, SUPPORT_PAUSE)
|
||||
? "hass:pause"
|
||||
: "hass:stop",
|
||||
? mdiPause
|
||||
: mdiStop,
|
||||
action:
|
||||
state !== "playing"
|
||||
? "media_play"
|
||||
@@ -305,7 +312,7 @@ export const computeMediaControls = (
|
||||
supportsFeature(stateObj, SUPPORT_NEXT_TRACK)
|
||||
) {
|
||||
buttons.push({
|
||||
icon: "hass:skip-next",
|
||||
icon: mdiSkipNext,
|
||||
action: "media_next_track",
|
||||
});
|
||||
}
|
||||
|
||||
+8
-13
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
mdiAlertCircleOutline,
|
||||
mdiGauge,
|
||||
mdiWaterPercent,
|
||||
mdiWeatherFog,
|
||||
@@ -12,7 +13,6 @@ import {
|
||||
import { css, html, svg, SVGTemplateResult, TemplateResult } from "lit";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { formatNumber } from "../common/number/format_number";
|
||||
import "../components/ha-icon";
|
||||
import "../components/ha-svg-icon";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
@@ -57,7 +57,7 @@ export const weatherSVGs = new Set<string>([
|
||||
]);
|
||||
|
||||
export const weatherIcons = {
|
||||
exceptional: "hass:alert-circle-outline",
|
||||
exceptional: mdiAlertCircleOutline,
|
||||
};
|
||||
|
||||
export const weatherAttrIcons = {
|
||||
@@ -245,17 +245,9 @@ const getWeatherExtrema = (
|
||||
const unit = getWeatherUnit(hass!, "temperature");
|
||||
|
||||
return html`
|
||||
${tempHigh
|
||||
? `
|
||||
${tempHigh} ${unit}
|
||||
`
|
||||
: ""}
|
||||
${tempHigh ? `${formatNumber(tempHigh, hass.locale)} ${unit}` : ""}
|
||||
${tempLow && tempHigh ? " / " : ""}
|
||||
${tempLow
|
||||
? `
|
||||
${tempLow} ${unit}
|
||||
`
|
||||
: ""}
|
||||
${tempLow ? `${formatNumber(tempLow, hass.locale)} ${unit}` : ""}
|
||||
`;
|
||||
};
|
||||
|
||||
@@ -441,7 +433,10 @@ export const getWeatherStateIcon = (
|
||||
|
||||
if (state in weatherIcons) {
|
||||
return html`
|
||||
<ha-icon class="weather-icon" .icon=${weatherIcons[state]}></ha-icon>
|
||||
<ha-svg-icon
|
||||
class="weather-icon"
|
||||
.path=${weatherIcons[state]}
|
||||
></ha-svg-icon>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,9 @@ export interface ZWaveJSNodeStatus {
|
||||
ready: boolean;
|
||||
status: number;
|
||||
is_secure: boolean | string;
|
||||
is_routing: boolean | null;
|
||||
zwave_plus_version: number | null;
|
||||
highest_security_class: SecurityClass | null;
|
||||
}
|
||||
|
||||
export interface ZwaveJSNodeMetadata {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
|
||||
@@ -48,6 +48,7 @@ class StepFlowForm extends LitElement {
|
||||
: ""}
|
||||
<ha-form
|
||||
.data=${stepData}
|
||||
.disabled=${this._loading}
|
||||
@value-changed=${this._stepDataChanged}
|
||||
.schema=${step.data_schema}
|
||||
.error=${step.errors}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import type { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -12,6 +10,7 @@ import { property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-camera-stream";
|
||||
import { HaCheckbox } from "../../../components/ha-checkbox";
|
||||
import {
|
||||
CameraEntity,
|
||||
CameraPreferences,
|
||||
@@ -25,7 +24,7 @@ import type { HomeAssistant } from "../../../types";
|
||||
class MoreInfoCamera extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property() public stateObj?: CameraEntity;
|
||||
@property({ attribute: false }) public stateObj?: CameraEntity;
|
||||
|
||||
@state() private _cameraPrefs?: CameraPreferences;
|
||||
|
||||
@@ -55,12 +54,14 @@ class MoreInfoCamera extends LitElement {
|
||||
></ha-camera-stream>
|
||||
${this._cameraPrefs
|
||||
? html`
|
||||
<paper-checkbox
|
||||
.checked=${this._cameraPrefs.preload_stream}
|
||||
@change=${this._handleCheckboxChanged}
|
||||
>
|
||||
Preload stream
|
||||
</paper-checkbox>
|
||||
<ha-formfield>
|
||||
<ha-checkbox
|
||||
.checked=${this._cameraPrefs.preload_stream}
|
||||
@change=${this._handleCheckboxChanged}
|
||||
>
|
||||
Preload stream
|
||||
</ha-checkbox>
|
||||
</ha-formfield>
|
||||
`
|
||||
: undefined}
|
||||
`;
|
||||
@@ -86,7 +87,7 @@ class MoreInfoCamera extends LitElement {
|
||||
supportsFeature(this.stateObj!, CAMERA_SUPPORT_STREAM) &&
|
||||
// The stream component for HLS streams supports a server-side pre-load
|
||||
// option that client initiated WebRTC streams do not
|
||||
this.stateObj!.attributes.stream_type === STREAM_TYPE_HLS
|
||||
this.stateObj!.attributes.frontend_stream_type === STREAM_TYPE_HLS
|
||||
) {
|
||||
// Fetch in background while we set up the video.
|
||||
this._fetchCameraPrefs();
|
||||
@@ -101,7 +102,7 @@ class MoreInfoCamera extends LitElement {
|
||||
}
|
||||
|
||||
private async _handleCheckboxChanged(ev) {
|
||||
const checkbox = ev.currentTarget as PaperCheckboxElement;
|
||||
const checkbox = ev.currentTarget as HaCheckbox;
|
||||
try {
|
||||
this._cameraPrefs = await updateCameraPrefs(
|
||||
this.hass!,
|
||||
@@ -122,7 +123,7 @@ class MoreInfoCamera extends LitElement {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
paper-checkbox {
|
||||
ha-checkbox {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
@@ -135,3 +136,9 @@ class MoreInfoCamera extends LitElement {
|
||||
}
|
||||
|
||||
customElements.define("more-info-camera", MoreInfoCamera);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"more-info-camera": MoreInfoCamera;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import {
|
||||
mdiLoginVariant,
|
||||
mdiMusicNote,
|
||||
mdiPlayBoxMultiple,
|
||||
mdiSend,
|
||||
mdiVolumeHigh,
|
||||
@@ -15,8 +17,8 @@ import { customElement, property, query } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-paper-dropdown-menu";
|
||||
import "../../../components/ha-slider";
|
||||
import { showMediaBrowserDialog } from "../../../components/media-player/show-media-browser-dialog";
|
||||
@@ -62,8 +64,8 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
<ha-icon-button
|
||||
action=${control.action}
|
||||
@click=${this._handleClick}
|
||||
.path=${control.icon}
|
||||
>
|
||||
<ha-icon .icon=${control.icon}></ha-icon>
|
||||
</ha-icon-button>
|
||||
`
|
||||
)}
|
||||
@@ -130,7 +132,10 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
stateObj.attributes.source_list?.length
|
||||
? html`
|
||||
<div class="source-input">
|
||||
<ha-icon class="source-input" icon="hass:login-variant"></ha-icon>
|
||||
<ha-svg-icon
|
||||
class="source-input"
|
||||
.path=${mdiLoginVariant}
|
||||
></ha-svg-icon>
|
||||
<ha-paper-dropdown-menu
|
||||
.label=${this.hass.localize("ui.card.media_player.source")}
|
||||
>
|
||||
@@ -155,7 +160,7 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
stateObj.attributes.sound_mode_list?.length
|
||||
? html`
|
||||
<div class="sound-input">
|
||||
<ha-icon icon="hass:music-note"></ha-icon>
|
||||
<ha-svg-icon .path=${mdiMusicNote}></ha-svg-icon>
|
||||
<ha-paper-dropdown-menu
|
||||
dynamic-align
|
||||
label-float
|
||||
@@ -228,8 +233,8 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.source-input ha-icon,
|
||||
.sound-input ha-icon {
|
||||
.source-input ha-svg-icon,
|
||||
.sound-input ha-svg-icon {
|
||||
padding: 7px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
mdiFan,
|
||||
mdiHomeMapMarker,
|
||||
mdiMapMarker,
|
||||
mdiPause,
|
||||
@@ -195,7 +196,7 @@ class MoreInfoVacuum extends LitElement {
|
||||
style="justify-content: center; align-self: center; padding-top: 1.3em"
|
||||
>
|
||||
<span>
|
||||
<ha-icon icon="hass:fan"></ha-icon>
|
||||
<ha-svg-icon .path=${mdiFan}></ha-svg-icon>
|
||||
${stateObj.attributes.fan_speed}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -269,23 +269,15 @@ export class QuickBar extends LitElement {
|
||||
class="command-item"
|
||||
hasMeta
|
||||
>
|
||||
<span>
|
||||
<span class="command-entry">
|
||||
<ha-chip
|
||||
.label=${item.categoryText}
|
||||
hasIcon
|
||||
.leadingIcon=${item.iconPath}
|
||||
class="command-category ${item.categoryKey}"
|
||||
>
|
||||
${item.iconPath
|
||||
? html`<ha-svg-icon
|
||||
.path=${item.iconPath}
|
||||
slot="icon"
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
${item.categoryText}</ha-chip
|
||||
>
|
||||
</ha-chip>
|
||||
${item.primaryText}
|
||||
</span>
|
||||
|
||||
<span class="command-text">${item.primaryText}</span>
|
||||
</mwc-list-item>
|
||||
`;
|
||||
}
|
||||
@@ -386,10 +378,14 @@ export class QuickBar extends LitElement {
|
||||
private _generateEntityItems(): EntityItem[] {
|
||||
return Object.keys(this.hass.states)
|
||||
.map((entityId) => {
|
||||
const entityState = this.hass.states[entityId];
|
||||
const entityItem = {
|
||||
primaryText: computeStateName(this.hass.states[entityId]),
|
||||
primaryText: computeStateName(entityState),
|
||||
altText: entityId,
|
||||
icon: domainIcon(computeDomain(entityId), this.hass.states[entityId]),
|
||||
icon: entityState.attributes.icon,
|
||||
iconPath: entityState.attributes.icon
|
||||
? undefined
|
||||
: domainIcon(computeDomain(entityId), entityState),
|
||||
action: () => fireEvent(this, "hass-more-info", { entityId }),
|
||||
};
|
||||
|
||||
@@ -603,6 +599,10 @@ export class QuickBar extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
ha-chip {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
ha-icon.entity,
|
||||
ha-svg-icon.entity {
|
||||
margin-left: 20px;
|
||||
@@ -619,7 +619,6 @@ export class QuickBar extends LitElement {
|
||||
}
|
||||
|
||||
.command-category {
|
||||
--ha-chip-icon-color: #585858;
|
||||
--ha-chip-text-color: #212121;
|
||||
}
|
||||
|
||||
@@ -635,8 +634,9 @@ export class QuickBar extends LitElement {
|
||||
--ha-chip-background-color: var(--warning-color);
|
||||
}
|
||||
|
||||
span.command-text {
|
||||
margin-left: 8px;
|
||||
span.command-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
mwc-list-item {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
/* eslint-disable lit/prefer-static-styles */
|
||||
import { mdiMicrophone } from "@mdi/js";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import type { PaperDialogScrollableElement } from "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
@@ -17,7 +15,6 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { SpeechRecognition } from "../../common/dom/speech-recognition";
|
||||
import { uid } from "../../common/util/uid";
|
||||
import "../../components/dialog/ha-paper-dialog";
|
||||
import "../../components/ha-icon-button";
|
||||
import {
|
||||
AgentInfo,
|
||||
@@ -27,6 +24,9 @@ import {
|
||||
} from "../../data/conversation";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../../components/ha-dialog";
|
||||
import type { HaDialog } from "../../components/ha-dialog";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
|
||||
interface Message {
|
||||
who: string;
|
||||
@@ -56,7 +56,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
|
||||
@state() private _agentInfo?: AgentInfo;
|
||||
|
||||
@query("#messages", true) private messages!: PaperDialogScrollableElement;
|
||||
@query("ha-dialog", true) private _dialog!: HaDialog;
|
||||
|
||||
private recognition!: SpeechRecognition;
|
||||
|
||||
@@ -70,74 +70,42 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
this._agentInfo = await getAgentInfo(this.hass);
|
||||
}
|
||||
|
||||
public async closeDialog(): Promise<void> {
|
||||
this._opened = false;
|
||||
if (this.recognition) {
|
||||
this.recognition.abort();
|
||||
}
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
// CSS custom property mixins only work in render https://github.com/Polymer/lit-element/issues/633
|
||||
if (!this._opened) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<style>
|
||||
paper-dialog-scrollable {
|
||||
--paper-dialog-scrollable: {
|
||||
-webkit-overflow-scrolling: auto;
|
||||
max-height: 50vh !important;
|
||||
}
|
||||
}
|
||||
|
||||
paper-dialog-scrollable.can-scroll {
|
||||
--paper-dialog-scrollable: {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
max-height: 50vh !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
paper-dialog-scrollable {
|
||||
--paper-dialog-scrollable: {
|
||||
-webkit-overflow-scrolling: auto;
|
||||
max-height: calc(100vh - 175px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
paper-dialog-scrollable.can-scroll {
|
||||
--paper-dialog-scrollable: {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
max-height: calc(100vh - 175px) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<ha-paper-dialog
|
||||
with-backdrop
|
||||
.opened=${this._opened}
|
||||
@opened-changed=${this._openedChanged}
|
||||
>
|
||||
${this._agentInfo && this._agentInfo.onboarding
|
||||
? html`
|
||||
<div class="onboarding">
|
||||
${this._agentInfo.onboarding.text}
|
||||
<div class="side-by-side" @click=${this._completeOnboarding}>
|
||||
<a
|
||||
class="button"
|
||||
href=${this._agentInfo.onboarding.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
><mwc-button unelevated
|
||||
>${this.hass.localize("ui.common.yes")}!</mwc-button
|
||||
></a
|
||||
>
|
||||
<mwc-button outlined
|
||||
>${this.hass.localize("ui.common.no")}</mwc-button
|
||||
>
|
||||
<ha-dialog open @closed=${this.closeDialog}>
|
||||
<div>
|
||||
${this._agentInfo && this._agentInfo.onboarding
|
||||
? html`
|
||||
<div class="onboarding">
|
||||
${this._agentInfo.onboarding.text}
|
||||
<div class="side-by-side" @click=${this._completeOnboarding}>
|
||||
<a
|
||||
class="button"
|
||||
href=${this._agentInfo.onboarding.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
><mwc-button unelevated
|
||||
>${this.hass.localize("ui.common.yes")}!</mwc-button
|
||||
></a
|
||||
>
|
||||
<mwc-button outlined
|
||||
>${this.hass.localize("ui.common.no")}</mwc-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<paper-dialog-scrollable
|
||||
id="messages"
|
||||
class=${classMap({
|
||||
"top-border": Boolean(
|
||||
this._agentInfo && this._agentInfo.onboarding
|
||||
),
|
||||
})}
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
${this._conversation.map(
|
||||
(message) => html`
|
||||
<div class=${this._computeMessageClasses(message)}>
|
||||
@@ -157,8 +125,8 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</paper-dialog-scrollable>
|
||||
<div class="input">
|
||||
</div>
|
||||
<div class="input" slot="primaryAction">
|
||||
<paper-input
|
||||
@keyup=${this._handleKeyUp}
|
||||
.label=${this.hass.localize(
|
||||
@@ -200,7 +168,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</ha-paper-dialog>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -346,18 +314,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
}
|
||||
|
||||
private _scrollMessagesBottom() {
|
||||
this.messages.scrollTarget.scrollTop =
|
||||
this.messages.scrollTarget.scrollHeight;
|
||||
if (this.messages.scrollTarget.scrollTop === 0) {
|
||||
fireEvent(this.messages, "iron-resize");
|
||||
}
|
||||
}
|
||||
|
||||
private _openedChanged(ev: CustomEvent) {
|
||||
this._opened = ev.detail.value;
|
||||
if (!this._opened && this.recognition) {
|
||||
this.recognition.abort();
|
||||
}
|
||||
this._dialog.scrollToPos(0, 99999);
|
||||
}
|
||||
|
||||
private _computeMessageClasses(message: Message) {
|
||||
@@ -368,10 +325,6 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
:host {
|
||||
z-index: 103;
|
||||
}
|
||||
|
||||
ha-icon-button {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
@@ -380,13 +333,12 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.input {
|
||||
margin: 0 0 16px 0;
|
||||
ha-dialog {
|
||||
--primary-action-button-flex: 1;
|
||||
--secondary-action-button-flex: 0;
|
||||
--mdc-dialog-max-width: 450px;
|
||||
}
|
||||
|
||||
ha-paper-dialog {
|
||||
width: 450px;
|
||||
}
|
||||
a.button {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -394,16 +346,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
width: 100%;
|
||||
}
|
||||
.onboarding {
|
||||
padding: 0 24px;
|
||||
}
|
||||
paper-dialog-scrollable.top-border::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: var(--divider-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
.side-by-side {
|
||||
display: flex;
|
||||
|
||||
@@ -91,12 +91,6 @@ class SupervisorErrorScreen extends LitElement {
|
||||
: this.hass.themes.default_theme);
|
||||
|
||||
themeSettings = this.hass.selectedTheme;
|
||||
if (themeName === "default" && themeSettings?.dark === undefined) {
|
||||
themeSettings = {
|
||||
...this.hass.selectedTheme,
|
||||
dark: this.hass.themes.darkMode,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
themeName =
|
||||
(this.hass.selectedTheme as unknown as string) ||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { PaperDialogBehavior } from "@polymer/paper-dialog-behavior/paper-dialog-behavior";
|
||||
import { mixinBehaviors } from "@polymer/polymer/lib/legacy/class";
|
||||
import { dedupingMixin } from "@polymer/polymer/lib/utils/mixin";
|
||||
import { EventsMixin } from "./events-mixin";
|
||||
/**
|
||||
* @polymerMixin
|
||||
* @appliesMixin EventsMixin
|
||||
* @appliesMixin PaperDialogBehavior
|
||||
*/
|
||||
export default dedupingMixin(
|
||||
(superClass) =>
|
||||
class extends mixinBehaviors(
|
||||
[EventsMixin, PaperDialogBehavior],
|
||||
superClass
|
||||
) {
|
||||
static get properties() {
|
||||
return {
|
||||
withBackdrop: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../components/ha-icon";
|
||||
import "../components/ha-svg-icon";
|
||||
|
||||
@customElement("action-badge")
|
||||
class ActionBadge extends LitElement {
|
||||
@@ -15,9 +15,12 @@ class ActionBadge extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="icon">
|
||||
<ha-icon .icon=${this.icon}></ha-icon>
|
||||
<ha-svg-icon .path=${this.icon}></ha-svg-icon>
|
||||
${this.badgeIcon
|
||||
? html` <ha-icon class="badge" .icon=${this.badgeIcon}></ha-icon> `
|
||||
? html`<ha-svg-icon
|
||||
class="badge"
|
||||
.path=${this.badgeIcon}
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="title">${this.title}</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../components/ha-icon";
|
||||
import "../components/ha-svg-icon";
|
||||
import { brandsUrl } from "../util/brands-url";
|
||||
|
||||
@customElement("integration-badge")
|
||||
@@ -27,7 +27,10 @@ class IntegrationBadge extends LitElement {
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
${this.badgeIcon
|
||||
? html` <ha-icon class="badge" .icon=${this.badgeIcon}></ha-icon> `
|
||||
? html`<ha-svg-icon
|
||||
class="badge"
|
||||
.path=${this.badgeIcon}
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="title">${this.title}</div>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-radio-button/paper-radio-button";
|
||||
import "@polymer/paper-radio-group/paper-radio-group";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -21,6 +19,9 @@ import { SYMBOL_TO_ISO } from "../data/currency";
|
||||
import { onboardCoreConfigStep } from "../data/onboarding";
|
||||
import type { PolymerChangedEvent } from "../polymer-types";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "../components/ha-radio";
|
||||
import "../components/ha-formfield";
|
||||
import type { HaRadio } from "../components/ha-radio";
|
||||
|
||||
const amsterdam: [number, number] = [52.3731339, 4.8903147];
|
||||
const mql = matchMedia("(prefers-color-scheme: dark)");
|
||||
@@ -135,32 +136,44 @@ class OnboardingCoreConfig extends LitElement {
|
||||
"ui.panel.config.core.section.core.core_config.unit_system"
|
||||
)}
|
||||
</div>
|
||||
<paper-radio-group
|
||||
class="flex"
|
||||
.selected=${this._unitSystemValue}
|
||||
@selected-changed=${this._unitSystemChanged}
|
||||
>
|
||||
<paper-radio-button name="metric" .disabled=${this._working}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.core.section.core.core_config.unit_system_metric"
|
||||
)}
|
||||
<div class="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.core.section.core.core_config.metric_example"
|
||||
<div class="radio-group">
|
||||
<ha-formfield
|
||||
.label=${html`${this.hass.localize(
|
||||
"ui.panel.config.core.section.core.core_config.unit_system_metric"
|
||||
)}
|
||||
</div>
|
||||
</paper-radio-button>
|
||||
<paper-radio-button name="imperial" .disabled=${this._working}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.core.section.core.core_config.unit_system_imperial"
|
||||
)}
|
||||
<div class="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.core.section.core.core_config.imperial_example"
|
||||
<div class="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.core.section.core.core_config.metric_example"
|
||||
)}
|
||||
</div>`}
|
||||
>
|
||||
<ha-radio
|
||||
name="unit_system"
|
||||
value="metric"
|
||||
.checked=${this._unitSystemValue === "metric"}
|
||||
@change=${this._unitSystemChanged}
|
||||
.disabled=${this._working}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${html`${this.hass.localize(
|
||||
"ui.panel.config.core.section.core.core_config.unit_system_imperial"
|
||||
)}
|
||||
</div>
|
||||
</paper-radio-button>
|
||||
</paper-radio-group>
|
||||
<div class="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.core.section.core.core_config.imperial_example"
|
||||
)}
|
||||
</div>`}
|
||||
>
|
||||
<ha-radio
|
||||
name="unit_system"
|
||||
value="imperial"
|
||||
.checked=${this._unitSystemValue === "imperial"}
|
||||
@change=${this._unitSystemChanged}
|
||||
.disabled=${this._working}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
@@ -281,10 +294,8 @@ class OnboardingCoreConfig extends LitElement {
|
||||
this._location = ev.detail.location;
|
||||
}
|
||||
|
||||
private _unitSystemChanged(
|
||||
ev: PolymerChangedEvent<ConfigUpdateValues["unit_system"]>
|
||||
) {
|
||||
this._unitSystem = ev.detail.value;
|
||||
private _unitSystemChanged(ev: CustomEvent) {
|
||||
this._unitSystem = (ev.target as HaRadio).value as "metric" | "imperial";
|
||||
}
|
||||
|
||||
private async _detect() {
|
||||
@@ -363,6 +374,13 @@ class OnboardingCoreConfig extends LitElement {
|
||||
.row > * {
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 16px;
|
||||
text-align: right;
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { mdiCheck, mdiDotsHorizontal } from "@mdi/js";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
@@ -79,7 +80,7 @@ class OnboardingIntegrations extends LitElement {
|
||||
<integration-badge
|
||||
.domain=${entry.domain}
|
||||
.title=${title}
|
||||
badgeIcon="hass:check"
|
||||
.badgeIcon=${mdiCheck}
|
||||
.darkOptimizedIcon=${this.hass.selectedTheme?.dark}
|
||||
></integration-badge>
|
||||
`,
|
||||
@@ -120,7 +121,7 @@ class OnboardingIntegrations extends LitElement {
|
||||
title=${this.onboardingLocalize(
|
||||
"ui.panel.page-onboarding.integration.more_integrations"
|
||||
)}
|
||||
icon="hass:dots-horizontal"
|
||||
.icon=${mdiDotsHorizontal}
|
||||
></action-badge>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -371,6 +371,10 @@ export class HAFullCalendar extends LitElement {
|
||||
);
|
||||
--fc-theme-standard-border-color: var(--divider-color);
|
||||
--fc-border-color: var(--divider-color);
|
||||
--fc-page-bg-color: var(
|
||||
--ha-card-background,
|
||||
var(--card-background-color, white)
|
||||
);
|
||||
}
|
||||
|
||||
a {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiArrowDown, mdiArrowUp, mdiDotsVertical } from "@mdi/js";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox";
|
||||
import "@material/mwc-select";
|
||||
import type { Select } from "@material/mwc-select";
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stringCompare } from "../../../../common/string/compare";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
||||
import type { Action } from "../../../../data/script";
|
||||
@@ -42,7 +44,8 @@ const OPTIONS = [
|
||||
"device_id",
|
||||
];
|
||||
|
||||
const getType = (action: Action) => OPTIONS.find((option) => option in action);
|
||||
const getType = (action: Action | undefined) =>
|
||||
action ? OPTIONS.find((option) => option in action) : undefined;
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
@@ -97,6 +100,19 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
|
||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
private _processedTypes = memoizeOne(
|
||||
(localize: LocalizeFunc): [string, string][] =>
|
||||
OPTIONS.map(
|
||||
(action) =>
|
||||
[
|
||||
action,
|
||||
localize(
|
||||
`ui.panel.config.automation.editor.actions.type.${action}.label`
|
||||
),
|
||||
] as [string, string]
|
||||
).sort((a, b) => stringCompare(a[1], b[1]))
|
||||
);
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (!changedProperties.has("action")) {
|
||||
return;
|
||||
@@ -171,9 +187,12 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
${this._warnings
|
||||
? html`<div class="warning">
|
||||
${this.hass.localize("ui.errors.config.editor_not_supported")}:
|
||||
<br />
|
||||
? html`<ha-alert
|
||||
alert-type="warning"
|
||||
.title=${this.hass.localize(
|
||||
"ui.errors.config.editor_not_supported"
|
||||
)}
|
||||
>
|
||||
${this._warnings!.length > 0 && this._warnings![0] !== undefined
|
||||
? html` <ul>
|
||||
${this._warnings!.map(
|
||||
@@ -182,7 +201,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
</ul>`
|
||||
: ""}
|
||||
${this.hass.localize("ui.errors.config.edit_in_yaml_supported")}
|
||||
</div>`
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
${yamlMode
|
||||
? html`
|
||||
@@ -206,28 +225,21 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
></ha-yaml-editor>
|
||||
`
|
||||
: html`
|
||||
<paper-dropdown-menu-light
|
||||
<mwc-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type_select"
|
||||
)}
|
||||
no-animations
|
||||
.value=${getType(this.action)}
|
||||
naturalMenuWidth
|
||||
@selected=${this._typeChanged}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${selected}
|
||||
@iron-select=${this._typeChanged}
|
||||
>
|
||||
${OPTIONS.map(
|
||||
(opt) => html`
|
||||
<paper-item .action=${opt}>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.actions.type.${opt}.label`
|
||||
)}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu-light>
|
||||
${this._processedTypes(this.hass.localize).map(
|
||||
([opt, label]) => html`
|
||||
<mwc-list-item .value=${opt}>${label}</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</mwc-select>
|
||||
|
||||
<div @ui-mode-not-available=${this._handleUiModeNotAvailable}>
|
||||
${dynamicElement(`ha-automation-action-${type}`, {
|
||||
hass: this.hass,
|
||||
@@ -287,8 +299,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
}
|
||||
|
||||
private _typeChanged(ev: CustomEvent) {
|
||||
const type = ((ev.target as PaperListboxElement)?.selectedItem as any)
|
||||
?.action;
|
||||
const type = (ev.target as Select).value;
|
||||
|
||||
if (!type) {
|
||||
return;
|
||||
|
||||
@@ -40,6 +40,9 @@ export class HaDelayAction extends LitElement implements ActionElement {
|
||||
|
||||
protected render() {
|
||||
return html`<ha-duration-input
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.actions.type.delay.delay`
|
||||
)}
|
||||
.data=${this._timeData}
|
||||
enableMillisecond
|
||||
@value-changed=${this._valueChanged}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox";
|
||||
import { CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "@material/mwc-select";
|
||||
import type { Select } from "@material/mwc-select";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stringCompare } from "../../../../common/string/compare";
|
||||
import { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-yaml-editor";
|
||||
import type { Condition } from "../../../../data/automation";
|
||||
@@ -45,6 +46,19 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
|
||||
@property() public yamlMode = false;
|
||||
|
||||
private _processedTypes = memoizeOne(
|
||||
(localize: LocalizeFunc): [string, string][] =>
|
||||
OPTIONS.map(
|
||||
(condition) =>
|
||||
[
|
||||
condition,
|
||||
localize(
|
||||
`ui.panel.config.automation.editor.conditions.type.${condition}.label`
|
||||
),
|
||||
] as [string, string]
|
||||
).sort((a, b) => stringCompare(a[1], b[1]))
|
||||
);
|
||||
|
||||
protected render() {
|
||||
const selected = OPTIONS.indexOf(this.condition.condition);
|
||||
const yamlMode = this.yamlMode || selected === -1;
|
||||
@@ -71,28 +85,21 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
></ha-yaml-editor>
|
||||
`
|
||||
: html`
|
||||
<paper-dropdown-menu-light
|
||||
<mwc-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type_select"
|
||||
)}
|
||||
no-animations
|
||||
.value=${this.condition.condition}
|
||||
naturalMenuWidth
|
||||
@selected=${this._typeChanged}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${selected}
|
||||
@iron-select=${this._typeChanged}
|
||||
>
|
||||
${OPTIONS.map(
|
||||
(opt) => html`
|
||||
<paper-item .condition=${opt}>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.conditions.type.${opt}.label`
|
||||
)}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu-light>
|
||||
${this._processedTypes(this.hass.localize).map(
|
||||
([opt, label]) => html`
|
||||
<mwc-list-item .value=${opt}>${label}</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</mwc-select>
|
||||
|
||||
<div>
|
||||
${dynamicElement(
|
||||
`ha-automation-condition-${this.condition.condition}`,
|
||||
@@ -104,8 +111,7 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
}
|
||||
|
||||
private _typeChanged(ev: CustomEvent) {
|
||||
const type = ((ev.target as PaperListboxElement)?.selectedItem as any)
|
||||
?.condition;
|
||||
const type = (ev.target as Select).value;
|
||||
|
||||
if (!type) {
|
||||
return;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { LogicalCondition } from "../../../../../data/automation";
|
||||
import { Condition, LogicalCondition } from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "../ha-automation-condition";
|
||||
import { ConditionElement } from "../ha-automation-condition-row";
|
||||
import { HaStateCondition } from "./ha-automation-condition-state";
|
||||
|
||||
@customElement("ha-automation-condition-logical")
|
||||
export class HaLogicalCondition extends LitElement implements ConditionElement {
|
||||
@@ -13,7 +14,14 @@ export class HaLogicalCondition extends LitElement implements ConditionElement {
|
||||
@property() public condition!: LogicalCondition;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { conditions: [{ condition: "state" }] };
|
||||
return {
|
||||
conditions: [
|
||||
{
|
||||
condition: "state",
|
||||
...HaStateCondition.defaultConfig,
|
||||
},
|
||||
] as Condition[],
|
||||
};
|
||||
}
|
||||
|
||||
protected render() {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-radio-button/paper-radio-button";
|
||||
import "@polymer/paper-radio-group/paper-radio-group";
|
||||
import type { PaperRadioGroupElement } from "@polymer/paper-radio-group/paper-radio-group";
|
||||
import { html, LitElement } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import type { SunCondition } from "../../../../../data/automation";
|
||||
@@ -11,12 +8,15 @@ import {
|
||||
ConditionElement,
|
||||
handleChangeEvent,
|
||||
} from "../ha-automation-condition-row";
|
||||
import "../../../../../components/ha-radio";
|
||||
import "../../../../../components/ha-formfield";
|
||||
import type { HaRadio } from "../../../../../components/ha-radio";
|
||||
|
||||
@customElement("ha-automation-condition-sun")
|
||||
export class HaSunCondition extends LitElement implements ConditionElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public condition!: SunCondition;
|
||||
@property({ attribute: false }) public condition!: SunCondition;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return {};
|
||||
@@ -25,28 +25,35 @@ export class HaSunCondition extends LitElement implements ConditionElement {
|
||||
protected render() {
|
||||
const { after, after_offset, before, before_offset } = this.condition;
|
||||
return html`
|
||||
<label id="beforelabel">
|
||||
<label>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.sun.before"
|
||||
)}
|
||||
</label>
|
||||
<paper-radio-group
|
||||
.selected=${before}
|
||||
.name=${"before"}
|
||||
aria-labelledby="beforelabel"
|
||||
@paper-radio-group-changed=${this._radioGroupPicked}
|
||||
>
|
||||
<paper-radio-button name="sunrise">
|
||||
${this.hass.localize(
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.sun.sunrise"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
<paper-radio-button name="sunset">
|
||||
${this.hass.localize(
|
||||
>
|
||||
<ha-radio
|
||||
name="before"
|
||||
value="sunrise"
|
||||
.checked=${before === "sunrise"}
|
||||
@change=${this._radioGroupPicked}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.sun.sunset"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
</paper-radio-group>
|
||||
>
|
||||
<ha-radio
|
||||
name="before"
|
||||
value="sunset"
|
||||
.checked=${before === "sunset"}
|
||||
@change=${this._radioGroupPicked}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
</label>
|
||||
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
@@ -57,28 +64,36 @@ export class HaSunCondition extends LitElement implements ConditionElement {
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
|
||||
<label id="afterlabel">
|
||||
<label>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.sun.after"
|
||||
)}
|
||||
</label>
|
||||
<paper-radio-group
|
||||
.selected=${after}
|
||||
.name=${"after"}
|
||||
aria-labelledby="afterlabel"
|
||||
@paper-radio-group-changed=${this._radioGroupPicked}
|
||||
>
|
||||
<paper-radio-button name="sunrise">
|
||||
${this.hass.localize(
|
||||
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.sun.sunrise"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
<paper-radio-button name="sunset">
|
||||
${this.hass.localize(
|
||||
>
|
||||
<ha-radio
|
||||
name="after"
|
||||
value="sunrise"
|
||||
.checked=${after === "sunrise"}
|
||||
@change=${this._radioGroupPicked}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.sun.sunset"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
</paper-radio-group>
|
||||
>
|
||||
<ha-radio
|
||||
name="after"
|
||||
value="sunset"
|
||||
.checked=${after === "sunset"}
|
||||
@change=${this._radioGroupPicked}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
</label>
|
||||
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
@@ -95,14 +110,27 @@ export class HaSunCondition extends LitElement implements ConditionElement {
|
||||
handleChangeEvent(this, ev);
|
||||
}
|
||||
|
||||
private _radioGroupPicked(ev) {
|
||||
const key = ev.target.name;
|
||||
private _radioGroupPicked(ev: CustomEvent) {
|
||||
const key = (ev.target as HaRadio).name;
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.condition,
|
||||
[key]: (ev.target as PaperRadioGroupElement).selected,
|
||||
[key]: (ev.target as HaRadio).value,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-condition-sun": HaSunCondition;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/dialog/ha-paper-dialog";
|
||||
import "../../../../components/ha-circular-progress";
|
||||
import type { AutomationConfig } from "../../../../data/automation";
|
||||
import { convertThingTalk } from "../../../../data/cloud";
|
||||
import type { PolymerChangedEvent } from "../../../../polymer-types";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "./ha-thingtalk-placeholders";
|
||||
import type { PlaceholderValues } from "./ha-thingtalk-placeholders";
|
||||
import type { ThingtalkDialogParams } from "./show-dialog-thingtalk";
|
||||
import "../../../../components/ha-dialog";
|
||||
|
||||
export interface Placeholder {
|
||||
name: string;
|
||||
@@ -38,8 +36,6 @@ class DialogThingtalk extends LitElement {
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
@state() private _placeholders?: PlaceholderContainer;
|
||||
|
||||
@query("#input") private _input?: PaperInputElement;
|
||||
@@ -51,7 +47,6 @@ class DialogThingtalk extends LitElement {
|
||||
public async showDialog(params: ThingtalkDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
this._opened = true;
|
||||
if (params.input) {
|
||||
this._value = params.input;
|
||||
await this.updateComplete;
|
||||
@@ -61,10 +56,10 @@ class DialogThingtalk extends LitElement {
|
||||
|
||||
public closeDialog() {
|
||||
this._placeholders = undefined;
|
||||
this._params = undefined;
|
||||
if (this._input) {
|
||||
this._input.value = null;
|
||||
}
|
||||
this._opened = false;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
@@ -77,26 +72,22 @@ class DialogThingtalk extends LitElement {
|
||||
<ha-thingtalk-placeholders
|
||||
.hass=${this.hass}
|
||||
.placeholders=${this._placeholders}
|
||||
.opened=${this._opened}
|
||||
.skip=${this._skip}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@closed=${this.closeDialog}
|
||||
@placeholders-filled=${this._handlePlaceholders}
|
||||
>
|
||||
</ha-thingtalk-placeholders>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
<ha-paper-dialog
|
||||
with-backdrop
|
||||
.opened=${this._opened}
|
||||
@opened-changed=${this._openedChanged}
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${this.hass.localize(
|
||||
`ui.panel.config.automation.thingtalk.task_selection.header`
|
||||
)}
|
||||
>
|
||||
<h2>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.thingtalk.task_selection.header`
|
||||
)}
|
||||
</h2>
|
||||
<paper-dialog-scrollable>
|
||||
<div>
|
||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.thingtalk.task_selection.introduction`
|
||||
@@ -143,23 +134,25 @@ class DialogThingtalk extends LitElement {
|
||||
class="attribution"
|
||||
>Powered by Almond</a
|
||||
>
|
||||
</paper-dialog-scrollable>
|
||||
<div class="paper-dialog-buttons">
|
||||
<mwc-button class="left" @click=${this._skip}>
|
||||
${this.hass.localize(`ui.common.skip`)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._generate} .disabled=${this._submitting}>
|
||||
${this._submitting
|
||||
? html`<ha-circular-progress
|
||||
active
|
||||
size="small"
|
||||
title="Creating your automation..."
|
||||
></ha-circular-progress>`
|
||||
: ""}
|
||||
${this.hass.localize(`ui.panel.config.automation.thingtalk.create`)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-paper-dialog>
|
||||
<mwc-button class="left" @click=${this._skip} slot="secondaryAction">
|
||||
${this.hass.localize(`ui.common.skip`)}
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
@click=${this._generate}
|
||||
.disabled=${this._submitting}
|
||||
slot="primaryAction"
|
||||
>
|
||||
${this._submitting
|
||||
? html`<ha-circular-progress
|
||||
active
|
||||
size="small"
|
||||
title="Creating your automation..."
|
||||
></ha-circular-progress>`
|
||||
: ""}
|
||||
${this.hass.localize(`ui.panel.config.automation.thingtalk.create`)}
|
||||
</mwc-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -234,12 +227,6 @@ class DialogThingtalk extends LitElement {
|
||||
this.closeDialog();
|
||||
};
|
||||
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
|
||||
if (!ev.detail.value) {
|
||||
this.closeDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private _handleKeyUp(ev: KeyboardEvent) {
|
||||
if (ev.keyCode === 13) {
|
||||
this._generate();
|
||||
@@ -255,7 +242,7 @@ class DialogThingtalk extends LitElement {
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-paper-dialog {
|
||||
ha-dialog {
|
||||
max-width: 500px;
|
||||
}
|
||||
mwc-button.left {
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
import { subscribeEntityRegistry } from "../../../../data/entity_registry";
|
||||
import { domainToName } from "../../../../data/integration";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
import { PolymerChangedEvent } from "../../../../polymer-types";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { Placeholder, PlaceholderContainer } from "./dialog-thingtalk";
|
||||
@@ -124,18 +123,14 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-paper-dialog
|
||||
modal
|
||||
with-backdrop
|
||||
.opened=${this.opened}
|
||||
@opened-changed=${this._openedChanged}
|
||||
<ha-dialog
|
||||
open
|
||||
scrimClickAction
|
||||
.heading=${this.hass.localize(
|
||||
`ui.panel.config.automation.thingtalk.link_devices.header`
|
||||
)}
|
||||
>
|
||||
<h2>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.thingtalk.link_devices.header`
|
||||
)}
|
||||
</h2>
|
||||
<paper-dialog-scrollable>
|
||||
<div>
|
||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||
${Object.entries(this.placeholders).map(
|
||||
([type, placeholders]) =>
|
||||
@@ -243,16 +238,18 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
|
||||
})}
|
||||
`
|
||||
)}
|
||||
</paper-dialog-scrollable>
|
||||
<div class="paper-dialog-buttons">
|
||||
<mwc-button class="left" @click=${this.skip}>
|
||||
${this.hass.localize(`ui.common.skip`)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._done} .disabled=${!this._isDone}>
|
||||
${this.hass.localize(`ui.panel.config.automation.thingtalk.create`)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-paper-dialog>
|
||||
<mwc-button @click=${this.skip} slot="secondaryAction">
|
||||
${this.hass.localize(`ui.common.skip`)}
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
@click=${this._done}
|
||||
.disabled=${!this._isDone}
|
||||
slot="primaryAction"
|
||||
>
|
||||
${this.hass.localize(`ui.panel.config.automation.thingtalk.create`)}
|
||||
</mwc-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -440,11 +437,6 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
|
||||
this.requestUpdate("_placeholderValues");
|
||||
}
|
||||
});
|
||||
|
||||
fireEvent(
|
||||
this.shadowRoot!.querySelector("ha-paper-dialog")! as HTMLElement,
|
||||
"iron-resize"
|
||||
);
|
||||
}
|
||||
|
||||
private _entityPicked(ev: Event): void {
|
||||
@@ -465,24 +457,16 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
|
||||
fireEvent(this, "placeholders-filled", { value: this._placeholderValues });
|
||||
}
|
||||
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
|
||||
// The opened-changed event doesn't leave the shadowdom so we re-dispatch it
|
||||
this.dispatchEvent(new CustomEvent(ev.type, ev));
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-paper-dialog {
|
||||
ha-dialog {
|
||||
max-width: 500px;
|
||||
}
|
||||
mwc-button.left {
|
||||
margin-right: auto;
|
||||
}
|
||||
paper-dialog-scrollable {
|
||||
margin-top: 10px;
|
||||
}
|
||||
h3 {
|
||||
margin: 10px 0 0 0;
|
||||
font-weight: 500;
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox";
|
||||
import "@material/mwc-select";
|
||||
import type { Select } from "@material/mwc-select";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stringCompare } from "../../../../common/string/compare";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import type { Trigger } from "../../../../data/automation";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
@@ -85,6 +87,19 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
|
||||
@state() private _yamlMode = false;
|
||||
|
||||
private _processedTypes = memoizeOne(
|
||||
(localize: LocalizeFunc): [string, string][] =>
|
||||
OPTIONS.map(
|
||||
(action) =>
|
||||
[
|
||||
action,
|
||||
localize(
|
||||
`ui.panel.config.automation.editor.triggers.type.${action}.label`
|
||||
),
|
||||
] as [string, string]
|
||||
).sort((a, b) => stringCompare(a[1], b[1]))
|
||||
);
|
||||
|
||||
protected render() {
|
||||
const selected = OPTIONS.indexOf(this.trigger.platform);
|
||||
const yamlMode = this._yamlMode || selected === -1;
|
||||
@@ -121,9 +136,12 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
${this._warnings
|
||||
? html`<div class="warning">
|
||||
${this.hass.localize("ui.errors.config.editor_not_supported")}:
|
||||
<br />
|
||||
? html`<ha-alert
|
||||
alert-type="warning"
|
||||
.title=${this.hass.localize(
|
||||
"ui.errors.config.editor_not_supported"
|
||||
)}
|
||||
>
|
||||
${this._warnings.length && this._warnings[0] !== undefined
|
||||
? html` <ul>
|
||||
${this._warnings.map(
|
||||
@@ -132,7 +150,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
</ul>`
|
||||
: ""}
|
||||
${this.hass.localize("ui.errors.config.edit_in_yaml_supported")}
|
||||
</div>`
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
${yamlMode
|
||||
? html`
|
||||
@@ -156,28 +174,21 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
></ha-yaml-editor>
|
||||
`
|
||||
: html`
|
||||
<paper-dropdown-menu-light
|
||||
<mwc-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type_select"
|
||||
)}
|
||||
no-animations
|
||||
.value=${this.trigger.platform}
|
||||
naturalMenuWidth
|
||||
@selected=${this._typeChanged}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${selected}
|
||||
@iron-select=${this._typeChanged}
|
||||
>
|
||||
${OPTIONS.map(
|
||||
(opt) => html`
|
||||
<paper-item .platform=${opt}>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.triggers.type.${opt}.label`
|
||||
)}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu-light>
|
||||
${this._processedTypes(this.hass.localize).map(
|
||||
([opt, label]) => html`
|
||||
<mwc-list-item .value=${opt}>${label}</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</mwc-select>
|
||||
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.id"
|
||||
@@ -233,8 +244,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
}
|
||||
|
||||
private _typeChanged(ev: CustomEvent) {
|
||||
const type = ((ev.target as PaperListboxElement)?.selectedItem as any)
|
||||
?.platform;
|
||||
const type = (ev.target as Select).value;
|
||||
|
||||
if (!type) {
|
||||
return;
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import "@polymer/paper-radio-button/paper-radio-button";
|
||||
import "@polymer/paper-radio-group/paper-radio-group";
|
||||
import type { PaperRadioGroupElement } from "@polymer/paper-radio-group/paper-radio-group";
|
||||
import { html, LitElement } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/entity/ha-entity-picker";
|
||||
import type { HaRadio } from "../../../../../components/ha-radio";
|
||||
import type { GeoLocationTrigger } from "../../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { handleChangeEvent } from "../ha-automation-trigger-row";
|
||||
@@ -12,16 +10,16 @@ import { handleChangeEvent } from "../ha-automation-trigger-row";
|
||||
const includeDomains = ["zone"];
|
||||
|
||||
@customElement("ha-automation-trigger-geo_location")
|
||||
export default class HaGeolocationTrigger extends LitElement {
|
||||
export class HaGeolocationTrigger extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public trigger!: GeoLocationTrigger;
|
||||
@property({ attribute: false }) public trigger!: GeoLocationTrigger;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return {
|
||||
source: "",
|
||||
zone: "",
|
||||
event: "enter",
|
||||
event: "enter" as GeoLocationTrigger["event"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -47,27 +45,35 @@ export default class HaGeolocationTrigger extends LitElement {
|
||||
allow-custom-entity
|
||||
.includeDomains=${includeDomains}
|
||||
></ha-entity-picker>
|
||||
<label id="eventlabel">
|
||||
<label>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.geo_location.event"
|
||||
)}
|
||||
</label>
|
||||
<paper-radio-group
|
||||
.selected=${event}
|
||||
aria-labelledby="eventlabel"
|
||||
@paper-radio-group-changed=${this._radioGroupPicked}
|
||||
>
|
||||
<paper-radio-button name="enter">
|
||||
${this.hass.localize(
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.geo_location.enter"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
<paper-radio-button name="leave">
|
||||
${this.hass.localize(
|
||||
>
|
||||
<ha-radio
|
||||
name="event"
|
||||
value="enter"
|
||||
.checked=${event === "enter"}
|
||||
@change=${this._radioGroupPicked}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.geo_location.leave"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
</paper-radio-group>
|
||||
>
|
||||
<ha-radio
|
||||
name="event"
|
||||
value="leave"
|
||||
.checked=${event === "leave"}
|
||||
@change=${this._radioGroupPicked}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
</label>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -87,10 +93,17 @@ export default class HaGeolocationTrigger extends LitElement {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.trigger,
|
||||
event: (ev.target as PaperRadioGroupElement).selected,
|
||||
event: (ev.target as HaRadio).value,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import "@polymer/paper-radio-button/paper-radio-button";
|
||||
import "@polymer/paper-radio-group/paper-radio-group";
|
||||
import type { PaperRadioGroupElement } from "@polymer/paper-radio-group/paper-radio-group";
|
||||
import { html, LitElement } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import type { HaRadio } from "../../../../../components/ha-radio";
|
||||
import type { HassTrigger } from "../../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../../../../../components/ha-formfield";
|
||||
import "../../../../../components/ha-radio";
|
||||
|
||||
@customElement("ha-automation-trigger-homeassistant")
|
||||
export default class HaHassTrigger extends LitElement {
|
||||
export class HaHassTrigger extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public trigger!: HassTrigger;
|
||||
@property({ attribute: false }) public trigger!: HassTrigger;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return {
|
||||
event: "start",
|
||||
event: "start" as HassTrigger["event"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,23 +26,31 @@ export default class HaHassTrigger extends LitElement {
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.homeassistant.event"
|
||||
)}
|
||||
</label>
|
||||
<paper-radio-group
|
||||
.selected=${event}
|
||||
aria-labelledby="eventlabel"
|
||||
@paper-radio-group-changed=${this._radioGroupPicked}
|
||||
>
|
||||
<paper-radio-button name="start">
|
||||
${this.hass.localize(
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.homeassistant.start"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
<paper-radio-button name="shutdown">
|
||||
${this.hass.localize(
|
||||
>
|
||||
<ha-radio
|
||||
name="event"
|
||||
value="start"
|
||||
.checked=${event === "start"}
|
||||
@change=${this._radioGroupPicked}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.homeassistant.shutdown"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
</paper-radio-group>
|
||||
>
|
||||
<ha-radio
|
||||
name="event"
|
||||
value="shutdown"
|
||||
.checked=${event === "shutdown"}
|
||||
@change=${this._radioGroupPicked}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
</label>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -51,10 +59,17 @@ export default class HaHassTrigger extends LitElement {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.trigger,
|
||||
event: (ev.target as PaperRadioGroupElement).selected,
|
||||
event: (ev.target as HaRadio).value,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { handleChangeEvent } from "../ha-automation-trigger-row";
|
||||
import "../../../../../components/ha-duration-input";
|
||||
|
||||
@customElement("ha-automation-trigger-numeric_state")
|
||||
export default class HaNumericStateTrigger extends LitElement {
|
||||
export class HaNumericStateTrigger extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public trigger!: NumericStateTrigger;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-radio-button/paper-radio-button";
|
||||
import "@polymer/paper-radio-group/paper-radio-group";
|
||||
import type { PaperRadioGroupElement } from "@polymer/paper-radio-group/paper-radio-group";
|
||||
import { html, LitElement } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-radio";
|
||||
import "../../../../../components/ha-formfield";
|
||||
import type { HaRadio } from "../../../../../components/ha-radio";
|
||||
import type { SunTrigger } from "../../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import {
|
||||
@@ -16,38 +16,47 @@ import {
|
||||
export class HaSunTrigger extends LitElement implements TriggerElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public trigger!: SunTrigger;
|
||||
@property({ attribute: false }) public trigger!: SunTrigger;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return {
|
||||
event: "sunrise",
|
||||
event: "sunrise" as SunTrigger["event"],
|
||||
offset: 0,
|
||||
};
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const { offset, event } = this.trigger;
|
||||
return html`
|
||||
<label id="eventlabel">
|
||||
<label>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.sun.event"
|
||||
)}
|
||||
</label>
|
||||
<paper-radio-group
|
||||
.selected=${event}
|
||||
aria-labelledby="eventlabel"
|
||||
@paper-radio-group-changed=${this._radioGroupPicked}
|
||||
>
|
||||
<paper-radio-button name="sunrise">
|
||||
${this.hass.localize(
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.sun.sunrise"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
<paper-radio-button name="sunset">
|
||||
${this.hass.localize(
|
||||
>
|
||||
<ha-radio
|
||||
name="event"
|
||||
value="sunrise"
|
||||
.checked=${event === "sunrise"}
|
||||
@change=${this._radioGroupPicked}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.sun.sunset"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
</paper-radio-group>
|
||||
>
|
||||
<ha-radio
|
||||
name="event"
|
||||
value="sunset"
|
||||
.checked=${event === "sunset"}
|
||||
@change=${this._radioGroupPicked}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
</label>
|
||||
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
@@ -69,8 +78,21 @@ export class HaSunTrigger extends LitElement implements TriggerElement {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.trigger,
|
||||
event: (ev.target as PaperRadioGroupElement).selected,
|
||||
event: (ev.target as HaRadio).value,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-trigger-sun": HaSunTrigger;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import "@polymer/paper-radio-button/paper-radio-button";
|
||||
import "@polymer/paper-radio-group/paper-radio-group";
|
||||
import type { PaperRadioGroupElement } from "@polymer/paper-radio-group/paper-radio-group";
|
||||
import { html, LitElement } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { computeStateDomain } from "../../../../../common/entity/compute_state_domain";
|
||||
@@ -10,6 +7,9 @@ import "../../../../../components/entity/ha-entity-picker";
|
||||
import type { ZoneTrigger } from "../../../../../data/automation";
|
||||
import type { PolymerChangedEvent } from "../../../../../polymer-types";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../../../../../components/ha-radio";
|
||||
import "../../../../../components/ha-formfield";
|
||||
import type { HaRadio } from "../../../../../components/ha-radio";
|
||||
|
||||
function zoneAndLocationFilter(stateObj) {
|
||||
return hasLocation(stateObj) && computeStateDomain(stateObj) !== "zone";
|
||||
@@ -27,7 +27,7 @@ export class HaZoneTrigger extends LitElement {
|
||||
return {
|
||||
entity_id: "",
|
||||
zone: "",
|
||||
event: "enter",
|
||||
event: "enter" as ZoneTrigger["event"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -54,27 +54,36 @@ export class HaZoneTrigger extends LitElement {
|
||||
allow-custom-entity
|
||||
.includeDomains=${includeDomains}
|
||||
></ha-entity-picker>
|
||||
<label id="eventlabel">
|
||||
|
||||
<label>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.zone.event"
|
||||
)}
|
||||
</label>
|
||||
<paper-radio-group
|
||||
.selected=${event}
|
||||
aria-labelledby="eventlabel"
|
||||
@paper-radio-group-changed=${this._radioGroupPicked}
|
||||
>
|
||||
<paper-radio-button name="enter">
|
||||
${this.hass.localize(
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.zone.enter"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
<paper-radio-button name="leave">
|
||||
${this.hass.localize(
|
||||
>
|
||||
<ha-radio
|
||||
name="event"
|
||||
value="enter"
|
||||
.checked=${event === "enter"}
|
||||
@change=${this._radioGroupPicked}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.zone.leave"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
</paper-radio-group>
|
||||
>
|
||||
<ha-radio
|
||||
name="event"
|
||||
value="leave"
|
||||
.checked=${event === "leave"}
|
||||
@change=${this._radioGroupPicked}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
</label>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -97,10 +106,17 @@ export class HaZoneTrigger extends LitElement {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.trigger,
|
||||
event: (ev.target as PaperRadioGroupElement).selected,
|
||||
event: (ev.target as HaRadio).value,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,7 @@ import "@material/mwc-button";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { formatDateTime } from "../../../../common/datetime/format_date_time";
|
||||
import "../../../../components/dialog/ha-paper-dialog";
|
||||
import type { HaPaperDialog } from "../../../../components/dialog/ha-paper-dialog";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { CloudCertificateParams as CloudCertificateDialogParams } from "./show-dialog-cloud-certificate";
|
||||
@@ -15,11 +14,13 @@ class DialogCloudCertificate extends LitElement {
|
||||
@property()
|
||||
private _params?: CloudCertificateDialogParams;
|
||||
|
||||
public async showDialog(params: CloudCertificateDialogParams) {
|
||||
public showDialog(params: CloudCertificateDialogParams) {
|
||||
this._params = params;
|
||||
// Wait till dialog is rendered.
|
||||
await this.updateComplete;
|
||||
this._dialog.open();
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -29,12 +30,12 @@ class DialogCloudCertificate extends LitElement {
|
||||
const { certificateInfo } = this._params;
|
||||
|
||||
return html`
|
||||
<ha-paper-dialog with-backdrop>
|
||||
<h2>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_certificate.certificate_information"
|
||||
)}
|
||||
</h2>
|
||||
<ha-dialog
|
||||
open
|
||||
.heading=${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_certificate.certificate_information"
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
${this.hass!.localize(
|
||||
@@ -56,31 +57,21 @@ class DialogCloudCertificate extends LitElement {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="paper-dialog-buttons">
|
||||
<mwc-button @click=${this._closeDialog}
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_certificate.close"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>
|
||||
</ha-paper-dialog>
|
||||
<mwc-button @click=${this.closeDialog} slot="primaryAction">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_certificate.close"
|
||||
)}
|
||||
</mwc-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private get _dialog(): HaPaperDialog {
|
||||
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
|
||||
}
|
||||
|
||||
private _closeDialog() {
|
||||
this._dialog.close();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-paper-dialog {
|
||||
width: 535px;
|
||||
ha-dialog {
|
||||
--mdc-dialog-max-width: 535px;
|
||||
}
|
||||
.break-word {
|
||||
overflow-wrap: break-word;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { state } from "lit/decorators";
|
||||
import "../../../../components/dialog/ha-paper-dialog";
|
||||
import type { HaPaperDialog } from "../../../../components/dialog/ha-paper-dialog";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
@@ -19,11 +17,13 @@ export class DialogManageCloudhook extends LitElement {
|
||||
|
||||
@state() private _params?: WebhookDialogParams;
|
||||
|
||||
public async showDialog(params: WebhookDialogParams) {
|
||||
public showDialog(params: WebhookDialogParams) {
|
||||
this._params = params;
|
||||
// Wait till dialog is rendered.
|
||||
await this.updateComplete;
|
||||
this._dialog.open();
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -39,14 +39,14 @@ export class DialogManageCloudhook extends LitElement {
|
||||
)
|
||||
: documentationUrl(this.hass!, `/integrations/${webhook.domain}/`);
|
||||
return html`
|
||||
<ha-paper-dialog with-backdrop>
|
||||
<h2>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_cloudhook.webhook_for",
|
||||
"name",
|
||||
webhook.name
|
||||
)}
|
||||
</h2>
|
||||
<ha-dialog
|
||||
open
|
||||
.heading=${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_cloudhook.webhook_for",
|
||||
"name",
|
||||
webhook.name
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
${this.hass!.localize(
|
||||
@@ -79,36 +79,29 @@ export class DialogManageCloudhook extends LitElement {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="paper-dialog-buttons">
|
||||
<a href=${docsUrl} target="_blank" rel="noreferrer">
|
||||
<mwc-button
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_cloudhook.view_documentation"
|
||||
)}</mwc-button
|
||||
>
|
||||
</a>
|
||||
<mwc-button @click=${this._closeDialog}
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_cloudhook.close"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>
|
||||
</ha-paper-dialog>
|
||||
<a
|
||||
href=${docsUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
slot="secondaryAction"
|
||||
>
|
||||
<mwc-button>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_cloudhook.view_documentation"
|
||||
)}
|
||||
</mwc-button>
|
||||
</a>
|
||||
<mwc-button @click=${this.closeDialog} slot="primaryAction">
|
||||
${this.hass!.localize("ui.panel.config.cloud.dialog_cloudhook.close")}
|
||||
</mwc-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private get _dialog(): HaPaperDialog {
|
||||
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
|
||||
}
|
||||
|
||||
private get _paperInput(): PaperInputElement {
|
||||
return this.shadowRoot!.querySelector("paper-input")!;
|
||||
}
|
||||
|
||||
private _closeDialog() {
|
||||
this._dialog.close();
|
||||
}
|
||||
|
||||
private async _disableWebhook() {
|
||||
showConfirmationDialog(this, {
|
||||
text: this.hass!.localize(
|
||||
@@ -118,7 +111,7 @@ export class DialogManageCloudhook extends LitElement {
|
||||
confirmText: this.hass!.localize("ui.common.disable"),
|
||||
confirm: () => {
|
||||
this._params!.disableHook();
|
||||
this._closeDialog();
|
||||
this.closeDialog();
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -147,7 +140,7 @@ export class DialogManageCloudhook extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-paper-dialog {
|
||||
ha-dialog {
|
||||
width: 650px;
|
||||
}
|
||||
paper-input {
|
||||
@@ -156,7 +149,7 @@ export class DialogManageCloudhook extends LitElement {
|
||||
button.link {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.paper-dialog-buttons a {
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
`,
|
||||
|
||||
@@ -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);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user