mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-05 19:26:33 +00:00
Compare commits
64 Commits
persistent
...
fix-assist
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6573555c1d | ||
![]() |
dccb565a7f | ||
![]() |
1fa95b0673 | ||
![]() |
7727bf7901 | ||
![]() |
24e531a16c | ||
![]() |
32a9b13af0 | ||
![]() |
c90c4d88af | ||
![]() |
cd3bec08f7 | ||
![]() |
8945650b62 | ||
![]() |
5ac9a6c9cc | ||
![]() |
ce9380e4d7 | ||
![]() |
927c6dd778 | ||
![]() |
952bcff8c8 | ||
![]() |
73e1b4b1d1 | ||
![]() |
cbe8be1573 | ||
![]() |
6b4300950d | ||
![]() |
c3c062cc29 | ||
![]() |
b15754a6a7 | ||
![]() |
343708cdaa | ||
![]() |
3b8ea5edbe | ||
![]() |
4761036816 | ||
![]() |
3bb5e95c50 | ||
![]() |
9e5774525f | ||
![]() |
349311a18d | ||
![]() |
48b6c2a925 | ||
![]() |
381c9f97d6 | ||
![]() |
9a116d4022 | ||
![]() |
d63d3a681c | ||
![]() |
3111c29049 | ||
![]() |
87aad75cc7 | ||
![]() |
d656269d75 | ||
![]() |
d169ff6a96 | ||
![]() |
06d9517e27 | ||
![]() |
a637b7db75 | ||
![]() |
96a6261a09 | ||
![]() |
a3f0c428f8 | ||
![]() |
b40a3224fc | ||
![]() |
68fb98454f | ||
![]() |
3803bdc8da | ||
![]() |
1dfd859a2d | ||
![]() |
f77f7b3c36 | ||
![]() |
82463c2ef6 | ||
![]() |
e53ae0b333 | ||
![]() |
b6ed8acd02 | ||
![]() |
897f118547 | ||
![]() |
d961f5be5f | ||
![]() |
96d6687724 | ||
![]() |
a77167e9d9 | ||
![]() |
d2199dfa34 | ||
![]() |
0f0d1d6e6f | ||
![]() |
9bcbb6f914 | ||
![]() |
2929bf5b1a | ||
![]() |
976fcab146 | ||
![]() |
655cf053c7 | ||
![]() |
152ca75499 | ||
![]() |
1645208f62 | ||
![]() |
3528f5c7aa | ||
![]() |
76490cc690 | ||
![]() |
bf18deb83c | ||
![]() |
f19dcba1ce | ||
![]() |
b3fa134198 | ||
![]() |
80c57fa326 | ||
![]() |
b748fee321 | ||
![]() |
1ee67937ec |
@@ -142,4 +142,5 @@ module.exports = {
|
|||||||
createCastConfig,
|
createCastConfig,
|
||||||
createHassioConfig,
|
createHassioConfig,
|
||||||
createGalleryConfig,
|
createGalleryConfig,
|
||||||
|
createRollupConfig,
|
||||||
};
|
};
|
||||||
|
@@ -253,4 +253,5 @@ module.exports = {
|
|||||||
createCastConfig,
|
createCastConfig,
|
||||||
createHassioConfig,
|
createHassioConfig,
|
||||||
createGalleryConfig,
|
createGalleryConfig,
|
||||||
|
createWebpackConfig,
|
||||||
};
|
};
|
||||||
|
@@ -51,6 +51,11 @@ const triggers = [
|
|||||||
{ platform: "tag" },
|
{ platform: "tag" },
|
||||||
{ platform: "time", at: "15:32" },
|
{ platform: "time", at: "15:32" },
|
||||||
{ platform: "template" },
|
{ platform: "template" },
|
||||||
|
{ platform: "conversation", command: "Turn on the lights" },
|
||||||
|
{
|
||||||
|
platform: "conversation",
|
||||||
|
command: ["Turn on the lights", "Turn the lights on"],
|
||||||
|
},
|
||||||
{ platform: "event", event_type: "homeassistant_started" },
|
{ platform: "event", event_type: "homeassistant_started" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -25,6 +25,7 @@ import { HaDeviceTrigger } from "../../../../src/panels/config/automation/trigge
|
|||||||
import { HaStateTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-state";
|
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 { HaMQTTTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt";
|
||||||
import "../../../../src/panels/config/automation/trigger/ha-automation-trigger";
|
import "../../../../src/panels/config/automation/trigger/ha-automation-trigger";
|
||||||
|
import { HaConversationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-conversation";
|
||||||
|
|
||||||
const SCHEMAS: { name: string; triggers: Trigger[] }[] = [
|
const SCHEMAS: { name: string; triggers: Trigger[] }[] = [
|
||||||
{
|
{
|
||||||
@@ -112,6 +113,16 @@ const SCHEMAS: { name: string; triggers: Trigger[] }[] = [
|
|||||||
name: "Device Trigger",
|
name: "Device Trigger",
|
||||||
triggers: [{ platform: "device", ...HaDeviceTrigger.defaultConfig }],
|
triggers: [{ platform: "device", ...HaDeviceTrigger.defaultConfig }],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Sentence",
|
||||||
|
triggers: [
|
||||||
|
{ platform: "conversation", ...HaConversationTrigger.defaultConfig },
|
||||||
|
{
|
||||||
|
platform: "conversation",
|
||||||
|
command: ["Turn on the lights", "Turn the lights on"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@customElement("demo-automation-editor-trigger")
|
@customElement("demo-automation-editor-trigger")
|
||||||
|
@@ -10,23 +10,23 @@ export class DemoHaCircularSlider extends LitElement {
|
|||||||
private current = 22;
|
private current = 22;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private value = 19;
|
private low = 19;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private high = 25;
|
private high = 25;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private changingValue?: number;
|
private changingLow?: number;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private changingHigh?: number;
|
private changingHigh?: number;
|
||||||
|
|
||||||
private _valueChanged(ev) {
|
private _lowChanged(ev) {
|
||||||
this.value = ev.detail.value;
|
this.low = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanging(ev) {
|
private _lowChanging(ev) {
|
||||||
this.changingValue = ev.detail.value;
|
this.changingLow = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _highChanged(ev) {
|
private _highChanged(ev) {
|
||||||
@@ -63,19 +63,40 @@ export class DemoHaCircularSlider extends LitElement {
|
|||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p class="title"><b>Single</b></p>
|
<p class="title"><b>Single</b></p>
|
||||||
<ha-control-circular-slider
|
<ha-control-circular-slider
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._lowChanged}
|
||||||
@value-changing=${this._valueChanging}
|
@value-changing=${this._lowChanging}
|
||||||
.value=${this.value}
|
.value=${this.low}
|
||||||
.current=${this.current}
|
.current=${this.current}
|
||||||
step="1"
|
step="1"
|
||||||
min="10"
|
min="10"
|
||||||
max="30"
|
max="30"
|
||||||
></ha-control-circular-slider>
|
></ha-control-circular-slider>
|
||||||
<div>
|
<div>
|
||||||
Value: ${this.value} °C
|
Low: ${this.low} °C
|
||||||
<br />
|
<br />
|
||||||
Changing:
|
Changing:
|
||||||
${this.changingValue != null ? `${this.changingValue} °C` : "-"}
|
${this.changingLow != null ? `${this.changingLow} °C` : "-"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
<ha-card>
|
||||||
|
<div class="card-content">
|
||||||
|
<p class="title"><b>Inverted</b></p>
|
||||||
|
<ha-control-circular-slider
|
||||||
|
inverted
|
||||||
|
@value-changed=${this._highChanged}
|
||||||
|
@value-changing=${this._highChanging}
|
||||||
|
.value=${this.high}
|
||||||
|
.current=${this.current}
|
||||||
|
step="1"
|
||||||
|
min="10"
|
||||||
|
max="30"
|
||||||
|
></ha-control-circular-slider>
|
||||||
|
<div>
|
||||||
|
High: ${this.high} °C
|
||||||
|
<br />
|
||||||
|
Changing:
|
||||||
|
${this.changingHigh != null ? `${this.changingHigh} °C` : "-"}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
@@ -84,11 +105,11 @@ export class DemoHaCircularSlider extends LitElement {
|
|||||||
<p class="title"><b>Dual</b></p>
|
<p class="title"><b>Dual</b></p>
|
||||||
<ha-control-circular-slider
|
<ha-control-circular-slider
|
||||||
dual
|
dual
|
||||||
@low-changed=${this._valueChanged}
|
@low-changed=${this._lowChanged}
|
||||||
@low-changing=${this._valueChanging}
|
@low-changing=${this._lowChanging}
|
||||||
@high-changed=${this._highChanged}
|
@high-changed=${this._highChanged}
|
||||||
@high-changing=${this._highChanging}
|
@high-changing=${this._highChanging}
|
||||||
.low=${this.value}
|
.low=${this.low}
|
||||||
.high=${this.high}
|
.high=${this.high}
|
||||||
.current=${this.current}
|
.current=${this.current}
|
||||||
step="1"
|
step="1"
|
||||||
@@ -96,10 +117,10 @@ export class DemoHaCircularSlider extends LitElement {
|
|||||||
max="30"
|
max="30"
|
||||||
></ha-control-circular-slider>
|
></ha-control-circular-slider>
|
||||||
<div>
|
<div>
|
||||||
Low value: ${this.value} °C
|
Low value: ${this.low} °C
|
||||||
<br />
|
<br />
|
||||||
Low changing:
|
Low changing:
|
||||||
${this.changingValue != null ? `${this.changingValue} °C` : "-"}
|
${this.changingLow != null ? `${this.changingLow} °C` : "-"}
|
||||||
<br />
|
<br />
|
||||||
High value: ${this.high} °C
|
High value: ${this.high} °C
|
||||||
<br />
|
<br />
|
||||||
@@ -132,6 +153,10 @@ export class DemoHaCircularSlider extends LitElement {
|
|||||||
--control-circular-slider-background: #ff9800;
|
--control-circular-slider-background: #ff9800;
|
||||||
--control-circular-slider-background-opacity: 0.3;
|
--control-circular-slider-background-opacity: 0.3;
|
||||||
}
|
}
|
||||||
|
ha-control-circular-slider[inverted] {
|
||||||
|
--control-circular-slider-color: #2196f3;
|
||||||
|
--control-circular-slider-background: #2196f3;
|
||||||
|
}
|
||||||
ha-control-circular-slider[dual] {
|
ha-control-circular-slider[dual] {
|
||||||
--control-circular-slider-high-color: #2196f3;
|
--control-circular-slider-high-color: #2196f3;
|
||||||
--control-circular-slider-low-color: #ff9800;
|
--control-circular-slider-low-color: #ff9800;
|
||||||
|
@@ -114,11 +114,22 @@ class HassioAddonInfo extends LitElement {
|
|||||||
|
|
||||||
@state() private _error?: string;
|
@state() private _error?: string;
|
||||||
|
|
||||||
|
private _fetchDataTimeout?: number;
|
||||||
|
|
||||||
private _addonStoreInfo = memoizeOne(
|
private _addonStoreInfo = memoizeOne(
|
||||||
(slug: string, storeAddons: StoreAddon[]) =>
|
(slug: string, storeAddons: StoreAddon[]) =>
|
||||||
storeAddons.find((addon) => addon.slug === slug)
|
storeAddons.find((addon) => addon.slug === slug)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
public disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
|
||||||
|
if (this._fetchDataTimeout) {
|
||||||
|
clearInterval(this._fetchDataTimeout);
|
||||||
|
this._fetchDataTimeout = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const addonStoreInfo =
|
const addonStoreInfo =
|
||||||
!this.addon.detached && !this.addon.available
|
!this.addon.detached && !this.addon.available
|
||||||
@@ -592,7 +603,10 @@ class HassioAddonInfo extends LitElement {
|
|||||||
</ha-progress-button>
|
</ha-progress-button>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<ha-progress-button @click=${this._startClicked}>
|
<ha-progress-button
|
||||||
|
@click=${this._startClicked}
|
||||||
|
.progress=${this.addon.state === "startup"}
|
||||||
|
>
|
||||||
${this.supervisor.localize("addon.dashboard.start")}
|
${this.supervisor.localize("addon.dashboard.start")}
|
||||||
</ha-progress-button>
|
</ha-progress-button>
|
||||||
`
|
`
|
||||||
@@ -672,9 +686,36 @@ class HassioAddonInfo extends LitElement {
|
|||||||
super.updated(changedProps);
|
super.updated(changedProps);
|
||||||
if (changedProps.has("addon")) {
|
if (changedProps.has("addon")) {
|
||||||
this._loadData();
|
this._loadData();
|
||||||
|
if (
|
||||||
|
!this._fetchDataTimeout &&
|
||||||
|
this.addon &&
|
||||||
|
"state" in this.addon &&
|
||||||
|
this.addon.state === "startup"
|
||||||
|
) {
|
||||||
|
// Addon is starting up, wait for it to start
|
||||||
|
this._scheduleDataUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _scheduleDataUpdate() {
|
||||||
|
this._fetchDataTimeout = window.setTimeout(async () => {
|
||||||
|
const addon = await fetchHassioAddonInfo(this.hass, this.addon.slug);
|
||||||
|
if (addon.state !== "startup") {
|
||||||
|
this._fetchDataTimeout = undefined;
|
||||||
|
this.addon = addon;
|
||||||
|
const eventdata = {
|
||||||
|
success: true,
|
||||||
|
response: undefined,
|
||||||
|
path: "start",
|
||||||
|
};
|
||||||
|
fireEvent(this, "hass-api-called", eventdata);
|
||||||
|
} else {
|
||||||
|
this._scheduleDataUpdate();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
private async _loadData(): Promise<void> {
|
private async _loadData(): Promise<void> {
|
||||||
if ("state" in this.addon && this.addon.state === "started") {
|
if ("state" in this.addon && this.addon.state === "started") {
|
||||||
this._metrics = await fetchHassioStats(
|
this._metrics = await fetchHassioStats(
|
||||||
|
@@ -16,6 +16,7 @@ import "../../../src/components/ha-icon-button";
|
|||||||
import {
|
import {
|
||||||
fetchHassioAddonInfo,
|
fetchHassioAddonInfo,
|
||||||
HassioAddonDetails,
|
HassioAddonDetails,
|
||||||
|
startHassioAddon,
|
||||||
} from "../../../src/data/hassio/addon";
|
} from "../../../src/data/hassio/addon";
|
||||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||||
import {
|
import {
|
||||||
@@ -23,7 +24,10 @@ import {
|
|||||||
validateHassioSession,
|
validateHassioSession,
|
||||||
} from "../../../src/data/hassio/ingress";
|
} from "../../../src/data/hassio/ingress";
|
||||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
import {
|
||||||
|
showAlertDialog,
|
||||||
|
showConfirmationDialog,
|
||||||
|
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||||
import "../../../src/layouts/hass-loading-screen";
|
import "../../../src/layouts/hass-loading-screen";
|
||||||
import "../../../src/layouts/hass-subpage";
|
import "../../../src/layouts/hass-subpage";
|
||||||
import { HomeAssistant, Route } from "../../../src/types";
|
import { HomeAssistant, Route } from "../../../src/types";
|
||||||
@@ -45,6 +49,8 @@ class HassioIngressView extends LitElement {
|
|||||||
|
|
||||||
private _sessionKeepAlive?: number;
|
private _sessionKeepAlive?: number;
|
||||||
|
|
||||||
|
private _fetchDataTimeout?: number;
|
||||||
|
|
||||||
public disconnectedCallback() {
|
public disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
|
|
||||||
@@ -52,16 +58,21 @@ class HassioIngressView extends LitElement {
|
|||||||
clearInterval(this._sessionKeepAlive);
|
clearInterval(this._sessionKeepAlive);
|
||||||
this._sessionKeepAlive = undefined;
|
this._sessionKeepAlive = undefined;
|
||||||
}
|
}
|
||||||
|
if (this._fetchDataTimeout) {
|
||||||
|
clearInterval(this._fetchDataTimeout);
|
||||||
|
this._fetchDataTimeout = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this._addon) {
|
if (!this._addon) {
|
||||||
return html` <hass-loading-screen></hass-loading-screen> `;
|
return html`<hass-loading-screen></hass-loading-screen>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const iframe = html`<iframe
|
const iframe = html`<iframe
|
||||||
title=${this._addon.name}
|
title=${this._addon.name}
|
||||||
src=${this._addon.ingress_url!}
|
src=${this._addon.ingress_url!}
|
||||||
|
@load=${this._checkLoaded}
|
||||||
>
|
>
|
||||||
</iframe>`;
|
</iframe>`;
|
||||||
|
|
||||||
@@ -132,10 +143,10 @@ class HassioIngressView extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const addon = this.route.path.substr(1);
|
const addon = this.route.path.substring(1);
|
||||||
|
|
||||||
const oldRoute = changedProps.get("route") as this["route"] | undefined;
|
const oldRoute = changedProps.get("route") as this["route"] | undefined;
|
||||||
const oldAddon = oldRoute ? oldRoute.path.substr(1) : undefined;
|
const oldAddon = oldRoute ? oldRoute.path.substring(1) : undefined;
|
||||||
|
|
||||||
if (addon && addon !== oldAddon) {
|
if (addon && addon !== oldAddon) {
|
||||||
this._fetchData(addon);
|
this._fetchData(addon);
|
||||||
@@ -145,33 +156,23 @@ class HassioIngressView extends LitElement {
|
|||||||
private async _fetchData(addonSlug: string) {
|
private async _fetchData(addonSlug: string) {
|
||||||
const createSessionPromise = createHassioSession(this.hass);
|
const createSessionPromise = createHassioSession(this.hass);
|
||||||
|
|
||||||
let addon;
|
let addon: HassioAddonDetails;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
addon = await fetchHassioAddonInfo(this.hass, addonSlug);
|
addon = await fetchHassioAddonInfo(this.hass, addonSlug);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
await showAlertDialog(this, {
|
await showAlertDialog(this, {
|
||||||
text: "Unable to fetch add-on info to start Ingress",
|
text: this.supervisor.localize("ingress.error_addon_info"),
|
||||||
title: "Supervisor",
|
title: "Supervisor",
|
||||||
});
|
});
|
||||||
await nextRender();
|
await nextRender();
|
||||||
history.back();
|
navigate("/hassio/store", { replace: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!addon.ingress_url) {
|
if (!addon.version) {
|
||||||
await showAlertDialog(this, {
|
await showAlertDialog(this, {
|
||||||
text: "Add-on does not support Ingress",
|
text: this.supervisor.localize("ingress.error_addon_not_installed"),
|
||||||
title: addon.name,
|
|
||||||
});
|
|
||||||
await nextRender();
|
|
||||||
history.back();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addon.state !== "started") {
|
|
||||||
await showAlertDialog(this, {
|
|
||||||
text: "Add-on is not running. Please start it first",
|
|
||||||
title: addon.name,
|
title: addon.name,
|
||||||
});
|
});
|
||||||
await nextRender();
|
await nextRender();
|
||||||
@@ -179,13 +180,74 @@ class HassioIngressView extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let session;
|
if (!addon.ingress_url) {
|
||||||
|
await showAlertDialog(this, {
|
||||||
|
text: this.supervisor.localize("ingress.error_addon_not_supported"),
|
||||||
|
title: addon.name,
|
||||||
|
});
|
||||||
|
await nextRender();
|
||||||
|
history.back();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!addon.state || !["startup", "started"].includes(addon.state)) {
|
||||||
|
const confirm = await showConfirmationDialog(this, {
|
||||||
|
text: this.supervisor.localize("ingress.error_addon_not_running"),
|
||||||
|
title: addon.name,
|
||||||
|
confirmText: this.supervisor.localize("ingress.start_addon"),
|
||||||
|
dismissText: this.supervisor.localize("common.no"),
|
||||||
|
});
|
||||||
|
if (confirm) {
|
||||||
|
try {
|
||||||
|
await startHassioAddon(this.hass, addonSlug);
|
||||||
|
fireEvent(this, "supervisor-collection-refresh", {
|
||||||
|
collection: "addon",
|
||||||
|
});
|
||||||
|
this._fetchData(addonSlug);
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
await showAlertDialog(this, {
|
||||||
|
text: this.supervisor.localize("ingress.error_starting_addon"),
|
||||||
|
title: addon.name,
|
||||||
|
});
|
||||||
|
await nextRender();
|
||||||
|
navigate(`/hassio/addon/${addon.slug}/logs`, { replace: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await nextRender();
|
||||||
|
navigate(`/hassio/addon/${addon.slug}/info`, { replace: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addon.state === "startup") {
|
||||||
|
// Addon is starting up, wait for it to start
|
||||||
|
this._fetchDataTimeout = window.setTimeout(() => {
|
||||||
|
this._fetchData(addonSlug);
|
||||||
|
}, 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addon.state !== "started") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._fetchDataTimeout) {
|
||||||
|
clearInterval(this._fetchDataTimeout);
|
||||||
|
this._fetchDataTimeout = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let session: string;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
session = await createSessionPromise;
|
session = await createSessionPromise;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
if (this._sessionKeepAlive) {
|
||||||
|
clearInterval(this._sessionKeepAlive);
|
||||||
|
}
|
||||||
await showAlertDialog(this, {
|
await showAlertDialog(this, {
|
||||||
text: "Unable to create an Ingress session",
|
text: this.supervisor.localize("ingress.error_creating_session"),
|
||||||
title: addon.name,
|
title: addon.name,
|
||||||
});
|
});
|
||||||
await nextRender();
|
await nextRender();
|
||||||
@@ -207,6 +269,31 @@ class HassioIngressView extends LitElement {
|
|||||||
this._addon = addon;
|
this._addon = addon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _checkLoaded(ev): void {
|
||||||
|
if (!this._addon) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ev.target.contentDocument.body.textContent === "502: Bad Gateway") {
|
||||||
|
showConfirmationDialog(this, {
|
||||||
|
text: this.supervisor.localize("ingress.error_addon_not_ready"),
|
||||||
|
title: this._addon.name,
|
||||||
|
confirmText: this.supervisor.localize("ingress.retry"),
|
||||||
|
dismissText: this.supervisor.localize("common.no"),
|
||||||
|
confirm: async () => {
|
||||||
|
const addon = this._addon;
|
||||||
|
this._addon = undefined;
|
||||||
|
await Promise.all([
|
||||||
|
this.updateComplete,
|
||||||
|
new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 500);
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
this._addon = addon;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _toggleMenu(): void {
|
private _toggleMenu(): void {
|
||||||
fireEvent(this, "hass-toggle-menu");
|
fireEvent(this, "hass-toggle-menu");
|
||||||
}
|
}
|
||||||
|
22
package.json
22
package.json
@@ -27,13 +27,13 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.22.5",
|
"@babel/runtime": "7.22.5",
|
||||||
"@braintree/sanitize-url": "6.0.2",
|
"@braintree/sanitize-url": "6.0.2",
|
||||||
"@codemirror/autocomplete": "6.8.0",
|
"@codemirror/autocomplete": "6.8.1",
|
||||||
"@codemirror/commands": "6.2.4",
|
"@codemirror/commands": "6.2.4",
|
||||||
"@codemirror/language": "6.8.0",
|
"@codemirror/language": "6.8.0",
|
||||||
"@codemirror/legacy-modes": "6.3.2",
|
"@codemirror/legacy-modes": "6.3.2",
|
||||||
"@codemirror/search": "6.5.0",
|
"@codemirror/search": "6.5.0",
|
||||||
"@codemirror/state": "6.2.1",
|
"@codemirror/state": "6.2.1",
|
||||||
"@codemirror/view": "6.13.2",
|
"@codemirror/view": "6.14.0",
|
||||||
"@egjs/hammerjs": "2.0.17",
|
"@egjs/hammerjs": "2.0.17",
|
||||||
"@formatjs/intl-datetimeformat": "6.10.0",
|
"@formatjs/intl-datetimeformat": "6.10.0",
|
||||||
"@formatjs/intl-displaynames": "6.5.0",
|
"@formatjs/intl-displaynames": "6.5.0",
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
"@lit-labs/context": "0.3.3",
|
"@lit-labs/context": "0.3.3",
|
||||||
"@lit-labs/motion": "1.0.3",
|
"@lit-labs/motion": "1.0.3",
|
||||||
"@lit-labs/virtualizer": "2.0.3",
|
"@lit-labs/virtualizer": "2.0.3",
|
||||||
"@lrnwebcomponents/simple-tooltip": "7.0.2",
|
"@lrnwebcomponents/simple-tooltip": "7.0.5",
|
||||||
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/mwc-button": "0.27.0",
|
"@material/mwc-button": "0.27.0",
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
"@material/mwc-top-app-bar": "0.27.0",
|
"@material/mwc-top-app-bar": "0.27.0",
|
||||||
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
||||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/web": "=1.0.0-pre.10",
|
"@material/web": "=1.0.0-pre.11",
|
||||||
"@mdi/js": "7.2.96",
|
"@mdi/js": "7.2.96",
|
||||||
"@mdi/svg": "7.2.96",
|
"@mdi/svg": "7.2.96",
|
||||||
"@polymer/app-layout": "3.1.0",
|
"@polymer/app-layout": "3.1.0",
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
"fuse.js": "6.6.2",
|
"fuse.js": "6.6.2",
|
||||||
"google-timezones-json": "1.1.0",
|
"google-timezones-json": "1.1.0",
|
||||||
"hls.js": "1.4.6",
|
"hls.js": "1.4.6",
|
||||||
"home-assistant-js-websocket": "8.0.1",
|
"home-assistant-js-websocket": "8.1.0",
|
||||||
"idb-keyval": "6.2.1",
|
"idb-keyval": "6.2.1",
|
||||||
"intl-messageformat": "10.5.0",
|
"intl-messageformat": "10.5.0",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
"@octokit/rest": "19.0.13",
|
"@octokit/rest": "19.0.13",
|
||||||
"@open-wc/dev-server-hmr": "0.1.4",
|
"@open-wc/dev-server-hmr": "0.1.4",
|
||||||
"@rollup/plugin-babel": "6.0.3",
|
"@rollup/plugin-babel": "6.0.3",
|
||||||
"@rollup/plugin-commonjs": "25.0.1",
|
"@rollup/plugin-commonjs": "25.0.2",
|
||||||
"@rollup/plugin-json": "6.0.0",
|
"@rollup/plugin-json": "6.0.0",
|
||||||
"@rollup/plugin-node-resolve": "15.1.0",
|
"@rollup/plugin-node-resolve": "15.1.0",
|
||||||
"@rollup/plugin-replace": "5.0.2",
|
"@rollup/plugin-replace": "5.0.2",
|
||||||
@@ -181,8 +181,8 @@
|
|||||||
"@types/sortablejs": "1.15.1",
|
"@types/sortablejs": "1.15.1",
|
||||||
"@types/tar": "6.1.5",
|
"@types/tar": "6.1.5",
|
||||||
"@types/webspeechapi": "0.0.29",
|
"@types/webspeechapi": "0.0.29",
|
||||||
"@typescript-eslint/eslint-plugin": "5.59.11",
|
"@typescript-eslint/eslint-plugin": "5.60.0",
|
||||||
"@typescript-eslint/parser": "5.59.11",
|
"@typescript-eslint/parser": "5.60.0",
|
||||||
"@web/dev-server": "0.1.38",
|
"@web/dev-server": "0.1.38",
|
||||||
"@web/dev-server-rollup": "0.4.1",
|
"@web/dev-server-rollup": "0.4.1",
|
||||||
"babel-loader": "9.1.2",
|
"babel-loader": "9.1.2",
|
||||||
@@ -203,7 +203,7 @@
|
|||||||
"esprima": "4.0.1",
|
"esprima": "4.0.1",
|
||||||
"fancy-log": "2.0.0",
|
"fancy-log": "2.0.0",
|
||||||
"fs-extra": "11.1.1",
|
"fs-extra": "11.1.1",
|
||||||
"glob": "10.2.7",
|
"glob": "10.3.0",
|
||||||
"gulp": "4.0.2",
|
"gulp": "4.0.2",
|
||||||
"gulp-flatmap": "1.0.2",
|
"gulp-flatmap": "1.0.2",
|
||||||
"gulp-json-transform": "0.4.8",
|
"gulp-json-transform": "0.4.8",
|
||||||
@@ -230,7 +230,7 @@
|
|||||||
"rollup-plugin-terser": "7.0.2",
|
"rollup-plugin-terser": "7.0.2",
|
||||||
"rollup-plugin-visualizer": "5.9.2",
|
"rollup-plugin-visualizer": "5.9.2",
|
||||||
"serve-handler": "6.1.5",
|
"serve-handler": "6.1.5",
|
||||||
"sinon": "15.1.2",
|
"sinon": "15.2.0",
|
||||||
"source-map-url": "0.4.1",
|
"source-map-url": "0.4.1",
|
||||||
"systemjs": "6.14.1",
|
"systemjs": "6.14.1",
|
||||||
"tar": "6.1.15",
|
"tar": "6.1.15",
|
||||||
@@ -239,7 +239,7 @@
|
|||||||
"typescript": "5.1.3",
|
"typescript": "5.1.3",
|
||||||
"vinyl-buffer": "1.0.1",
|
"vinyl-buffer": "1.0.1",
|
||||||
"vinyl-source-stream": "2.0.0",
|
"vinyl-source-stream": "2.0.0",
|
||||||
"webpack": "5.87.0",
|
"webpack": "5.88.0",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"webpack-dev-server": "4.15.1",
|
"webpack-dev-server": "4.15.1",
|
||||||
"webpack-manifest-plugin": "5.0.0",
|
"webpack-manifest-plugin": "5.0.0",
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools~=62.3", "wheel~=0.37.1"]
|
requires = ["setuptools~=68.0", "wheel~=0.40.0"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
|
@@ -1,2 +0,0 @@
|
|||||||
# Setuptools v62.3 doesn't support editable installs with just 'pyproject.toml' (PEP 660).
|
|
||||||
# Keep this file until it does!
|
|
@@ -169,12 +169,6 @@ export const computeStateDisplayFromEntityAttributes = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (domain === "humidifier") {
|
|
||||||
if (state === "on" && attributes.humidity) {
|
|
||||||
return `${attributes.humidity} %`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// `counter` `number` and `input_number` domains do not have a unit of measurement but should still use `formatNumber`
|
// `counter` `number` and `input_number` domains do not have a unit of measurement but should still use `formatNumber`
|
||||||
if (
|
if (
|
||||||
domain === "counter" ||
|
domain === "counter" ||
|
||||||
|
@@ -15,6 +15,7 @@ import {
|
|||||||
mdiCheckCircleOutline,
|
mdiCheckCircleOutline,
|
||||||
mdiClock,
|
mdiClock,
|
||||||
mdiCloseCircleOutline,
|
mdiCloseCircleOutline,
|
||||||
|
mdiCrosshairsQuestion,
|
||||||
mdiFan,
|
mdiFan,
|
||||||
mdiFanOff,
|
mdiFanOff,
|
||||||
mdiGestureTapButton,
|
mdiGestureTapButton,
|
||||||
@@ -31,6 +32,7 @@ import {
|
|||||||
mdiPowerPlugOff,
|
mdiPowerPlugOff,
|
||||||
mdiRestart,
|
mdiRestart,
|
||||||
mdiRobot,
|
mdiRobot,
|
||||||
|
mdiRobotConfused,
|
||||||
mdiRobotOff,
|
mdiRobotOff,
|
||||||
mdiSpeaker,
|
mdiSpeaker,
|
||||||
mdiSpeakerOff,
|
mdiSpeakerOff,
|
||||||
@@ -91,13 +93,19 @@ export const domainIconWithoutDefault = (
|
|||||||
return alarmPanelIcon(compareState);
|
return alarmPanelIcon(compareState);
|
||||||
|
|
||||||
case "automation":
|
case "automation":
|
||||||
return compareState === "off" ? mdiRobotOff : mdiRobot;
|
return compareState === "unavailable"
|
||||||
|
? mdiRobotConfused
|
||||||
|
: compareState === "off"
|
||||||
|
? mdiRobotOff
|
||||||
|
: mdiRobot;
|
||||||
|
|
||||||
case "binary_sensor":
|
case "binary_sensor":
|
||||||
return binarySensorIcon(compareState, stateObj);
|
return binarySensorIcon(compareState, stateObj);
|
||||||
|
|
||||||
case "button":
|
case "button":
|
||||||
switch (stateObj?.attributes.device_class) {
|
switch (stateObj?.attributes.device_class) {
|
||||||
|
case "identify":
|
||||||
|
return mdiCrosshairsQuestion;
|
||||||
case "restart":
|
case "restart":
|
||||||
return mdiRestart;
|
return mdiRestart;
|
||||||
case "update":
|
case "update":
|
||||||
|
@@ -30,6 +30,7 @@ export const FIXED_DOMAIN_STATES = {
|
|||||||
lock: ["jammed", "locked", "locking", "unlocked", "unlocking"],
|
lock: ["jammed", "locked", "locking", "unlocked", "unlocking"],
|
||||||
media_player: ["idle", "off", "paused", "playing", "standby"],
|
media_player: ["idle", "off", "paused", "playing", "standby"],
|
||||||
person: ["home", "not_home"],
|
person: ["home", "not_home"],
|
||||||
|
plant: ["ok", "problem"],
|
||||||
remote: ["on", "off"],
|
remote: ["on", "off"],
|
||||||
scene: [],
|
scene: [],
|
||||||
schedule: ["on", "off"],
|
schedule: ["on", "off"],
|
||||||
|
@@ -110,3 +110,15 @@ export const stateColorProperties = (
|
|||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const stateColorBrightness = (stateObj: HassEntity): string => {
|
||||||
|
if (
|
||||||
|
stateObj.attributes.brightness &&
|
||||||
|
computeDomain(stateObj.entity_id) !== "plant"
|
||||||
|
) {
|
||||||
|
// lowest brightness will be around 50% (that's pretty dark)
|
||||||
|
const brightness = stateObj.attributes.brightness;
|
||||||
|
return `brightness(${(brightness + 245) / 5}%)`;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
@@ -17,12 +17,13 @@ export const stripPrefixFromEntityName = (
|
|||||||
|
|
||||||
if (lowerCasedEntityName.startsWith(lowerCasedPrefixWithSuffix)) {
|
if (lowerCasedEntityName.startsWith(lowerCasedPrefixWithSuffix)) {
|
||||||
const newName = entityName.substring(lowerCasedPrefixWithSuffix.length);
|
const newName = entityName.substring(lowerCasedPrefixWithSuffix.length);
|
||||||
|
if (newName.length) {
|
||||||
// If first word already has an upper case letter (e.g. from brand name)
|
// If first word already has an upper case letter (e.g. from brand name)
|
||||||
// leave as-is, otherwise capitalize the first word.
|
// leave as-is, otherwise capitalize the first word.
|
||||||
return hasUpperCase(newName.substr(0, newName.indexOf(" ")))
|
return hasUpperCase(newName.substr(0, newName.indexOf(" ")))
|
||||||
? newName
|
? newName
|
||||||
: newName[0].toUpperCase() + newName.slice(1);
|
: newName[0].toUpperCase() + newName.slice(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
19
src/common/translations/auto_case_noun.ts
Normal file
19
src/common/translations/auto_case_noun.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// In a few languages nouns are always capitalized. This helper
|
||||||
|
// indicates if for a given language that is the case.
|
||||||
|
|
||||||
|
import { capitalizeFirstLetter } from "../string/capitalize-first-letter";
|
||||||
|
|
||||||
|
export const useCapitalizedNouns = (language: string): boolean => {
|
||||||
|
switch (language) {
|
||||||
|
case "de":
|
||||||
|
case "lb":
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const autoCaseNoun = (noun: string, language: string): string =>
|
||||||
|
useCapitalizedNouns(language)
|
||||||
|
? capitalizeFirstLetter(noun)
|
||||||
|
: noun.toLocaleLowerCase(language);
|
@@ -166,7 +166,7 @@ class StatisticsChart extends LitElement {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
beginAtZero: false,
|
beginAtZero: this.chartType === "bar",
|
||||||
ticks: {
|
ticks: {
|
||||||
maxTicksLimit: 7,
|
maxTicksLimit: 7,
|
||||||
},
|
},
|
||||||
|
@@ -349,6 +349,7 @@ export class HaDataTable extends LitElement {
|
|||||||
class="mdc-data-table__content scroller ha-scrollbar"
|
class="mdc-data-table__content scroller ha-scrollbar"
|
||||||
@scroll=${this._saveScrollPos}
|
@scroll=${this._saveScrollPos}
|
||||||
.items=${this._items}
|
.items=${this._items}
|
||||||
|
.keyFunction=${this._keyFunction}
|
||||||
.renderItem=${this._renderRow}
|
.renderItem=${this._renderRow}
|
||||||
></lit-virtualizer>
|
></lit-virtualizer>
|
||||||
`}
|
`}
|
||||||
@@ -357,6 +358,8 @@ export class HaDataTable extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _keyFunction = (row: DataTableRowData) => row[this.id] || row;
|
||||||
|
|
||||||
private _renderRow = (row: DataTableRowData, index: number) => {
|
private _renderRow = (row: DataTableRowData, index: number) => {
|
||||||
// not sure how this happens...
|
// not sure how this happens...
|
||||||
if (!row) {
|
if (!row) {
|
||||||
|
@@ -13,7 +13,10 @@ import { ifDefined } from "lit/directives/if-defined";
|
|||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { computeDomain } from "../../common/entity/compute_domain";
|
import { computeDomain } from "../../common/entity/compute_domain";
|
||||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||||
import { stateColorCss } from "../../common/entity/state_color";
|
import {
|
||||||
|
stateColorCss,
|
||||||
|
stateColorBrightness,
|
||||||
|
} from "../../common/entity/state_color";
|
||||||
import { iconColorCSS } from "../../common/style/icon_color_css";
|
import { iconColorCSS } from "../../common/style/icon_color_css";
|
||||||
import { cameraUrlWithWidthHeight } from "../../data/camera";
|
import { cameraUrlWithWidthHeight } from "../../data/camera";
|
||||||
import { HVAC_ACTION_TO_MODE } from "../../data/climate";
|
import { HVAC_ACTION_TO_MODE } from "../../data/climate";
|
||||||
@@ -153,8 +156,7 @@ export class StateBadge extends LitElement {
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
console.warn(errorMessage);
|
console.warn(errorMessage);
|
||||||
}
|
}
|
||||||
// lowest brightness will be around 50% (that's pretty dark)
|
iconStyle.filter = stateColorBrightness(stateObj);
|
||||||
iconStyle.filter = `brightness(${(brightness + 245) / 5}%)`;
|
|
||||||
}
|
}
|
||||||
if (stateObj.attributes.hvac_action) {
|
if (stateObj.attributes.hvac_action) {
|
||||||
const hvacAction = stateObj.attributes.hvac_action;
|
const hvacAction = stateObj.attributes.hvac_action;
|
||||||
|
@@ -68,6 +68,9 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
public dual?: boolean;
|
public dual?: boolean;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true })
|
||||||
|
public inverted?: boolean;
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
public label?: string;
|
public label?: string;
|
||||||
|
|
||||||
@@ -80,15 +83,15 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
@property({ type: Number })
|
@property({ type: Number })
|
||||||
public value?: number;
|
public value?: number;
|
||||||
|
|
||||||
@property({ type: Number })
|
|
||||||
public current?: number;
|
|
||||||
|
|
||||||
@property({ type: Number })
|
@property({ type: Number })
|
||||||
public low?: number;
|
public low?: number;
|
||||||
|
|
||||||
@property({ type: Number })
|
@property({ type: Number })
|
||||||
public high?: number;
|
public high?: number;
|
||||||
|
|
||||||
|
@property({ type: Number })
|
||||||
|
public current?: number;
|
||||||
|
|
||||||
@property({ type: Number })
|
@property({ type: Number })
|
||||||
public step = 1;
|
public step = 1;
|
||||||
|
|
||||||
@@ -98,6 +101,15 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
@property({ type: Number })
|
@property({ type: Number })
|
||||||
public max = 100;
|
public max = 100;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
public _localValue?: number = this.value;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
public _localLow?: number = this.low;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
public _localHigh?: number = this.high;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
public _activeSlider?: ActiveSlider;
|
public _activeSlider?: ActiveSlider;
|
||||||
|
|
||||||
@@ -120,17 +132,36 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
|
|
||||||
private _boundedValue(value: number) {
|
private _boundedValue(value: number) {
|
||||||
const min =
|
const min =
|
||||||
this._activeSlider === "high" ? Math.min(this.low ?? this.max) : this.min;
|
this._activeSlider === "high"
|
||||||
|
? Math.min(this._localLow ?? this.max)
|
||||||
|
: this.min;
|
||||||
const max =
|
const max =
|
||||||
this._activeSlider === "low" ? Math.max(this.high ?? this.min) : this.max;
|
this._activeSlider === "low"
|
||||||
|
? Math.max(this._localHigh ?? this.min)
|
||||||
|
: this.max;
|
||||||
return Math.min(Math.max(value, min), max);
|
return Math.min(Math.max(value, min), max);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
protected firstUpdated(changedProps: PropertyValues): void {
|
||||||
super.firstUpdated(changedProperties);
|
super.firstUpdated(changedProps);
|
||||||
this._setupListeners();
|
this._setupListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues): void {
|
||||||
|
super.updated(changedProps);
|
||||||
|
if (!this._activeSlider) {
|
||||||
|
if (changedProps.has("value")) {
|
||||||
|
this._localValue = this.value;
|
||||||
|
}
|
||||||
|
if (changedProps.has("low")) {
|
||||||
|
this._localLow = this.low;
|
||||||
|
}
|
||||||
|
if (changedProps.has("high")) {
|
||||||
|
this._localHigh = this.high;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
connectedCallback(): void {
|
connectedCallback(): void {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this._setupListeners();
|
this._setupListeners();
|
||||||
@@ -164,8 +195,8 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
|
|
||||||
private _findActiveSlider(value: number): ActiveSlider {
|
private _findActiveSlider(value: number): ActiveSlider {
|
||||||
if (!this.dual) return "value";
|
if (!this.dual) return "value";
|
||||||
const low = Math.max(this.low ?? this.min, this.min);
|
const low = Math.max(this._localLow ?? this.min, this.min);
|
||||||
const high = Math.min(this.high ?? this.max, this.max);
|
const high = Math.min(this._localHigh ?? this.max, this.max);
|
||||||
if (low >= value) {
|
if (low >= value) {
|
||||||
return "low";
|
return "low";
|
||||||
}
|
}
|
||||||
@@ -178,13 +209,29 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _setActiveValue(value: number) {
|
private _setActiveValue(value: number) {
|
||||||
if (!this._activeSlider) return;
|
switch (this._activeSlider) {
|
||||||
this[this._activeSlider] = value;
|
case "high":
|
||||||
|
this._localHigh = value;
|
||||||
|
break;
|
||||||
|
case "low":
|
||||||
|
this._localLow = value;
|
||||||
|
break;
|
||||||
|
case "value":
|
||||||
|
this._localValue = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getActiveValue(): number | undefined {
|
private _getActiveValue(): number | undefined {
|
||||||
if (!this._activeSlider) return undefined;
|
switch (this._activeSlider) {
|
||||||
return this[this._activeSlider];
|
case "high":
|
||||||
|
return this._localHigh;
|
||||||
|
case "low":
|
||||||
|
return this._localLow;
|
||||||
|
case "value":
|
||||||
|
return this._localValue;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
_setupListeners() {
|
_setupListeners() {
|
||||||
@@ -235,6 +282,7 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
const raw = this._percentageToValue(percentage);
|
const raw = this._percentageToValue(percentage);
|
||||||
const bounded = this._boundedValue(raw);
|
const bounded = this._boundedValue(raw);
|
||||||
const stepped = this._steppedValue(bounded);
|
const stepped = this._steppedValue(bounded);
|
||||||
|
this._setActiveValue(stepped);
|
||||||
if (this._activeSlider) {
|
if (this._activeSlider) {
|
||||||
fireEvent(this, `${this._activeSlider}-changing`, {
|
fireEvent(this, `${this._activeSlider}-changing`, {
|
||||||
value: undefined,
|
value: undefined,
|
||||||
@@ -340,23 +388,41 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _strokeDashArc(
|
||||||
|
percentage: number,
|
||||||
|
inverted?: boolean
|
||||||
|
): [string, string] {
|
||||||
|
const maxRatio = MAX_ANGLE / 360;
|
||||||
|
const f = RADIUS * 2 * Math.PI;
|
||||||
|
if (inverted) {
|
||||||
|
const arcLength = (1 - percentage) * f * maxRatio;
|
||||||
|
const strokeDasharray = `${arcLength} ${f - arcLength}`;
|
||||||
|
const strokeDashOffset = `${arcLength + f * (1 - maxRatio)}`;
|
||||||
|
return [strokeDasharray, strokeDashOffset];
|
||||||
|
}
|
||||||
|
const arcLength = percentage * f * maxRatio;
|
||||||
|
const strokeDasharray = `${arcLength} ${f - arcLength}`;
|
||||||
|
const strokeDashOffset = "0";
|
||||||
|
return [strokeDasharray, strokeDashOffset];
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const trackPath = arc({ x: 0, y: 0, start: 0, end: MAX_ANGLE, r: RADIUS });
|
const trackPath = arc({ x: 0, y: 0, start: 0, end: MAX_ANGLE, r: RADIUS });
|
||||||
|
|
||||||
const maxRatio = MAX_ANGLE / 360;
|
const lowValue = this.dual ? this._localLow : this._localValue;
|
||||||
|
const highValue = this._localHigh;
|
||||||
const f = RADIUS * 2 * Math.PI;
|
|
||||||
const lowValue = this.dual ? this.low : this.value;
|
|
||||||
const highValue = this.high;
|
|
||||||
const lowPercentage = this._valueToPercentage(lowValue ?? this.min);
|
const lowPercentage = this._valueToPercentage(lowValue ?? this.min);
|
||||||
const highPercentage = this._valueToPercentage(highValue ?? this.max);
|
const highPercentage = this._valueToPercentage(highValue ?? this.max);
|
||||||
|
|
||||||
const lowArcLength = lowPercentage * f * maxRatio;
|
const [lowStrokeDasharray, lowStrokeDashOffset] = this._strokeDashArc(
|
||||||
const lowStrokeDasharray = `${lowArcLength} ${f - lowArcLength}`;
|
lowPercentage,
|
||||||
|
this.inverted
|
||||||
|
);
|
||||||
|
|
||||||
const highArcLength = (1 - highPercentage) * f * maxRatio;
|
const [highStrokeDasharray, highStrokeDashOffset] = this._strokeDashArc(
|
||||||
const highStrokeDasharray = `${highArcLength} ${f - highArcLength}`;
|
highPercentage,
|
||||||
const highStrokeDashOffset = `${highArcLength + f * (1 - maxRatio)}`;
|
true
|
||||||
|
);
|
||||||
|
|
||||||
const currentPercentage = this._valueToPercentage(this.current ?? 0);
|
const currentPercentage = this._valueToPercentage(this.current ?? 0);
|
||||||
const currentAngle = currentPercentage * MAX_ANGLE;
|
const currentAngle = currentPercentage * MAX_ANGLE;
|
||||||
@@ -381,27 +447,31 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
</g>
|
</g>
|
||||||
<g id="display">
|
<g id="display">
|
||||||
<path class="background" d=${trackPath} />
|
<path class="background" d=${trackPath} />
|
||||||
<circle
|
${lowValue != null
|
||||||
.id=${this.dual ? "low" : "value"}
|
? svg`
|
||||||
class="track"
|
<circle
|
||||||
cx="0"
|
.id=${this.dual ? "low" : "value"}
|
||||||
cy="0"
|
class="track"
|
||||||
r=${RADIUS}
|
cx="0"
|
||||||
stroke-dasharray=${lowStrokeDasharray}
|
cy="0"
|
||||||
stroke-dashoffset="0"
|
r=${RADIUS}
|
||||||
role="slider"
|
stroke-dasharray=${lowStrokeDasharray}
|
||||||
tabindex="0"
|
stroke-dashoffset=${lowStrokeDashOffset}
|
||||||
aria-valuemin=${this.min}
|
role="slider"
|
||||||
aria-valuemax=${this.max}
|
tabindex="0"
|
||||||
aria-valuenow=${lowValue != null
|
aria-valuemin=${this.min}
|
||||||
? this._steppedValue(lowValue)
|
aria-valuemax=${this.max}
|
||||||
: undefined}
|
aria-valuenow=${
|
||||||
aria-disabled=${this.disabled}
|
lowValue != null ? this._steppedValue(lowValue) : undefined
|
||||||
aria-label=${ifDefined(this.lowLabel ?? this.label)}
|
}
|
||||||
@keydown=${this._handleKeyDown}
|
aria-disabled=${this.disabled}
|
||||||
@keyup=${this._handleKeyUp}
|
aria-label=${ifDefined(this.lowLabel ?? this.label)}
|
||||||
/>
|
@keydown=${this._handleKeyDown}
|
||||||
${this.dual
|
@keyup=${this._handleKeyUp}
|
||||||
|
/>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
${this.dual && highValue != null
|
||||||
? svg`
|
? svg`
|
||||||
<circle
|
<circle
|
||||||
id="high"
|
id="high"
|
||||||
@@ -496,6 +566,7 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
fill: none;
|
fill: none;
|
||||||
stroke: var(--control-circular-slider-background);
|
stroke: var(--control-circular-slider-background);
|
||||||
opacity: var(--control-circular-slider-background-opacity);
|
opacity: var(--control-circular-slider-background-opacity);
|
||||||
|
transition: stroke 180ms ease-in-out, opacity 180ms ease-in-out;
|
||||||
stroke-linecap: round;
|
stroke-linecap: round;
|
||||||
stroke-width: 24px;
|
stroke-width: 24px;
|
||||||
}
|
}
|
||||||
@@ -507,7 +578,8 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
stroke-width: 24px;
|
stroke-width: 24px;
|
||||||
transition: stroke-width 300ms ease-in-out,
|
transition: stroke-width 300ms ease-in-out,
|
||||||
stroke-dasharray 300ms ease-in-out,
|
stroke-dasharray 300ms ease-in-out,
|
||||||
stroke-dashoffset 300ms ease-in-out;
|
stroke-dashoffset 300ms ease-in-out, stroke 180ms ease-in-out,
|
||||||
|
opacity 180ms ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.track:focus-visible {
|
.track:focus-visible {
|
||||||
|
@@ -34,6 +34,8 @@ const getValue = (obj, item) =>
|
|||||||
|
|
||||||
const getError = (obj, item) => (obj && item.name ? obj[item.name] : null);
|
const getError = (obj, item) => (obj && item.name ? obj[item.name] : null);
|
||||||
|
|
||||||
|
const getWarning = (obj, item) => (obj && item.name ? obj[item.name] : null);
|
||||||
|
|
||||||
@customElement("ha-form")
|
@customElement("ha-form")
|
||||||
export class HaForm extends LitElement implements HaFormElement {
|
export class HaForm extends LitElement implements HaFormElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@@ -44,10 +46,14 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
@property() public error?: Record<string, string>;
|
@property() public error?: Record<string, string>;
|
||||||
|
|
||||||
|
@property() public warning?: Record<string, string>;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property() public computeError?: (schema: any, error) => string;
|
@property() public computeError?: (schema: any, error) => string;
|
||||||
|
|
||||||
|
@property() public computeWarning?: (schema: any, warning) => string;
|
||||||
|
|
||||||
@property() public computeLabel?: (
|
@property() public computeLabel?: (
|
||||||
schema: any,
|
schema: any,
|
||||||
data: HaFormDataContainer
|
data: HaFormDataContainer
|
||||||
@@ -98,6 +104,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${this.schema.map((item) => {
|
${this.schema.map((item) => {
|
||||||
const error = getError(this.error, item);
|
const error = getError(this.error, item);
|
||||||
|
const warning = getWarning(this.warning, item);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${error
|
${error
|
||||||
@@ -106,6 +113,12 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
${this._computeError(error, item)}
|
${this._computeError(error, item)}
|
||||||
</ha-alert>
|
</ha-alert>
|
||||||
`
|
`
|
||||||
|
: warning
|
||||||
|
? html`
|
||||||
|
<ha-alert own-margin alert-type="warning">
|
||||||
|
${this._computeWarning(warning, item)}
|
||||||
|
</ha-alert>
|
||||||
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${"selector" in item
|
${"selector" in item
|
||||||
? html`<ha-selector
|
? html`<ha-selector
|
||||||
@@ -187,6 +200,13 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
return this.computeError ? this.computeError(error, schema) : error;
|
return this.computeError ? this.computeError(error, schema) : error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _computeWarning(
|
||||||
|
warning,
|
||||||
|
schema: HaFormSchema | readonly HaFormSchema[]
|
||||||
|
) {
|
||||||
|
return this.computeWarning ? this.computeWarning(warning, schema) : warning;
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
.root > * {
|
.root > * {
|
||||||
|
@@ -73,25 +73,25 @@ class HaMenuButton extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
const oldHass = changedProps.has("hass")
|
||||||
|
? (changedProps.get("hass") as HomeAssistant | undefined)
|
||||||
|
: this.hass;
|
||||||
|
const oldNarrow = changedProps.has("narrow")
|
||||||
|
? (changedProps.get("narrow") as boolean | undefined)
|
||||||
|
: this.narrow;
|
||||||
|
|
||||||
let oldNarrow: boolean | undefined;
|
const oldShowButton =
|
||||||
let newNarrow: boolean | undefined;
|
oldNarrow || oldHass?.dockedSidebar === "always_hidden";
|
||||||
if (changedProps.has("narrow")) {
|
const showButton =
|
||||||
oldNarrow = changedProps.get("narrow");
|
this.narrow || this.hass.dockedSidebar === "always_hidden";
|
||||||
newNarrow = this.narrow;
|
|
||||||
} else if (oldHass) {
|
|
||||||
oldNarrow = oldHass.dockedSidebar === "always_hidden";
|
|
||||||
newNarrow = this.hass.dockedSidebar === "always_hidden";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldNarrow === newNarrow) {
|
if (oldShowButton === showButton) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.style.display = newNarrow || this._alwaysVisible ? "initial" : "none";
|
this.style.display = showButton || this._alwaysVisible ? "initial" : "none";
|
||||||
|
|
||||||
if (!newNarrow) {
|
if (!showButton) {
|
||||||
if (this._unsubNotifications) {
|
if (this._unsubNotifications) {
|
||||||
this._unsubNotifications();
|
this._unsubNotifications();
|
||||||
this._unsubNotifications = undefined;
|
this._unsubNotifications = undefined;
|
||||||
|
@@ -249,12 +249,16 @@ export class HaServiceControl extends LitElement {
|
|||||||
) {
|
) {
|
||||||
const targetSelector = target ? { target } : { target: {} };
|
const targetSelector = target ? { target } : { target: {} };
|
||||||
const targetEntities =
|
const targetEntities =
|
||||||
ensureArray(value?.target?.entity_id || value?.data?.entity_id) || [];
|
ensureArray(
|
||||||
|
value?.target?.entity_id || value?.data?.entity_id
|
||||||
|
)?.slice() || [];
|
||||||
const targetDevices =
|
const targetDevices =
|
||||||
ensureArray(value?.target?.device_id || value?.data?.device_id) || [];
|
ensureArray(
|
||||||
|
value?.target?.device_id || value?.data?.device_id
|
||||||
|
)?.slice() || [];
|
||||||
const targetAreas = ensureArray(
|
const targetAreas = ensureArray(
|
||||||
value?.target?.area_id || value?.data?.area_id
|
value?.target?.area_id || value?.data?.area_id
|
||||||
);
|
)?.slice();
|
||||||
if (targetAreas) {
|
if (targetAreas) {
|
||||||
targetAreas.forEach((areaId) => {
|
targetAreas.forEach((areaId) => {
|
||||||
const expanded = expandAreaTarget(
|
const expanded = expandAreaTarget(
|
||||||
|
@@ -160,6 +160,9 @@ export class HaTextField extends TextFieldBase {
|
|||||||
.mdc-text-field__input[type="number"] {
|
.mdc-text-field__input[type="number"] {
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
|
.mdc-text-field__affix--prefix {
|
||||||
|
padding-right: var(--text-field-prefix-padding-right, 2px);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
// safari workaround - must be explicit
|
// safari workaround - must be explicit
|
||||||
document.dir === "rtl"
|
document.dir === "rtl"
|
||||||
|
@@ -107,6 +107,11 @@ export interface NumericStateTrigger extends BaseTrigger {
|
|||||||
for?: string | number | ForDict;
|
for?: string | number | ForDict;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ConversationTrigger extends BaseTrigger {
|
||||||
|
platform: "conversation";
|
||||||
|
command: string | string[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface SunTrigger extends BaseTrigger {
|
export interface SunTrigger extends BaseTrigger {
|
||||||
platform: "sun";
|
platform: "sun";
|
||||||
offset: number;
|
offset: number;
|
||||||
@@ -178,6 +183,7 @@ export type Trigger =
|
|||||||
| HassTrigger
|
| HassTrigger
|
||||||
| NumericStateTrigger
|
| NumericStateTrigger
|
||||||
| SunTrigger
|
| SunTrigger
|
||||||
|
| ConversationTrigger
|
||||||
| TimePatternTrigger
|
| TimePatternTrigger
|
||||||
| WebhookTrigger
|
| WebhookTrigger
|
||||||
| PersistentNotificationTrigger
|
| PersistentNotificationTrigger
|
||||||
@@ -387,7 +393,7 @@ export const testCondition = (
|
|||||||
variables,
|
variables,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Clipboard = {
|
export type AutomationClipboard = {
|
||||||
trigger?: Trigger;
|
trigger?: Trigger;
|
||||||
condition?: Condition;
|
condition?: Condition;
|
||||||
action?: Action;
|
action?: Action;
|
||||||
|
@@ -81,6 +81,26 @@ export const describeTrigger = (
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entityRegistry: EntityRegistryEntry[],
|
entityRegistry: EntityRegistryEntry[],
|
||||||
ignoreAlias = false
|
ignoreAlias = false
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
return tryDescribeTrigger(trigger, hass, entityRegistry, ignoreAlias);
|
||||||
|
} catch (error: any) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
let msg = "Error in describing trigger";
|
||||||
|
if (error.message) {
|
||||||
|
msg += ": " + error.message;
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const tryDescribeTrigger = (
|
||||||
|
trigger: Trigger,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entityRegistry: EntityRegistryEntry[],
|
||||||
|
ignoreAlias = false
|
||||||
) => {
|
) => {
|
||||||
if (trigger.alias && !ignoreAlias) {
|
if (trigger.alias && !ignoreAlias) {
|
||||||
return trigger.alias;
|
return trigger.alias;
|
||||||
@@ -590,6 +610,24 @@ export const describeTrigger = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Conversation Trigger
|
||||||
|
if (trigger.platform === "conversation") {
|
||||||
|
if (!trigger.command) {
|
||||||
|
return hass.localize(
|
||||||
|
`${triggerTranslationBaseKey}.conversation.description.empty`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hass.localize(
|
||||||
|
`${triggerTranslationBaseKey}.conversation.description.full`,
|
||||||
|
{
|
||||||
|
sentence: disjunctionFormatter.format(
|
||||||
|
ensureArray(trigger.command).map((cmd) => `'${cmd}'`)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Persistent Notification Trigger
|
// Persistent Notification Trigger
|
||||||
if (trigger.platform === "persistent_notification") {
|
if (trigger.platform === "persistent_notification") {
|
||||||
return "When a persistent notification is updated";
|
return "When a persistent notification is updated";
|
||||||
@@ -625,6 +663,26 @@ export const describeCondition = (
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entityRegistry: EntityRegistryEntry[],
|
entityRegistry: EntityRegistryEntry[],
|
||||||
ignoreAlias = false
|
ignoreAlias = false
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
return tryDescribeCondition(condition, hass, entityRegistry, ignoreAlias);
|
||||||
|
} catch (error: any) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
let msg = "Error in describing condition";
|
||||||
|
if (error.message) {
|
||||||
|
msg += ": " + error.message;
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const tryDescribeCondition = (
|
||||||
|
condition: Condition,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entityRegistry: EntityRegistryEntry[],
|
||||||
|
ignoreAlias = false
|
||||||
) => {
|
) => {
|
||||||
if (condition.alias && !ignoreAlias) {
|
if (condition.alias && !ignoreAlias) {
|
||||||
return condition.alias;
|
return condition.alias;
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { ensureArray } from "../common/array/ensure-array";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
interface IntentTarget {
|
interface IntentTarget {
|
||||||
@@ -52,16 +53,30 @@ export interface ConversationResult {
|
|||||||
| IntentResultError;
|
| IntentResultError;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentInfo {
|
|
||||||
attribution?: { name: string; url: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Agent {
|
export interface Agent {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
supported_languages: "*" | string[];
|
supported_languages: "*" | string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AssitDebugResult {
|
||||||
|
intent: {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
entities: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssistDebugResponse {
|
||||||
|
results: (AssitDebugResult | null)[];
|
||||||
|
}
|
||||||
|
|
||||||
export const processConversationInput = (
|
export const processConversationInput = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
text: string,
|
text: string,
|
||||||
@@ -87,15 +102,6 @@ export const listAgents = (
|
|||||||
country,
|
country,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getAgentInfo = (
|
|
||||||
hass: HomeAssistant,
|
|
||||||
agent_id?: string
|
|
||||||
): Promise<AgentInfo> =>
|
|
||||||
hass.callWS({
|
|
||||||
type: "conversation/agent/info",
|
|
||||||
agent_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const prepareConversation = (
|
export const prepareConversation = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
language?: string
|
language?: string
|
||||||
@@ -104,3 +110,16 @@ export const prepareConversation = (
|
|||||||
type: "conversation/prepare",
|
type: "conversation/prepare",
|
||||||
language,
|
language,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const debugAgent = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
sentences: string[] | string,
|
||||||
|
language: string,
|
||||||
|
device_id?: string
|
||||||
|
): Promise<AssistDebugResponse> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "conversation/agent/homeassistant/debug",
|
||||||
|
sentences: ensureArray(sentences),
|
||||||
|
language,
|
||||||
|
device_id,
|
||||||
|
});
|
||||||
|
@@ -23,7 +23,13 @@ export type AddonStartup =
|
|||||||
| "services"
|
| "services"
|
||||||
| "application"
|
| "application"
|
||||||
| "once";
|
| "once";
|
||||||
export type AddonState = "started" | "stopped" | null;
|
export type AddonState =
|
||||||
|
| "startup"
|
||||||
|
| "started"
|
||||||
|
| "stopped"
|
||||||
|
| "unknown"
|
||||||
|
| "error"
|
||||||
|
| null;
|
||||||
export type AddonRepository = "core" | "local" | string;
|
export type AddonRepository = "core" | "local" | string;
|
||||||
|
|
||||||
interface AddonTranslations {
|
interface AddonTranslations {
|
||||||
|
@@ -13,6 +13,7 @@ export type HumidifierEntity = HassEntityBase & {
|
|||||||
state: HumidifierState;
|
state: HumidifierState;
|
||||||
attributes: HassEntityAttributeBase & {
|
attributes: HassEntityAttributeBase & {
|
||||||
humidity?: number;
|
humidity?: number;
|
||||||
|
current_humidity?: number;
|
||||||
min_humidity?: number;
|
min_humidity?: number;
|
||||||
max_humidity?: number;
|
max_humidity?: number;
|
||||||
mode?: string;
|
mode?: string;
|
||||||
|
@@ -7,6 +7,7 @@ import {
|
|||||||
import { computeDomain } from "../common/entity/compute_domain";
|
import { computeDomain } from "../common/entity/compute_domain";
|
||||||
import { computeStateDisplay } from "../common/entity/compute_state_display";
|
import { computeStateDisplay } from "../common/entity/compute_state_display";
|
||||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||||
|
import { autoCaseNoun } from "../common/translations/auto_case_noun";
|
||||||
import { LocalizeFunc } from "../common/translations/localize";
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
import { HaEntityPickerEntityFilterFunc } from "../components/entity/ha-entity-picker";
|
import { HaEntityPickerEntityFilterFunc } from "../components/entity/ha-entity-picker";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
@@ -359,15 +360,21 @@ export const localizeStateMessage = (
|
|||||||
case "vibration":
|
case "vibration":
|
||||||
if (isOn) {
|
if (isOn) {
|
||||||
return localize(`${LOGBOOK_LOCALIZE_PATH}.detected_device_class`, {
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.detected_device_class`, {
|
||||||
device_class: localize(
|
device_class: autoCaseNoun(
|
||||||
`component.binary_sensor.device_class.${device_class}`
|
localize(
|
||||||
|
`component.binary_sensor.entity_component.${device_class}.name`
|
||||||
|
),
|
||||||
|
hass.language
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (isOff) {
|
if (isOff) {
|
||||||
return localize(`${LOGBOOK_LOCALIZE_PATH}.cleared_device_class`, {
|
return localize(`${LOGBOOK_LOCALIZE_PATH}.cleared_device_class`, {
|
||||||
device_class: localize(
|
device_class: autoCaseNoun(
|
||||||
`component.binary_sensor.device_class.${device_class}`
|
localize(
|
||||||
|
`component.binary_sensor.entity_component.${device_class}.name`
|
||||||
|
),
|
||||||
|
hass.language
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -51,6 +51,7 @@ export const serviceActionStruct: Describe<ServiceAction> = assign(
|
|||||||
entity_id: optional(string()),
|
entity_id: optional(string()),
|
||||||
target: optional(targetStruct),
|
target: optional(targetStruct),
|
||||||
data: optional(object()),
|
data: optional(object()),
|
||||||
|
response_variable: optional(string()),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -116,6 +117,7 @@ export interface ServiceAction extends BaseAction {
|
|||||||
entity_id?: string;
|
entity_id?: string;
|
||||||
target?: HassServiceTarget;
|
target?: HassServiceTarget;
|
||||||
data?: Record<string, unknown>;
|
data?: Record<string, unknown>;
|
||||||
|
response_variable?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeviceAction extends BaseAction {
|
export interface DeviceAction extends BaseAction {
|
||||||
@@ -221,6 +223,7 @@ export interface VariablesAction extends BaseAction {
|
|||||||
|
|
||||||
export interface StopAction extends BaseAction {
|
export interface StopAction extends BaseAction {
|
||||||
stop: string;
|
stop: string;
|
||||||
|
response_variable?: string;
|
||||||
error?: boolean;
|
error?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -38,6 +38,32 @@ export const describeAction = <T extends ActionType>(
|
|||||||
action: ActionTypes[T],
|
action: ActionTypes[T],
|
||||||
actionType?: T,
|
actionType?: T,
|
||||||
ignoreAlias = false
|
ignoreAlias = false
|
||||||
|
): string => {
|
||||||
|
try {
|
||||||
|
return tryDescribeAction(
|
||||||
|
hass,
|
||||||
|
entityRegistry,
|
||||||
|
action,
|
||||||
|
actionType,
|
||||||
|
ignoreAlias
|
||||||
|
);
|
||||||
|
} catch (error: any) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(error);
|
||||||
|
let msg = "Error in describing action";
|
||||||
|
if (error.message) {
|
||||||
|
msg += ": " + error.message;
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const tryDescribeAction = <T extends ActionType>(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entityRegistry: EntityRegistryEntry[],
|
||||||
|
action: ActionTypes[T],
|
||||||
|
actionType?: T,
|
||||||
|
ignoreAlias = false
|
||||||
): string => {
|
): string => {
|
||||||
if (action.alias && !ignoreAlias) {
|
if (action.alias && !ignoreAlias) {
|
||||||
return action.alias;
|
return action.alias;
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import { HomeAssistant } from "../types";
|
import { Context, HomeAssistant } from "../types";
|
||||||
import { Action } from "./script";
|
import { Action } from "./script";
|
||||||
|
|
||||||
export const callExecuteScript = (
|
export const callExecuteScript = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
sequence: Action | Action[]
|
sequence: Action | Action[]
|
||||||
) =>
|
): Promise<{ context: Context; response: Record<string, any> }> =>
|
||||||
hass.callWS({
|
hass.callWS({
|
||||||
type: "execute_script",
|
type: "execute_script",
|
||||||
sequence,
|
sequence,
|
||||||
|
@@ -22,6 +22,8 @@ interface MountOptions {
|
|||||||
default_backup_mount?: string | null;
|
default_backup_mount?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CIFSVersion = "auto" | "1.0" | "2.0";
|
||||||
|
|
||||||
interface SupervisorMountBase {
|
interface SupervisorMountBase {
|
||||||
name: string;
|
name: string;
|
||||||
usage: SupervisorMountUsage;
|
usage: SupervisorMountUsage;
|
||||||
@@ -42,6 +44,7 @@ export interface SupervisorNFSMount extends SupervisorMountResponse {
|
|||||||
export interface SupervisorCIFSMount extends SupervisorMountResponse {
|
export interface SupervisorCIFSMount extends SupervisorMountResponse {
|
||||||
type: SupervisorMountType.CIFS;
|
type: SupervisorMountType.CIFS;
|
||||||
share: string;
|
share: string;
|
||||||
|
version?: CIFSVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SupervisorMount = SupervisorNFSMount | SupervisorCIFSMount;
|
export type SupervisorMount = SupervisorNFSMount | SupervisorCIFSMount;
|
||||||
@@ -51,6 +54,7 @@ export type SupervisorNFSMountRequestParams = SupervisorNFSMount;
|
|||||||
export interface SupervisorCIFSMountRequestParams extends SupervisorCIFSMount {
|
export interface SupervisorCIFSMountRequestParams extends SupervisorCIFSMount {
|
||||||
username?: string;
|
username?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
|
version?: CIFSVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SupervisorMountRequestParams =
|
export type SupervisorMountRequestParams =
|
||||||
|
@@ -129,5 +129,6 @@ export const getSupervisorEventCollection = (
|
|||||||
`_supervisor${key}Event`,
|
`_supervisor${key}Event`,
|
||||||
(conn2) => supervisorApiWsRequest(conn2, { endpoint }),
|
(conn2) => supervisorApiWsRequest(conn2, { endpoint }),
|
||||||
(connection, store) =>
|
(connection, store) =>
|
||||||
subscribeSupervisorEventUpdates(connection, store, key)
|
subscribeSupervisorEventUpdates(connection, store, key),
|
||||||
|
{ unsubGrace: false }
|
||||||
);
|
);
|
||||||
|
@@ -9,6 +9,7 @@ import {
|
|||||||
mdiMapMarker,
|
mdiMapMarker,
|
||||||
mdiMapMarkerRadius,
|
mdiMapMarkerRadius,
|
||||||
mdiMessageAlert,
|
mdiMessageAlert,
|
||||||
|
mdiMicrophoneMessage,
|
||||||
mdiNfcVariant,
|
mdiNfcVariant,
|
||||||
mdiNumeric,
|
mdiNumeric,
|
||||||
mdiStateMachine,
|
mdiStateMachine,
|
||||||
@@ -27,6 +28,7 @@ export const TRIGGER_TYPES = {
|
|||||||
mqtt: mdiSwapHorizontal,
|
mqtt: mdiSwapHorizontal,
|
||||||
numeric_state: mdiNumeric,
|
numeric_state: mdiNumeric,
|
||||||
sun: mdiWeatherSunny,
|
sun: mdiWeatherSunny,
|
||||||
|
conversation: mdiMicrophoneMessage,
|
||||||
tag: mdiNfcVariant,
|
tag: mdiNfcVariant,
|
||||||
template: mdiCodeBraces,
|
template: mdiCodeBraces,
|
||||||
time: mdiClockOutline,
|
time: mdiClockOutline,
|
||||||
|
@@ -7,6 +7,7 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { domainIcon } from "../../../../common/entity/domain_icon";
|
import { domainIcon } from "../../../../common/entity/domain_icon";
|
||||||
import { stateColorCss } from "../../../../common/entity/state_color";
|
import { stateColorCss } from "../../../../common/entity/state_color";
|
||||||
@@ -136,8 +137,6 @@ export class HaMoreInfoLockToggle extends LitElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-control-switch
|
<ha-control-switch
|
||||||
.pathOn=${onIcon}
|
|
||||||
.pathOff=${offIcon}
|
|
||||||
vertical
|
vertical
|
||||||
reversed
|
reversed
|
||||||
.checked=${this._isOn}
|
.checked=${this._isOn}
|
||||||
@@ -149,12 +148,33 @@ export class HaMoreInfoLockToggle extends LitElement {
|
|||||||
})}
|
})}
|
||||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||||
>
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="icon-on"
|
||||||
|
.path=${onIcon}
|
||||||
|
class=${classMap({ pulse: locking })}
|
||||||
|
></ha-svg-icon>
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="icon-off"
|
||||||
|
.path=${offIcon}
|
||||||
|
class=${classMap({ pulse: unlocking })}
|
||||||
|
></ha-svg-icon>
|
||||||
</ha-control-switch>
|
</ha-control-switch>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
ha-control-switch {
|
ha-control-switch {
|
||||||
height: 45vh;
|
height: 45vh;
|
||||||
max-height: 320px;
|
max-height: 320px;
|
||||||
@@ -164,6 +184,9 @@ export class HaMoreInfoLockToggle extends LitElement {
|
|||||||
--control-switch-padding: 6px;
|
--control-switch-padding: 6px;
|
||||||
--mdc-icon-size: 24px;
|
--mdc-icon-size: 24px;
|
||||||
}
|
}
|
||||||
|
.pulse {
|
||||||
|
animation: pulse 1s infinite;
|
||||||
|
}
|
||||||
.buttons {
|
.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@@ -4,7 +4,7 @@ import { customElement, property } from "lit/decorators";
|
|||||||
import "../../../components/ha-date-input";
|
import "../../../components/ha-date-input";
|
||||||
import "../../../components/ha-time-input";
|
import "../../../components/ha-time-input";
|
||||||
import { setDateValue } from "../../../data/date";
|
import { setDateValue } from "../../../data/date";
|
||||||
import { isUnavailableState } from "../../../data/entity";
|
import { isUnavailableState, UNAVAILABLE } from "../../../data/entity";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
@customElement("more-info-date")
|
@customElement("more-info-date")
|
||||||
@@ -14,15 +14,17 @@ class MoreInfoDate extends LitElement {
|
|||||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this.stateObj || isUnavailableState(this.stateObj.state)) {
|
if (!this.stateObj || this.stateObj.state === UNAVAILABLE) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-date-input
|
<ha-date-input
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
.value=${this.stateObj.state}
|
.value=${isUnavailableState(this.stateObj.state)
|
||||||
.disabled=${isUnavailableState(this.stateObj.state)}
|
? undefined
|
||||||
|
: this.stateObj.state}
|
||||||
|
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||||
@value-changed=${this._dateChanged}
|
@value-changed=${this._dateChanged}
|
||||||
>
|
>
|
||||||
</ha-date-input>
|
</ha-date-input>
|
||||||
@@ -30,7 +32,9 @@ class MoreInfoDate extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _dateChanged(ev: CustomEvent<{ value: string }>): void {
|
private _dateChanged(ev: CustomEvent<{ value: string }>): void {
|
||||||
setDateValue(this.hass!, this.stateObj!.entity_id, ev.detail.value);
|
if (ev.detail.value) {
|
||||||
|
setDateValue(this.hass!, this.stateObj!.entity_id, ev.detail.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
|
@@ -5,7 +5,7 @@ import { customElement, property } from "lit/decorators";
|
|||||||
import "../../../components/ha-date-input";
|
import "../../../components/ha-date-input";
|
||||||
import "../../../components/ha-time-input";
|
import "../../../components/ha-time-input";
|
||||||
import { setDateTimeValue } from "../../../data/datetime";
|
import { setDateTimeValue } from "../../../data/datetime";
|
||||||
import { isUnavailableState } from "../../../data/entity";
|
import { isUnavailableState, UNAVAILABLE } from "../../../data/entity";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
@customElement("more-info-datetime")
|
@customElement("more-info-datetime")
|
||||||
@@ -15,25 +15,27 @@ class MoreInfoDatetime extends LitElement {
|
|||||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this.stateObj || isUnavailableState(this.stateObj.state)) {
|
if (!this.stateObj || this.stateObj.state === UNAVAILABLE) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dateObj = new Date(this.stateObj.state);
|
const dateObj = isUnavailableState(this.stateObj.state)
|
||||||
const time = format(dateObj, "HH:mm:ss");
|
? undefined
|
||||||
const date = format(dateObj, "yyyy-MM-dd");
|
: new Date(this.stateObj.state);
|
||||||
|
const time = dateObj ? format(dateObj, "HH:mm:ss") : undefined;
|
||||||
|
const date = dateObj ? format(dateObj, "yyyy-MM-dd") : undefined;
|
||||||
|
|
||||||
return html`<ha-date-input
|
return html`<ha-date-input
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
.value=${date}
|
.value=${date}
|
||||||
.disabled=${isUnavailableState(this.stateObj.state)}
|
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||||
@value-changed=${this._dateChanged}
|
@value-changed=${this._dateChanged}
|
||||||
>
|
>
|
||||||
</ha-date-input>
|
</ha-date-input>
|
||||||
<ha-time-input
|
<ha-time-input
|
||||||
.value=${time}
|
.value=${time}
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
.disabled=${isUnavailableState(this.stateObj.state)}
|
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||||
@value-changed=${this._timeChanged}
|
@value-changed=${this._timeChanged}
|
||||||
@click=${this._stopEventPropagation}
|
@click=${this._stopEventPropagation}
|
||||||
></ha-time-input>`;
|
></ha-time-input>`;
|
||||||
@@ -44,19 +46,23 @@ class MoreInfoDatetime extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _timeChanged(ev: CustomEvent<{ value: string }>): void {
|
private _timeChanged(ev: CustomEvent<{ value: string }>): void {
|
||||||
const dateObj = new Date(this.stateObj!.state);
|
if (ev.detail.value) {
|
||||||
const newTime = ev.detail.value.split(":").map(Number);
|
const dateObj = new Date(this.stateObj!.state);
|
||||||
dateObj.setHours(newTime[0], newTime[1], newTime[2]);
|
const newTime = ev.detail.value.split(":").map(Number);
|
||||||
|
dateObj.setHours(newTime[0], newTime[1], newTime[2]);
|
||||||
|
|
||||||
setDateTimeValue(this.hass!, this.stateObj!.entity_id, dateObj);
|
setDateTimeValue(this.hass!, this.stateObj!.entity_id, dateObj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _dateChanged(ev: CustomEvent<{ value: string }>): void {
|
private _dateChanged(ev: CustomEvent<{ value: string }>): void {
|
||||||
const dateObj = new Date(this.stateObj!.state);
|
if (ev.detail.value) {
|
||||||
const newDate = ev.detail.value.split("-").map(Number);
|
const dateObj = new Date(this.stateObj!.state);
|
||||||
dateObj.setFullYear(newDate[0], newDate[1] - 1, newDate[2]);
|
const newDate = ev.detail.value.split("-").map(Number);
|
||||||
|
dateObj.setFullYear(newDate[0], newDate[1] - 1, newDate[2]);
|
||||||
|
|
||||||
setDateTimeValue(this.hass!, this.stateObj!.entity_id, dateObj);
|
setDateTimeValue(this.hass!, this.stateObj!.entity_id, dateObj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
|
@@ -46,7 +46,8 @@ class MoreInfoGroup extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseStateObj = states.find((s) => s.state === "on") || states[0];
|
const baseStateObj =
|
||||||
|
states.find((s) => s.state === this.stateObj!.state) || states[0];
|
||||||
|
|
||||||
const groupDomain = computeGroupDomain(this.stateObj);
|
const groupDomain = computeGroupDomain(this.stateObj);
|
||||||
|
|
||||||
@@ -56,6 +57,8 @@ class MoreInfoGroup extends LitElement {
|
|||||||
this._groupDomainStateObj = {
|
this._groupDomainStateObj = {
|
||||||
...baseStateObj,
|
...baseStateObj,
|
||||||
entity_id: this.stateObj.entity_id,
|
entity_id: this.stateObj.entity_id,
|
||||||
|
last_updated: this.stateObj.last_updated,
|
||||||
|
last_changed: this.stateObj.last_changed,
|
||||||
attributes: {
|
attributes: {
|
||||||
...baseStateObj.attributes,
|
...baseStateObj.attributes,
|
||||||
friendly_name: this.stateObj.attributes.friendly_name,
|
friendly_name: this.stateObj.attributes.friendly_name,
|
||||||
|
@@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import "../../../components/ha-date-input";
|
import "../../../components/ha-date-input";
|
||||||
import "../../../components/ha-time-input";
|
import "../../../components/ha-time-input";
|
||||||
import { isUnavailableState } from "../../../data/entity";
|
import { isUnavailableState, UNAVAILABLE } from "../../../data/entity";
|
||||||
import { setTimeValue } from "../../../data/time";
|
import { setTimeValue } from "../../../data/time";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
@@ -14,15 +14,17 @@ class MoreInfoTime extends LitElement {
|
|||||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this.stateObj || isUnavailableState(this.stateObj.state)) {
|
if (!this.stateObj || this.stateObj.state === UNAVAILABLE) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-time-input
|
<ha-time-input
|
||||||
.value=${this.stateObj.state}
|
.value=${isUnavailableState(this.stateObj.state)
|
||||||
|
? undefined
|
||||||
|
: this.stateObj.state}
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
.disabled=${isUnavailableState(this.stateObj.state)}
|
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||||
@value-changed=${this._timeChanged}
|
@value-changed=${this._timeChanged}
|
||||||
@click=${this._stopEventPropagation}
|
@click=${this._stopEventPropagation}
|
||||||
></ha-time-input>
|
></ha-time-input>
|
||||||
@@ -34,7 +36,9 @@ class MoreInfoTime extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _timeChanged(ev: CustomEvent<{ value: string }>): void {
|
private _timeChanged(ev: CustomEvent<{ value: string }>): void {
|
||||||
setTimeValue(this.hass!, this.stateObj!.entity_id, ev.detail.value);
|
if (ev.detail.value) {
|
||||||
|
setTimeValue(this.hass!, this.stateObj!.entity_id, ev.detail.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
|
@@ -105,36 +105,34 @@ class MoreInfoVacuum extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
${stateObj.state !== UNAVAILABLE
|
${stateObj.state !== UNAVAILABLE
|
||||||
? html` <div class="flex-horizontal">
|
? html` <div class="flex-horizontal">
|
||||||
${supportsFeature(stateObj, VacuumEntityFeature.STATUS)
|
<div>
|
||||||
? html`
|
<span class="status-subtitle"
|
||||||
<div>
|
>${this.hass!.localize(
|
||||||
<span class="status-subtitle"
|
"ui.dialogs.more_info_control.vacuum.status"
|
||||||
>${this.hass!.localize(
|
)}:
|
||||||
"ui.dialogs.more_info_control.vacuum.status"
|
</span>
|
||||||
)}:
|
<span>
|
||||||
</span>
|
<strong>
|
||||||
<span>
|
${supportsFeature(stateObj, VacuumEntityFeature.STATUS) &&
|
||||||
<strong>
|
stateObj.attributes.status
|
||||||
${computeAttributeValueDisplay(
|
? computeAttributeValueDisplay(
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
this.hass.locale,
|
this.hass.locale,
|
||||||
this.hass.config,
|
this.hass.config,
|
||||||
this.hass.entities,
|
this.hass.entities,
|
||||||
"status"
|
"status"
|
||||||
) ||
|
)
|
||||||
computeStateDisplay(
|
: computeStateDisplay(
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
this.hass.locale,
|
this.hass.locale,
|
||||||
this.hass.config,
|
this.hass.config,
|
||||||
this.hass.entities
|
this.hass.entities
|
||||||
)}
|
)}
|
||||||
</strong>
|
</strong>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${supportsFeature(stateObj, VacuumEntityFeature.BATTERY) &&
|
${supportsFeature(stateObj, VacuumEntityFeature.BATTERY) &&
|
||||||
stateObj.attributes.battery_level
|
stateObj.attributes.battery_level
|
||||||
? html`
|
? html`
|
||||||
|
@@ -35,7 +35,6 @@ import {
|
|||||||
listAssistPipelines,
|
listAssistPipelines,
|
||||||
runAssistPipeline,
|
runAssistPipeline,
|
||||||
} from "../../data/assist_pipeline";
|
} from "../../data/assist_pipeline";
|
||||||
import { AgentInfo, getAgentInfo } from "../../data/conversation";
|
|
||||||
import { haStyleDialog } from "../../resources/styles";
|
import { haStyleDialog } from "../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import { AudioRecorder } from "../../util/audio-recorder";
|
import { AudioRecorder } from "../../util/audio-recorder";
|
||||||
@@ -66,8 +65,6 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
|
|
||||||
@state() private _pipeline?: AssistPipeline;
|
@state() private _pipeline?: AssistPipeline;
|
||||||
|
|
||||||
@state() private _agentInfo?: AgentInfo;
|
|
||||||
|
|
||||||
@state() private _showSendButton = false;
|
@state() private _showSendButton = false;
|
||||||
|
|
||||||
@state() private _pipelines?: AssistPipeline[];
|
@state() private _pipelines?: AssistPipeline[];
|
||||||
@@ -115,7 +112,6 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
this._opened = false;
|
this._opened = false;
|
||||||
this._pipeline = undefined;
|
this._pipeline = undefined;
|
||||||
this._pipelines = undefined;
|
this._pipelines = undefined;
|
||||||
this._agentInfo = undefined;
|
|
||||||
this._conversation = undefined;
|
this._conversation = undefined;
|
||||||
this._conversationId = null;
|
this._conversationId = null;
|
||||||
this._audioRecorder?.close();
|
this._audioRecorder?.close();
|
||||||
@@ -265,17 +261,6 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
`}
|
`}
|
||||||
</span>
|
</span>
|
||||||
</ha-textfield>
|
</ha-textfield>
|
||||||
${this._agentInfo && this._agentInfo.attribution
|
|
||||||
? html`
|
|
||||||
<a
|
|
||||||
href=${this._agentInfo.attribution.url}
|
|
||||||
class="attribution"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>${this._agentInfo.attribution.name}</a
|
|
||||||
>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</div>
|
</div>
|
||||||
</ha-dialog>
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
@@ -298,12 +283,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
if (e.code === "not_found") {
|
if (e.code === "not_found") {
|
||||||
this._pipelineId = undefined;
|
this._pipelineId = undefined;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
this._agentInfo = await getAgentInfo(
|
|
||||||
this.hass,
|
|
||||||
this._pipeline.conversation_engine
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _loadPipelines() {
|
private async _loadPipelines() {
|
||||||
@@ -728,12 +708,6 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
flex: 1 0;
|
flex: 1 0;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
.attribution {
|
|
||||||
display: block;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
padding-top: 4px;
|
|
||||||
margin-bottom: -8px;
|
|
||||||
}
|
|
||||||
.messages {
|
.messages {
|
||||||
display: block;
|
display: block;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
|
@@ -19,6 +19,7 @@ import "../components/ha-menu-button";
|
|||||||
import "../components/ha-svg-icon";
|
import "../components/ha-svg-icon";
|
||||||
import "../components/ha-tab";
|
import "../components/ha-tab";
|
||||||
import { HomeAssistant, Route } from "../types";
|
import { HomeAssistant, Route } from "../types";
|
||||||
|
import { haStyleScrollbar } from "../resources/styles";
|
||||||
|
|
||||||
export interface PageNavigation {
|
export interface PageNavigation {
|
||||||
path: string;
|
path: string;
|
||||||
@@ -186,7 +187,7 @@ class HassTabsSubpage extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="content ${classMap({ tabs: showTabs })}"
|
class="content ha-scrollbar ${classMap({ tabs: showTabs })}"
|
||||||
@scroll=${this._saveScrollPos}
|
@scroll=${this._saveScrollPos}
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
@@ -211,143 +212,146 @@ class HassTabsSubpage extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return [
|
||||||
:host {
|
haStyleScrollbar,
|
||||||
display: block;
|
css`
|
||||||
height: 100%;
|
:host {
|
||||||
background-color: var(--primary-background-color);
|
display: block;
|
||||||
}
|
height: 100%;
|
||||||
|
background-color: var(--primary-background-color);
|
||||||
:host([narrow]) {
|
|
||||||
width: 100%;
|
|
||||||
position: fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-menu-button {
|
|
||||||
margin-right: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 20px;
|
|
||||||
height: var(--header-height);
|
|
||||||
background-color: var(--sidebar-background-color);
|
|
||||||
font-weight: 400;
|
|
||||||
border-bottom: 1px solid var(--divider-color);
|
|
||||||
padding: 8px 12px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
@media (max-width: 599px) {
|
|
||||||
.toolbar {
|
|
||||||
padding: 4px;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.toolbar a {
|
|
||||||
color: var(--sidebar-text-color);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.bottom-bar a {
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tabbar {
|
:host([narrow]) {
|
||||||
display: flex;
|
width: 100%;
|
||||||
font-size: 14px;
|
position: fixed;
|
||||||
overflow: hidden;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#tabbar > a {
|
ha-menu-button {
|
||||||
overflow: hidden;
|
margin-right: 24px;
|
||||||
max-width: 45%;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#tabbar.bottom-bar {
|
.toolbar {
|
||||||
position: absolute;
|
display: flex;
|
||||||
bottom: 0;
|
align-items: center;
|
||||||
left: 0;
|
font-size: 20px;
|
||||||
padding: 0 16px;
|
height: var(--header-height);
|
||||||
box-sizing: border-box;
|
background-color: var(--sidebar-background-color);
|
||||||
background-color: var(--sidebar-background-color);
|
font-weight: 400;
|
||||||
border-top: 1px solid var(--divider-color);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
justify-content: space-around;
|
padding: 8px 12px;
|
||||||
z-index: 2;
|
box-sizing: border-box;
|
||||||
font-size: 12px;
|
}
|
||||||
width: 100%;
|
@media (max-width: 599px) {
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
.toolbar {
|
||||||
}
|
padding: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toolbar a {
|
||||||
|
color: var(--sidebar-text-color);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.bottom-bar a {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
#tabbar:not(.bottom-bar) {
|
#tabbar {
|
||||||
flex: 1;
|
display: flex;
|
||||||
justify-content: center;
|
font-size: 14px;
|
||||||
}
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
:host(:not([narrow])) #toolbar-icon {
|
#tabbar > a {
|
||||||
min-width: 40px;
|
overflow: hidden;
|
||||||
}
|
max-width: 45%;
|
||||||
|
}
|
||||||
|
|
||||||
ha-menu-button,
|
#tabbar.bottom-bar {
|
||||||
ha-icon-button-arrow-prev,
|
position: absolute;
|
||||||
::slotted([slot="toolbar-icon"]) {
|
bottom: 0;
|
||||||
display: flex;
|
left: 0;
|
||||||
flex-shrink: 0;
|
padding: 0 16px;
|
||||||
pointer-events: auto;
|
box-sizing: border-box;
|
||||||
color: var(--sidebar-icon-color);
|
background-color: var(--sidebar-background-color);
|
||||||
}
|
border-top: 1px solid var(--divider-color);
|
||||||
|
justify-content: space-around;
|
||||||
|
z-index: 2;
|
||||||
|
font-size: 12px;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
.main-title {
|
#tabbar:not(.bottom-bar) {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
max-height: var(--header-height);
|
justify-content: center;
|
||||||
line-height: 20px;
|
}
|
||||||
color: var(--sidebar-text-color);
|
|
||||||
margin: var(--main-title-margin, 0 0 0 24px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
:host(:not([narrow])) #toolbar-icon {
|
||||||
position: relative;
|
min-width: 40px;
|
||||||
width: calc(
|
}
|
||||||
100% - env(safe-area-inset-left) - env(safe-area-inset-right)
|
|
||||||
);
|
|
||||||
margin-left: env(safe-area-inset-left);
|
|
||||||
margin-right: env(safe-area-inset-right);
|
|
||||||
height: calc(100% - 1px - var(--header-height));
|
|
||||||
height: calc(
|
|
||||||
100% - 1px - var(--header-height) - env(safe-area-inset-bottom)
|
|
||||||
);
|
|
||||||
overflow: auto;
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([narrow]) .content.tabs {
|
ha-menu-button,
|
||||||
height: calc(100% - 2 * var(--header-height));
|
ha-icon-button-arrow-prev,
|
||||||
height: calc(
|
::slotted([slot="toolbar-icon"]) {
|
||||||
100% - 2 * var(--header-height) - env(safe-area-inset-bottom)
|
display: flex;
|
||||||
);
|
flex-shrink: 0;
|
||||||
}
|
pointer-events: auto;
|
||||||
|
color: var(--sidebar-icon-color);
|
||||||
|
}
|
||||||
|
|
||||||
#fab {
|
.main-title {
|
||||||
position: fixed;
|
flex: 1;
|
||||||
right: calc(16px + env(safe-area-inset-right));
|
max-height: var(--header-height);
|
||||||
bottom: calc(16px + env(safe-area-inset-bottom));
|
line-height: 20px;
|
||||||
z-index: 1;
|
color: var(--sidebar-text-color);
|
||||||
}
|
margin: var(--main-title-margin, 0 0 0 24px);
|
||||||
:host([narrow]) #fab.tabs {
|
}
|
||||||
bottom: calc(84px + env(safe-area-inset-bottom));
|
|
||||||
}
|
.content {
|
||||||
#fab[is-wide] {
|
position: relative;
|
||||||
bottom: 24px;
|
width: calc(
|
||||||
right: 24px;
|
100% - env(safe-area-inset-left) - env(safe-area-inset-right)
|
||||||
}
|
);
|
||||||
:host([rtl]) #fab {
|
margin-left: env(safe-area-inset-left);
|
||||||
right: auto;
|
margin-right: env(safe-area-inset-right);
|
||||||
left: calc(16px + env(safe-area-inset-left));
|
height: calc(100% - 1px - var(--header-height));
|
||||||
}
|
height: calc(
|
||||||
:host([rtl][is-wide]) #fab {
|
100% - 1px - var(--header-height) - env(safe-area-inset-bottom)
|
||||||
bottom: 24px;
|
);
|
||||||
left: 24px;
|
overflow: auto;
|
||||||
right: auto;
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
`;
|
|
||||||
|
:host([narrow]) .content.tabs {
|
||||||
|
height: calc(100% - 2 * var(--header-height));
|
||||||
|
height: calc(
|
||||||
|
100% - 2 * var(--header-height) - env(safe-area-inset-bottom)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#fab {
|
||||||
|
position: fixed;
|
||||||
|
right: calc(16px + env(safe-area-inset-right));
|
||||||
|
bottom: calc(16px + env(safe-area-inset-bottom));
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
:host([narrow]) #fab.tabs {
|
||||||
|
bottom: calc(84px + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
#fab[is-wide] {
|
||||||
|
bottom: 24px;
|
||||||
|
right: 24px;
|
||||||
|
}
|
||||||
|
:host([rtl]) #fab {
|
||||||
|
right: auto;
|
||||||
|
left: calc(16px + env(safe-area-inset-left));
|
||||||
|
}
|
||||||
|
:host([rtl][is-wide]) #fab {
|
||||||
|
bottom: 24px;
|
||||||
|
left: 24px;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,9 +3,9 @@ import "@material/mwc-list/mwc-list-item";
|
|||||||
import {
|
import {
|
||||||
mdiAlertCircleCheck,
|
mdiAlertCircleCheck,
|
||||||
mdiCheck,
|
mdiCheck,
|
||||||
mdiContentDuplicate,
|
|
||||||
mdiContentCopy,
|
mdiContentCopy,
|
||||||
mdiContentCut,
|
mdiContentCut,
|
||||||
|
mdiContentDuplicate,
|
||||||
mdiDelete,
|
mdiDelete,
|
||||||
mdiDotsVertical,
|
mdiDotsVertical,
|
||||||
mdiPlay,
|
mdiPlay,
|
||||||
@@ -14,17 +14,19 @@ import {
|
|||||||
mdiSort,
|
mdiSort,
|
||||||
mdiStopCircleOutline,
|
mdiStopCircleOutline,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
|
import deepClone from "deep-clone-simple";
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
|
||||||
LitElement,
|
LitElement,
|
||||||
nothing,
|
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
|
nothing,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
||||||
@@ -36,12 +38,12 @@ import "../../../../components/ha-expansion-panel";
|
|||||||
import "../../../../components/ha-icon-button";
|
import "../../../../components/ha-icon-button";
|
||||||
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
||||||
import { ACTION_TYPES, YAML_ONLY_ACTION_TYPES } from "../../../../data/action";
|
import { ACTION_TYPES, YAML_ONLY_ACTION_TYPES } from "../../../../data/action";
|
||||||
|
import { AutomationClipboard } from "../../../../data/automation";
|
||||||
import { validateConfig } from "../../../../data/config";
|
import { validateConfig } from "../../../../data/config";
|
||||||
import {
|
import {
|
||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
subscribeEntityRegistry,
|
subscribeEntityRegistry,
|
||||||
} from "../../../../data/entity_registry";
|
} from "../../../../data/entity_registry";
|
||||||
import { Clipboard } from "../../../../data/automation";
|
|
||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
NonConditionAction,
|
NonConditionAction,
|
||||||
@@ -127,7 +129,13 @@ export default class HaAutomationActionRow extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public reOrderMode = false;
|
@property({ type: Boolean }) public reOrderMode = false;
|
||||||
|
|
||||||
@property() public clipboard?: Clipboard;
|
@storage({
|
||||||
|
key: "automationClipboard",
|
||||||
|
state: false,
|
||||||
|
subscribe: true,
|
||||||
|
storage: "sessionStorage",
|
||||||
|
})
|
||||||
|
public _clipboard?: AutomationClipboard;
|
||||||
|
|
||||||
@state() private _entityReg: EntityRegistryEntry[] = [];
|
@state() private _entityReg: EntityRegistryEntry[] = [];
|
||||||
|
|
||||||
@@ -396,7 +404,6 @@ export default class HaAutomationActionRow extends LitElement {
|
|||||||
narrow: this.narrow,
|
narrow: this.narrow,
|
||||||
reOrderMode: this.reOrderMode,
|
reOrderMode: this.reOrderMode,
|
||||||
disabled: this.disabled,
|
disabled: this.disabled,
|
||||||
clipboard: this.clipboard,
|
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
@@ -431,10 +438,10 @@ export default class HaAutomationActionRow extends LitElement {
|
|||||||
fireEvent(this, "duplicate");
|
fireEvent(this, "duplicate");
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
fireEvent(this, "set-clipboard", { action: this.action });
|
this._setClipboard();
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
fireEvent(this, "set-clipboard", { action: this.action });
|
this._setClipboard();
|
||||||
fireEvent(this, "value-changed", { value: null });
|
fireEvent(this, "value-changed", { value: null });
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
@@ -454,6 +461,13 @@ export default class HaAutomationActionRow extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _setClipboard() {
|
||||||
|
this._clipboard = {
|
||||||
|
...this._clipboard,
|
||||||
|
action: deepClone(this.action),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private _onDisable() {
|
private _onDisable() {
|
||||||
const enabled = !(this.action.enabled ?? true);
|
const enabled = !(this.action.enabled ?? true);
|
||||||
const value = { ...this.action, enabled };
|
const value = { ...this.action, enabled };
|
||||||
|
@@ -29,7 +29,7 @@ import type { HaSelect } from "../../../../components/ha-select";
|
|||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
import { ACTION_TYPES } from "../../../../data/action";
|
import { ACTION_TYPES } from "../../../../data/action";
|
||||||
import { Action } from "../../../../data/script";
|
import { Action } from "../../../../data/script";
|
||||||
import { Clipboard } from "../../../../data/automation";
|
import { AutomationClipboard } from "../../../../data/automation";
|
||||||
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||||
import {
|
import {
|
||||||
loadSortable,
|
loadSortable,
|
||||||
@@ -52,6 +52,7 @@ import "./types/ha-automation-action-service";
|
|||||||
import "./types/ha-automation-action-stop";
|
import "./types/ha-automation-action-stop";
|
||||||
import "./types/ha-automation-action-wait_for_trigger";
|
import "./types/ha-automation-action-wait_for_trigger";
|
||||||
import "./types/ha-automation-action-wait_template";
|
import "./types/ha-automation-action-wait_template";
|
||||||
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
|
|
||||||
const PASTE_VALUE = "__paste__";
|
const PASTE_VALUE = "__paste__";
|
||||||
|
|
||||||
@@ -69,7 +70,13 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public reOrderMode = false;
|
@property({ type: Boolean }) public reOrderMode = false;
|
||||||
|
|
||||||
@property() public clipboard?: Clipboard;
|
@storage({
|
||||||
|
key: "automationClipboard",
|
||||||
|
state: true,
|
||||||
|
subscribe: true,
|
||||||
|
storage: "sessionStorage",
|
||||||
|
})
|
||||||
|
public _clipboard?: AutomationClipboard;
|
||||||
|
|
||||||
private _focusLastActionOnChange = false;
|
private _focusLastActionOnChange = false;
|
||||||
|
|
||||||
@@ -113,7 +120,6 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
@duplicate=${this._duplicateAction}
|
@duplicate=${this._duplicateAction}
|
||||||
@value-changed=${this._actionChanged}
|
@value-changed=${this._actionChanged}
|
||||||
@re-order=${this._enterReOrderMode}
|
@re-order=${this._enterReOrderMode}
|
||||||
.clipboard=${this.clipboard}
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
>
|
>
|
||||||
${this.reOrderMode
|
${this.reOrderMode
|
||||||
@@ -162,14 +168,14 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
>
|
>
|
||||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||||
</ha-button>
|
</ha-button>
|
||||||
${this.clipboard?.action
|
${this._clipboard?.action
|
||||||
? html` <mwc-list-item .value=${PASTE_VALUE} graphic="icon">
|
? html` <mwc-list-item .value=${PASTE_VALUE} graphic="icon">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.actions.paste"
|
"ui.panel.config.automation.editor.actions.paste"
|
||||||
)}
|
)}
|
||||||
(${this.hass.localize(
|
(${this.hass.localize(
|
||||||
`ui.panel.config.automation.editor.actions.type.${getType(
|
`ui.panel.config.automation.editor.actions.type.${getType(
|
||||||
this.clipboard.action
|
this._clipboard.action
|
||||||
)}.label`
|
)}.label`
|
||||||
)})
|
)})
|
||||||
<ha-svg-icon slot="graphic" .path=${mdiContentPaste}></ha-svg-icon
|
<ha-svg-icon slot="graphic" .path=${mdiContentPaste}></ha-svg-icon
|
||||||
@@ -260,7 +266,7 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
|
|
||||||
let actions: Action[];
|
let actions: Action[];
|
||||||
if (action === PASTE_VALUE) {
|
if (action === PASTE_VALUE) {
|
||||||
actions = this.actions.concat(deepClone(this.clipboard!.action));
|
actions = this.actions.concat(deepClone(this._clipboard!.action));
|
||||||
} else {
|
} else {
|
||||||
const elClass = customElements.get(
|
const elClass = customElements.get(
|
||||||
`ha-automation-action-${action}`
|
`ha-automation-action-${action}`
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { mdiDelete, mdiPlus } from "@mdi/js";
|
import { mdiDelete, mdiPlus } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { CSSResultGroup, LitElement, css, html } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
|
||||||
import { ensureArray } from "../../../../../common/array/ensure-array";
|
import { ensureArray } from "../../../../../common/array/ensure-array";
|
||||||
import "../../../../../components/ha-icon-button";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
import "../../../../../components/ha-button";
|
import "../../../../../components/ha-button";
|
||||||
import { Condition, Clipboard } from "../../../../../data/automation";
|
import "../../../../../components/ha-icon-button";
|
||||||
|
import { Condition } from "../../../../../data/automation";
|
||||||
import { Action, ChooseAction } from "../../../../../data/script";
|
import { Action, ChooseAction } from "../../../../../data/script";
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
@@ -23,8 +23,6 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
|||||||
|
|
||||||
@state() private _showDefault = false;
|
@state() private _showDefault = false;
|
||||||
|
|
||||||
@property() public clipboard?: Clipboard;
|
|
||||||
|
|
||||||
public static get defaultConfig() {
|
public static get defaultConfig() {
|
||||||
return { choose: [{ conditions: [], sequence: [] }] };
|
return { choose: [{ conditions: [], sequence: [] }] };
|
||||||
}
|
}
|
||||||
@@ -65,7 +63,6 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.idx=${idx}
|
.idx=${idx}
|
||||||
@value-changed=${this._conditionChanged}
|
@value-changed=${this._conditionChanged}
|
||||||
.clipboard=${this.clipboard}
|
|
||||||
></ha-automation-condition>
|
></ha-automation-condition>
|
||||||
<h3>
|
<h3>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
@@ -80,7 +77,6 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.idx=${idx}
|
.idx=${idx}
|
||||||
@value-changed=${this._actionChanged}
|
@value-changed=${this._actionChanged}
|
||||||
.clipboard=${this.clipboard}
|
|
||||||
></ha-automation-action>
|
></ha-automation-action>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>`
|
</ha-card>`
|
||||||
@@ -109,7 +105,6 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._defaultChanged}
|
@value-changed=${this._defaultChanged}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.clipboard=${this.clipboard}
|
|
||||||
></ha-automation-action>
|
></ha-automation-action>
|
||||||
`
|
`
|
||||||
: html`<div class="link-button-row">
|
: html`<div class="link-button-row">
|
||||||
|
@@ -6,7 +6,7 @@ import { stringCompare } from "../../../../../common/string/compare";
|
|||||||
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||||
import "../../../../../components/ha-select";
|
import "../../../../../components/ha-select";
|
||||||
import type { HaSelect } from "../../../../../components/ha-select";
|
import type { HaSelect } from "../../../../../components/ha-select";
|
||||||
import type { Condition, Clipboard } from "../../../../../data/automation";
|
import type { Condition } from "../../../../../data/automation";
|
||||||
import { CONDITION_TYPES } from "../../../../../data/condition";
|
import { CONDITION_TYPES } from "../../../../../data/condition";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import "../../condition/ha-automation-condition-editor";
|
import "../../condition/ha-automation-condition-editor";
|
||||||
@@ -20,8 +20,6 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
|||||||
|
|
||||||
@property() public action!: Condition;
|
@property() public action!: Condition;
|
||||||
|
|
||||||
@property() public clipboard?: Clipboard;
|
|
||||||
|
|
||||||
public static get defaultConfig() {
|
public static get defaultConfig() {
|
||||||
return { condition: "state" };
|
return { condition: "state" };
|
||||||
}
|
}
|
||||||
@@ -51,7 +49,6 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@value-changed=${this._conditionChanged}
|
@value-changed=${this._conditionChanged}
|
||||||
.clipboard=${this.clipboard}
|
|
||||||
></ha-automation-condition-editor>
|
></ha-automation-condition-editor>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import "../../../../../components/ha-textfield";
|
||||||
import { Action, IfAction } from "../../../../../data/script";
|
import { Action, IfAction } from "../../../../../data/script";
|
||||||
import type { Clipboard } from "../../../../../data/automation";
|
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import type { Condition } from "../../../../lovelace/common/validate-condition";
|
import type { Condition } from "../../../../lovelace/common/validate-condition";
|
||||||
import "../ha-automation-action";
|
import "../ha-automation-action";
|
||||||
import "../../../../../components/ha-textfield";
|
|
||||||
import type { ActionElement } from "../ha-automation-action-row";
|
import type { ActionElement } from "../ha-automation-action-row";
|
||||||
|
|
||||||
@customElement("ha-automation-action-if")
|
@customElement("ha-automation-action-if")
|
||||||
@@ -20,8 +19,6 @@ export class HaIfAction extends LitElement implements ActionElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public reOrderMode = false;
|
@property({ type: Boolean }) public reOrderMode = false;
|
||||||
|
|
||||||
@property() public clipboard?: Clipboard;
|
|
||||||
|
|
||||||
@state() private _showElse = false;
|
@state() private _showElse = false;
|
||||||
|
|
||||||
public static get defaultConfig() {
|
public static get defaultConfig() {
|
||||||
@@ -46,7 +43,6 @@ export class HaIfAction extends LitElement implements ActionElement {
|
|||||||
.reOrderMode=${this.reOrderMode}
|
.reOrderMode=${this.reOrderMode}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._ifChanged}
|
@value-changed=${this._ifChanged}
|
||||||
.clipboard=${this.clipboard}
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
></ha-automation-condition>
|
></ha-automation-condition>
|
||||||
|
|
||||||
@@ -61,7 +57,6 @@ export class HaIfAction extends LitElement implements ActionElement {
|
|||||||
.reOrderMode=${this.reOrderMode}
|
.reOrderMode=${this.reOrderMode}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._thenChanged}
|
@value-changed=${this._thenChanged}
|
||||||
.clipboard=${this.clipboard}
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
></ha-automation-action>
|
></ha-automation-action>
|
||||||
${this._showElse || action.else
|
${this._showElse || action.else
|
||||||
@@ -77,7 +72,6 @@ export class HaIfAction extends LitElement implements ActionElement {
|
|||||||
.reOrderMode=${this.reOrderMode}
|
.reOrderMode=${this.reOrderMode}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._elseChanged}
|
@value-changed=${this._elseChanged}
|
||||||
.clipboard=${this.clipboard}
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
></ha-automation-action>
|
></ha-automation-action>
|
||||||
`
|
`
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
import { CSSResultGroup, html, LitElement } from "lit";
|
import { CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import "../../../../../components/ha-textfield";
|
||||||
import { Action, ParallelAction } from "../../../../../data/script";
|
import { Action, ParallelAction } from "../../../../../data/script";
|
||||||
import type { Clipboard } from "../../../../../data/automation";
|
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import "../ha-automation-action";
|
import "../ha-automation-action";
|
||||||
import "../../../../../components/ha-textfield";
|
|
||||||
import type { ActionElement } from "../ha-automation-action-row";
|
import type { ActionElement } from "../ha-automation-action-row";
|
||||||
|
|
||||||
@customElement("ha-automation-action-parallel")
|
@customElement("ha-automation-action-parallel")
|
||||||
@@ -19,8 +18,6 @@ export class HaParallelAction extends LitElement implements ActionElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public reOrderMode = false;
|
@property({ type: Boolean }) public reOrderMode = false;
|
||||||
|
|
||||||
@property() public clipboard?: Clipboard;
|
|
||||||
|
|
||||||
public static get defaultConfig() {
|
public static get defaultConfig() {
|
||||||
return {
|
return {
|
||||||
parallel: [],
|
parallel: [],
|
||||||
@@ -37,7 +34,6 @@ export class HaParallelAction extends LitElement implements ActionElement {
|
|||||||
.reOrderMode=${this.reOrderMode}
|
.reOrderMode=${this.reOrderMode}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._actionsChanged}
|
@value-changed=${this._actionsChanged}
|
||||||
.clipboard=${this.clipboard}
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
></ha-automation-action>
|
></ha-automation-action>
|
||||||
`;
|
`;
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import "../../../../../components/ha-textfield";
|
||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
CountRepeat,
|
CountRepeat,
|
||||||
@@ -8,12 +9,10 @@ import {
|
|||||||
UntilRepeat,
|
UntilRepeat,
|
||||||
WhileRepeat,
|
WhileRepeat,
|
||||||
} from "../../../../../data/script";
|
} from "../../../../../data/script";
|
||||||
import type { Clipboard } from "../../../../../data/automation";
|
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import type { Condition } from "../../../../lovelace/common/validate-condition";
|
import type { Condition } from "../../../../lovelace/common/validate-condition";
|
||||||
import "../ha-automation-action";
|
import "../ha-automation-action";
|
||||||
import "../../../../../components/ha-textfield";
|
|
||||||
import type { ActionElement } from "../ha-automation-action-row";
|
import type { ActionElement } from "../ha-automation-action-row";
|
||||||
|
|
||||||
const OPTIONS = ["count", "while", "until"] as const;
|
const OPTIONS = ["count", "while", "until"] as const;
|
||||||
@@ -30,8 +29,6 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public reOrderMode = false;
|
@property({ type: Boolean }) public reOrderMode = false;
|
||||||
|
|
||||||
@property() public clipboard?: Clipboard;
|
|
||||||
|
|
||||||
public static get defaultConfig() {
|
public static get defaultConfig() {
|
||||||
return { repeat: { count: 2, sequence: [] } };
|
return { repeat: { count: 2, sequence: [] } };
|
||||||
}
|
}
|
||||||
@@ -85,7 +82,6 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._conditionChanged}
|
@value-changed=${this._conditionChanged}
|
||||||
.clipboard=${this.clipboard}
|
|
||||||
></ha-automation-condition>`
|
></ha-automation-condition>`
|
||||||
: type === "until"
|
: type === "until"
|
||||||
? html` <h3>
|
? html` <h3>
|
||||||
@@ -99,7 +95,6 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._conditionChanged}
|
@value-changed=${this._conditionChanged}
|
||||||
.clipboard=${this.clipboard}
|
|
||||||
></ha-automation-condition>`
|
></ha-automation-condition>`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
@@ -114,7 +109,6 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
|||||||
.reOrderMode=${this.reOrderMode}
|
.reOrderMode=${this.reOrderMode}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._actionChanged}
|
@value-changed=${this._actionChanged}
|
||||||
.clipboard=${this.clipboard}
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
></ha-automation-action>
|
></ha-automation-action>
|
||||||
`;
|
`;
|
||||||
|
@@ -1,4 +1,11 @@
|
|||||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
nothing,
|
||||||
|
PropertyValues,
|
||||||
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { assert } from "superstruct";
|
import { assert } from "superstruct";
|
||||||
@@ -21,7 +28,9 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public narrow = false;
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
@state() private _action!: ServiceAction;
|
@state() private _action?: ServiceAction;
|
||||||
|
|
||||||
|
@state() private _responseChecked = false;
|
||||||
|
|
||||||
private _fields = memoizeOne(
|
private _fields = memoizeOne(
|
||||||
(
|
(
|
||||||
@@ -98,6 +107,12 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
|
if (!this._action) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
const [domain, service] = this._action.service
|
||||||
|
? this._action.service.split(".", 2)
|
||||||
|
: [undefined, undefined];
|
||||||
return html`
|
return html`
|
||||||
<ha-service-control
|
<ha-service-control
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
@@ -107,6 +122,41 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
|||||||
.showAdvanced=${this.hass.userData?.showAdvanced}
|
.showAdvanced=${this.hass.userData?.showAdvanced}
|
||||||
@value-changed=${this._actionChanged}
|
@value-changed=${this._actionChanged}
|
||||||
></ha-service-control>
|
></ha-service-control>
|
||||||
|
${domain && service && this.hass.services[domain]?.[service]?.response
|
||||||
|
? html`<ha-settings-row .narrow=${this.narrow}>
|
||||||
|
${this.hass.services[domain][service].response!.optional
|
||||||
|
? html`<ha-checkbox
|
||||||
|
.checked=${this._action.response_variable ||
|
||||||
|
this._responseChecked}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@change=${this._responseCheckboxChanged}
|
||||||
|
slot="prefix"
|
||||||
|
></ha-checkbox>`
|
||||||
|
: html`<div slot="prefix" class="checkbox-spacer"></div>`}
|
||||||
|
<span slot="heading"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.service.response_variable"
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
<span slot="description">
|
||||||
|
${this.hass.services[domain][service].response!.optional
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.service.has_optional_response"
|
||||||
|
)
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.service.has_response"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<ha-textfield
|
||||||
|
.value=${this._action.response_variable || ""}
|
||||||
|
.required=${!this.hass.services[domain][service].response!
|
||||||
|
.optional}
|
||||||
|
.disabled=${this.disabled ||
|
||||||
|
(!this._action.response_variable && !this._responseChecked)}
|
||||||
|
@change=${this._responseVariableChanged}
|
||||||
|
></ha-textfield>
|
||||||
|
</ha-settings-row>`
|
||||||
|
: nothing}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +164,39 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
|||||||
if (ev.detail.value === this._action) {
|
if (ev.detail.value === this._action) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
}
|
}
|
||||||
|
const value = { ...this.action, ...ev.detail.value };
|
||||||
|
if ("response_variable" in this.action) {
|
||||||
|
const [domain, service] = this._action!.service
|
||||||
|
? this._action!.service.split(".", 2)
|
||||||
|
: [undefined, undefined];
|
||||||
|
if (
|
||||||
|
domain &&
|
||||||
|
service &&
|
||||||
|
this.hass.services[domain]?.[service] &&
|
||||||
|
!("response" in this.hass.services[domain][service])
|
||||||
|
) {
|
||||||
|
delete value.response_variable;
|
||||||
|
this._responseChecked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fireEvent(this, "value-changed", { value });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _responseVariableChanged(ev) {
|
||||||
|
const value = { ...this.action, response_variable: ev.target.value };
|
||||||
|
if (!ev.target.value) {
|
||||||
|
delete value.response_variable;
|
||||||
|
}
|
||||||
|
fireEvent(this, "value-changed", { value });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _responseCheckboxChanged(ev) {
|
||||||
|
this._responseChecked = ev.target.checked;
|
||||||
|
if (!this._responseChecked) {
|
||||||
|
const value = { ...this.action };
|
||||||
|
delete value.response_variable;
|
||||||
|
fireEvent(this, "value-changed", { value });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
@@ -122,6 +205,25 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
|||||||
display: block;
|
display: block;
|
||||||
margin: 0 -16px;
|
margin: 0 -16px;
|
||||||
}
|
}
|
||||||
|
ha-settings-row {
|
||||||
|
margin: 0 -16px;
|
||||||
|
padding: var(--service-control-padding, 0 16px);
|
||||||
|
}
|
||||||
|
ha-settings-row {
|
||||||
|
--paper-time-input-justify-content: flex-end;
|
||||||
|
--settings-row-content-width: 100%;
|
||||||
|
--settings-row-prefix-display: contents;
|
||||||
|
border-top: var(
|
||||||
|
--service-control-items-border-top,
|
||||||
|
1px solid var(--divider-color)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ha-checkbox {
|
||||||
|
margin-left: -16px;
|
||||||
|
}
|
||||||
|
.checkbox-spacer {
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@ export class HaStopAction extends LitElement implements ActionElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const { error, stop } = this.action;
|
const { error, stop, response_variable } = this.action;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-textfield
|
<ha-textfield
|
||||||
@@ -30,6 +30,14 @@ export class HaStopAction extends LitElement implements ActionElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@change=${this._stopChanged}
|
@change=${this._stopChanged}
|
||||||
></ha-textfield>
|
></ha-textfield>
|
||||||
|
<ha-textfield
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.type.stop.response_variable"
|
||||||
|
)}
|
||||||
|
.value=${response_variable || ""}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@change=${this._responseChanged}
|
||||||
|
></ha-textfield>
|
||||||
<ha-formfield
|
<ha-formfield
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@@ -45,14 +53,21 @@ export class HaStopAction extends LitElement implements ActionElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _stopChanged(ev: CustomEvent) {
|
private _stopChanged(ev: Event) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: { ...this.action, stop: (ev.target as any).value },
|
value: { ...this.action, stop: (ev.target as any).value },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _errorChanged(ev: CustomEvent) {
|
private _responseChanged(ev: Event) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: { ...this.action, response_variable: (ev.target as any).value },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _errorChanged(ev: Event) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: { ...this.action, error: (ev.target as any).checked },
|
value: { ...this.action, error: (ev.target as any).checked },
|
||||||
|
@@ -1,17 +1,16 @@
|
|||||||
import "../../../../../components/ha-textfield";
|
|
||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { ensureArray } from "../../../../../common/array/ensure-array";
|
||||||
|
import { createDurationData } from "../../../../../common/datetime/create_duration_data";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import { TimeChangedEvent } from "../../../../../components/ha-base-time-input";
|
||||||
|
import "../../../../../components/ha-duration-input";
|
||||||
import "../../../../../components/ha-formfield";
|
import "../../../../../components/ha-formfield";
|
||||||
|
import "../../../../../components/ha-textfield";
|
||||||
import { WaitForTriggerAction } from "../../../../../data/script";
|
import { WaitForTriggerAction } from "../../../../../data/script";
|
||||||
import type { Clipboard } from "../../../../../data/automation";
|
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import "../../trigger/ha-automation-trigger";
|
import "../../trigger/ha-automation-trigger";
|
||||||
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
|
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
|
||||||
import "../../../../../components/ha-duration-input";
|
|
||||||
import { createDurationData } from "../../../../../common/datetime/create_duration_data";
|
|
||||||
import { TimeChangedEvent } from "../../../../../components/ha-base-time-input";
|
|
||||||
import { ensureArray } from "../../../../../common/array/ensure-array";
|
|
||||||
|
|
||||||
@customElement("ha-automation-action-wait_for_trigger")
|
@customElement("ha-automation-action-wait_for_trigger")
|
||||||
export class HaWaitForTriggerAction
|
export class HaWaitForTriggerAction
|
||||||
@@ -26,8 +25,6 @@ export class HaWaitForTriggerAction
|
|||||||
|
|
||||||
@property({ type: Boolean }) public reOrderMode = false;
|
@property({ type: Boolean }) public reOrderMode = false;
|
||||||
|
|
||||||
@property() public clipboard?: Clipboard;
|
|
||||||
|
|
||||||
public static get defaultConfig() {
|
public static get defaultConfig() {
|
||||||
return { wait_for_trigger: [] };
|
return { wait_for_trigger: [] };
|
||||||
}
|
}
|
||||||
@@ -65,7 +62,6 @@ export class HaWaitForTriggerAction
|
|||||||
.name=${"wait_for_trigger"}
|
.name=${"wait_for_trigger"}
|
||||||
.reOrderMode=${this.reOrderMode}
|
.reOrderMode=${this.reOrderMode}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
.clipboard=${this.clipboard}
|
|
||||||
></ha-automation-trigger>
|
></ha-automation-trigger>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -138,11 +138,12 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
|||||||
this.config.use_blueprint.input[key]) ??
|
this.config.use_blueprint.input[key]) ??
|
||||||
value?.default}
|
value?.default}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
|
.required=${value?.default === undefined}
|
||||||
@value-changed=${this._inputChanged}
|
@value-changed=${this._inputChanged}
|
||||||
></ha-selector>`
|
></ha-selector>`
|
||||||
: html`<ha-textfield
|
: html`<ha-textfield
|
||||||
.key=${key}
|
.key=${key}
|
||||||
required
|
.required=${value?.default === undefined}
|
||||||
.value=${(this.config.use_blueprint.input &&
|
.value=${(this.config.use_blueprint.input &&
|
||||||
this.config.use_blueprint.input[key]) ??
|
this.config.use_blueprint.input[key]) ??
|
||||||
value?.default}
|
value?.default}
|
||||||
|
@@ -4,7 +4,7 @@ import memoizeOne from "memoize-one";
|
|||||||
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import "../../../../components/ha-yaml-editor";
|
import "../../../../components/ha-yaml-editor";
|
||||||
import type { Condition, Clipboard } from "../../../../data/automation";
|
import type { Condition } from "../../../../data/automation";
|
||||||
import { expandConditionWithShorthand } from "../../../../data/automation";
|
import { expandConditionWithShorthand } from "../../../../data/automation";
|
||||||
import { haStyle } from "../../../../resources/styles";
|
import { haStyle } from "../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
@@ -32,8 +32,6 @@ export default class HaAutomationConditionEditor extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public reOrderMode = false;
|
@property({ type: Boolean }) public reOrderMode = false;
|
||||||
|
|
||||||
@property() public clipboard?: Clipboard;
|
|
||||||
|
|
||||||
private _processedCondition = memoizeOne((condition) =>
|
private _processedCondition = memoizeOne((condition) =>
|
||||||
expandConditionWithShorthand(condition)
|
expandConditionWithShorthand(condition)
|
||||||
);
|
);
|
||||||
@@ -72,7 +70,6 @@ export default class HaAutomationConditionEditor extends LitElement {
|
|||||||
condition: condition,
|
condition: condition,
|
||||||
reOrderMode: this.reOrderMode,
|
reOrderMode: this.reOrderMode,
|
||||||
disabled: this.disabled,
|
disabled: this.disabled,
|
||||||
clipboard: this.clipboard,
|
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -3,9 +3,9 @@ import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import {
|
import {
|
||||||
mdiCheck,
|
mdiCheck,
|
||||||
mdiContentDuplicate,
|
|
||||||
mdiContentCopy,
|
mdiContentCopy,
|
||||||
mdiContentCut,
|
mdiContentCut,
|
||||||
|
mdiContentDuplicate,
|
||||||
mdiDelete,
|
mdiDelete,
|
||||||
mdiDotsVertical,
|
mdiDotsVertical,
|
||||||
mdiFlask,
|
mdiFlask,
|
||||||
@@ -14,9 +14,11 @@ import {
|
|||||||
mdiSort,
|
mdiSort,
|
||||||
mdiStopCircleOutline,
|
mdiStopCircleOutline,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import deepClone from "deep-clone-simple";
|
||||||
|
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
||||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||||
@@ -24,8 +26,8 @@ import "../../../../components/ha-button-menu";
|
|||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import "../../../../components/ha-expansion-panel";
|
import "../../../../components/ha-expansion-panel";
|
||||||
import "../../../../components/ha-icon-button";
|
import "../../../../components/ha-icon-button";
|
||||||
|
import type { AutomationClipboard } from "../../../../data/automation";
|
||||||
import { Condition, testCondition } from "../../../../data/automation";
|
import { Condition, testCondition } from "../../../../data/automation";
|
||||||
import type { Clipboard } from "../../../../data/automation";
|
|
||||||
import { describeCondition } from "../../../../data/automation_i18n";
|
import { describeCondition } from "../../../../data/automation_i18n";
|
||||||
import { CONDITION_TYPES } from "../../../../data/condition";
|
import { CONDITION_TYPES } from "../../../../data/condition";
|
||||||
import { validateConfig } from "../../../../data/config";
|
import { validateConfig } from "../../../../data/config";
|
||||||
@@ -83,7 +85,13 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property() public clipboard?: Clipboard;
|
@storage({
|
||||||
|
key: "automationClipboard",
|
||||||
|
state: false,
|
||||||
|
subscribe: true,
|
||||||
|
storage: "sessionStorage",
|
||||||
|
})
|
||||||
|
public _clipboard?: AutomationClipboard;
|
||||||
|
|
||||||
@state() private _yamlMode = false;
|
@state() private _yamlMode = false;
|
||||||
|
|
||||||
@@ -290,7 +298,6 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.condition=${this.condition}
|
.condition=${this.condition}
|
||||||
.reOrderMode=${this.reOrderMode}
|
.reOrderMode=${this.reOrderMode}
|
||||||
.clipboard=${this.clipboard}
|
|
||||||
></ha-automation-condition-editor>
|
></ha-automation-condition-editor>
|
||||||
</div>
|
</div>
|
||||||
</ha-expansion-panel>
|
</ha-expansion-panel>
|
||||||
@@ -343,10 +350,10 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
fireEvent(this, "duplicate");
|
fireEvent(this, "duplicate");
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
fireEvent(this, "set-clipboard", { condition: this.condition });
|
this._setClipboard();
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
fireEvent(this, "set-clipboard", { condition: this.condition });
|
this._setClipboard();
|
||||||
fireEvent(this, "value-changed", { value: null });
|
fireEvent(this, "value-changed", { value: null });
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
@@ -366,6 +373,13 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _setClipboard() {
|
||||||
|
this._clipboard = {
|
||||||
|
...this._clipboard,
|
||||||
|
condition: deepClone(this.condition),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private _onDisable() {
|
private _onDisable() {
|
||||||
const enabled = !(this.condition.enabled ?? true);
|
const enabled = !(this.condition.enabled ?? true);
|
||||||
const value = { ...this.condition, enabled };
|
const value = { ...this.condition, enabled };
|
||||||
|
@@ -3,9 +3,9 @@ import type { ActionDetail } from "@material/mwc-list";
|
|||||||
import {
|
import {
|
||||||
mdiArrowDown,
|
mdiArrowDown,
|
||||||
mdiArrowUp,
|
mdiArrowUp,
|
||||||
|
mdiContentPaste,
|
||||||
mdiDrag,
|
mdiDrag,
|
||||||
mdiPlus,
|
mdiPlus,
|
||||||
mdiContentPaste,
|
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import deepClone from "deep-clone-simple";
|
import deepClone from "deep-clone-simple";
|
||||||
import {
|
import {
|
||||||
@@ -13,8 +13,8 @@ import {
|
|||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyValues,
|
|
||||||
nothing,
|
nothing,
|
||||||
|
PropertyValues,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { repeat } from "lit/directives/repeat";
|
import { repeat } from "lit/directives/repeat";
|
||||||
@@ -24,7 +24,10 @@ import { fireEvent } from "../../../../common/dom/fire_event";
|
|||||||
import "../../../../components/ha-button";
|
import "../../../../components/ha-button";
|
||||||
import "../../../../components/ha-button-menu";
|
import "../../../../components/ha-button-menu";
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
import type { Condition, Clipboard } from "../../../../data/automation";
|
import type {
|
||||||
|
AutomationClipboard,
|
||||||
|
Condition,
|
||||||
|
} from "../../../../data/automation";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import "./ha-automation-condition-row";
|
import "./ha-automation-condition-row";
|
||||||
import type HaAutomationConditionRow from "./ha-automation-condition-row";
|
import type HaAutomationConditionRow from "./ha-automation-condition-row";
|
||||||
@@ -49,6 +52,7 @@ import "./types/ha-automation-condition-template";
|
|||||||
import "./types/ha-automation-condition-time";
|
import "./types/ha-automation-condition-time";
|
||||||
import "./types/ha-automation-condition-trigger";
|
import "./types/ha-automation-condition-trigger";
|
||||||
import "./types/ha-automation-condition-zone";
|
import "./types/ha-automation-condition-zone";
|
||||||
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
|
|
||||||
const PASTE_VALUE = "__paste__";
|
const PASTE_VALUE = "__paste__";
|
||||||
|
|
||||||
@@ -64,7 +68,13 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public reOrderMode = false;
|
@property({ type: Boolean }) public reOrderMode = false;
|
||||||
|
|
||||||
@property() public clipboard?: Clipboard;
|
@storage({
|
||||||
|
key: "automationClipboard",
|
||||||
|
state: true,
|
||||||
|
subscribe: true,
|
||||||
|
storage: "sessionStorage",
|
||||||
|
})
|
||||||
|
public _clipboard?: AutomationClipboard;
|
||||||
|
|
||||||
private _focusLastConditionOnChange = false;
|
private _focusLastConditionOnChange = false;
|
||||||
|
|
||||||
@@ -157,7 +167,6 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
@move-condition=${this._move}
|
@move-condition=${this._move}
|
||||||
@value-changed=${this._conditionChanged}
|
@value-changed=${this._conditionChanged}
|
||||||
@re-order=${this._enterReOrderMode}
|
@re-order=${this._enterReOrderMode}
|
||||||
.clipboard=${this.clipboard}
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
>
|
>
|
||||||
${this.reOrderMode
|
${this.reOrderMode
|
||||||
@@ -206,13 +215,13 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
>
|
>
|
||||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||||
</ha-button>
|
</ha-button>
|
||||||
${this.clipboard?.condition
|
${this._clipboard?.condition
|
||||||
? html` <mwc-list-item .value=${PASTE_VALUE} graphic="icon">
|
? html` <mwc-list-item .value=${PASTE_VALUE} graphic="icon">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.conditions.paste"
|
"ui.panel.config.automation.editor.conditions.paste"
|
||||||
)}
|
)}
|
||||||
(${this.hass.localize(
|
(${this.hass.localize(
|
||||||
`ui.panel.config.automation.editor.conditions.type.${this.clipboard.condition.condition}.label`
|
`ui.panel.config.automation.editor.conditions.type.${this._clipboard.condition.condition}.label`
|
||||||
)})
|
)})
|
||||||
<ha-svg-icon slot="graphic" .path=${mdiContentPaste}></ha-svg-icon
|
<ha-svg-icon slot="graphic" .path=${mdiContentPaste}></ha-svg-icon
|
||||||
></mwc-list-item>`
|
></mwc-list-item>`
|
||||||
@@ -281,7 +290,9 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
|
|
||||||
let conditions: Condition[];
|
let conditions: Condition[];
|
||||||
if (value === PASTE_VALUE) {
|
if (value === PASTE_VALUE) {
|
||||||
conditions = this.conditions.concat(deepClone(this.clipboard!.condition));
|
conditions = this.conditions.concat(
|
||||||
|
deepClone(this._clipboard!.condition)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const condition = value as Condition["condition"];
|
const condition = value as Condition["condition"];
|
||||||
|
|
||||||
|
@@ -1,10 +1,7 @@
|
|||||||
import { html, LitElement } from "lit";
|
import { html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
import type {
|
import type { LogicalCondition } from "../../../../../data/automation";
|
||||||
LogicalCondition,
|
|
||||||
Clipboard,
|
|
||||||
} from "../../../../../data/automation";
|
|
||||||
import type { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import "../ha-automation-condition";
|
import "../ha-automation-condition";
|
||||||
import type { ConditionElement } from "../ha-automation-condition-row";
|
import type { ConditionElement } from "../ha-automation-condition-row";
|
||||||
@@ -19,8 +16,6 @@ export class HaLogicalCondition extends LitElement implements ConditionElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public reOrderMode = false;
|
@property({ type: Boolean }) public reOrderMode = false;
|
||||||
|
|
||||||
@property() public clipboard?: Clipboard;
|
|
||||||
|
|
||||||
public static get defaultConfig() {
|
public static get defaultConfig() {
|
||||||
return {
|
return {
|
||||||
conditions: [],
|
conditions: [],
|
||||||
@@ -35,7 +30,6 @@ export class HaLogicalCondition extends LitElement implements ConditionElement {
|
|||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.clipboard=${this.clipboard}
|
|
||||||
.reOrderMode=${this.reOrderMode}
|
.reOrderMode=${this.reOrderMode}
|
||||||
></ha-automation-condition>
|
></ha-automation-condition>
|
||||||
`;
|
`;
|
||||||
|
@@ -11,17 +11,19 @@ import {
|
|||||||
mdiPlay,
|
mdiPlay,
|
||||||
mdiPlayCircleOutline,
|
mdiPlayCircleOutline,
|
||||||
mdiRenameBox,
|
mdiRenameBox,
|
||||||
|
mdiRobotConfused,
|
||||||
mdiStopCircleOutline,
|
mdiStopCircleOutline,
|
||||||
mdiTransitConnection,
|
mdiTransitConnection,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
|
nothing,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { property, query, state } from "lit/decorators";
|
import { property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
@@ -46,10 +48,7 @@ import {
|
|||||||
saveAutomationConfig,
|
saveAutomationConfig,
|
||||||
showAutomationEditor,
|
showAutomationEditor,
|
||||||
triggerAutomationActions,
|
triggerAutomationActions,
|
||||||
Trigger,
|
|
||||||
Condition,
|
|
||||||
} from "../../../data/automation";
|
} from "../../../data/automation";
|
||||||
import { Action } from "../../../data/script";
|
|
||||||
import { fetchEntityRegistry } from "../../../data/entity_registry";
|
import { fetchEntityRegistry } from "../../../data/entity_registry";
|
||||||
import {
|
import {
|
||||||
showAlertDialog,
|
showAlertDialog,
|
||||||
@@ -65,6 +64,8 @@ import { showAutomationModeDialog } from "./automation-mode-dialog/show-dialog-a
|
|||||||
import { showAutomationRenameDialog } from "./automation-rename-dialog/show-dialog-automation-rename";
|
import { showAutomationRenameDialog } from "./automation-rename-dialog/show-dialog-automation-rename";
|
||||||
import "./blueprint-automation-editor";
|
import "./blueprint-automation-editor";
|
||||||
import "./manual-automation-editor";
|
import "./manual-automation-editor";
|
||||||
|
import { UNAVAILABLE } from "../../../data/entity";
|
||||||
|
import { validateConfig } from "../../../data/config";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@@ -79,11 +80,6 @@ declare global {
|
|||||||
"ui-mode-not-available": Error;
|
"ui-mode-not-available": Error;
|
||||||
duplicate: undefined;
|
duplicate: undefined;
|
||||||
"re-order": undefined;
|
"re-order": undefined;
|
||||||
"set-clipboard": {
|
|
||||||
trigger?: Trigger;
|
|
||||||
condition?: Condition;
|
|
||||||
action?: Action;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +110,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _readOnly = false;
|
@state() private _readOnly = false;
|
||||||
|
|
||||||
|
@state() private _validationErrors?: (string | TemplateResult)[];
|
||||||
|
|
||||||
@query("ha-yaml-editor", true) private _yamlEditor?: HaYamlEditor;
|
@query("ha-yaml-editor", true) private _yamlEditor?: HaYamlEditor;
|
||||||
|
|
||||||
private _configSubscriptions: Record<
|
private _configSubscriptions: Record<
|
||||||
@@ -299,9 +297,22 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
})}"
|
})}"
|
||||||
@subscribe-automation-config=${this._subscribeAutomationConfig}
|
@subscribe-automation-config=${this._subscribeAutomationConfig}
|
||||||
>
|
>
|
||||||
${this._errors
|
${this._errors || stateObj?.state === UNAVAILABLE
|
||||||
? html`<ha-alert alert-type="error">
|
? html`<ha-alert
|
||||||
${this._errors}
|
alert-type="error"
|
||||||
|
.title=${stateObj?.state === UNAVAILABLE
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.unavailable"
|
||||||
|
)
|
||||||
|
: undefined}
|
||||||
|
>
|
||||||
|
${this._errors || this._validationErrors}
|
||||||
|
${stateObj?.state === UNAVAILABLE
|
||||||
|
? html`<ha-svg-icon
|
||||||
|
slot="icon"
|
||||||
|
.path=${mdiRobotConfused}
|
||||||
|
></ha-svg-icon>`
|
||||||
|
: nothing}
|
||||||
</ha-alert>`
|
</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
${this._mode === "gui"
|
${this._mode === "gui"
|
||||||
@@ -435,6 +446,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
if (changedProps.has("entityId") && this.entityId) {
|
if (changedProps.has("entityId") && this.entityId) {
|
||||||
getAutomationStateConfig(this.hass, this.entityId).then((c) => {
|
getAutomationStateConfig(this.hass, this.entityId).then((c) => {
|
||||||
this._config = this._normalizeConfig(c.config);
|
this._config = this._normalizeConfig(c.config);
|
||||||
|
this._checkValidation();
|
||||||
});
|
});
|
||||||
this._entityId = this.entityId;
|
this._entityId = this.entityId;
|
||||||
this._dirty = false;
|
this._dirty = false;
|
||||||
@@ -463,6 +475,30 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
this._entityId = automation?.entity_id;
|
this._entityId = automation?.entity_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _checkValidation() {
|
||||||
|
this._validationErrors = undefined;
|
||||||
|
if (!this._entityId || !this._config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const stateObj = this.hass.states[this._entityId];
|
||||||
|
if (stateObj?.state !== UNAVAILABLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const validation = await validateConfig(this.hass, {
|
||||||
|
trigger: this._config.trigger,
|
||||||
|
condition: this._config.condition,
|
||||||
|
action: this._config.action,
|
||||||
|
});
|
||||||
|
this._validationErrors = Object.entries(validation).map(([key, value]) =>
|
||||||
|
value.valid
|
||||||
|
? ""
|
||||||
|
: html`${this.hass.localize(
|
||||||
|
`ui.panel.config.automation.editor.${key}s.header`
|
||||||
|
)}:
|
||||||
|
${value.error}<br />`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private _normalizeConfig(config: AutomationConfig): AutomationConfig {
|
private _normalizeConfig(config: AutomationConfig): AutomationConfig {
|
||||||
// Normalize data: ensure trigger, action and condition are lists
|
// Normalize data: ensure trigger, action and condition are lists
|
||||||
// Happens when people copy paste their automations into the config
|
// Happens when people copy paste their automations into the config
|
||||||
@@ -484,6 +520,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
this._dirty = false;
|
this._dirty = false;
|
||||||
this._readOnly = false;
|
this._readOnly = false;
|
||||||
this._config = this._normalizeConfig(config);
|
this._config = this._normalizeConfig(config);
|
||||||
|
this._checkValidation();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const entityRegistry = await fetchEntityRegistry(this.hass.connection);
|
const entityRegistry = await fetchEntityRegistry(this.hass.connection);
|
||||||
const entity = entityRegistry.find(
|
const entity = entityRegistry.find(
|
||||||
@@ -694,6 +731,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
await this._promptAutomationAlias();
|
await this._promptAutomationAlias();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._validationErrors = undefined;
|
||||||
try {
|
try {
|
||||||
await saveAutomationConfig(this.hass, id, this._config!);
|
await saveAutomationConfig(this.hass, id, this._config!);
|
||||||
} catch (errors: any) {
|
} catch (errors: any) {
|
||||||
|
@@ -15,6 +15,7 @@ import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { differenceInDays } from "date-fns/esm";
|
import { differenceInDays } from "date-fns/esm";
|
||||||
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
|
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
|
||||||
import { relativeTime } from "../../../common/datetime/relative_time";
|
import { relativeTime } from "../../../common/datetime/relative_time";
|
||||||
@@ -52,6 +53,7 @@ import { configSections } from "../ha-panel-config";
|
|||||||
import { showNewAutomationDialog } from "./show-dialog-new-automation";
|
import { showNewAutomationDialog } from "./show-dialog-new-automation";
|
||||||
import { findRelated } from "../../../data/search";
|
import { findRelated } from "../../../data/search";
|
||||||
import { fetchBlueprints } from "../../../data/blueprint";
|
import { fetchBlueprints } from "../../../data/blueprint";
|
||||||
|
import { UNAVAILABLE } from "../../../data/entity";
|
||||||
|
|
||||||
@customElement("ha-automation-picker")
|
@customElement("ha-automation-picker")
|
||||||
class HaAutomationPicker extends LitElement {
|
class HaAutomationPicker extends LitElement {
|
||||||
@@ -106,7 +108,15 @@ class HaAutomationPicker extends LitElement {
|
|||||||
),
|
),
|
||||||
type: "icon",
|
type: "icon",
|
||||||
template: (_, automation) =>
|
template: (_, automation) =>
|
||||||
html`<ha-state-icon .state=${automation}></ha-state-icon>`,
|
html`<ha-state-icon
|
||||||
|
.state=${automation}
|
||||||
|
style=${styleMap({
|
||||||
|
color:
|
||||||
|
automation.state === UNAVAILABLE
|
||||||
|
? "var(--error-color)"
|
||||||
|
: "unset",
|
||||||
|
})}
|
||||||
|
></ha-state-icon>`,
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
title: this.hass.localize(
|
title: this.hass.localize(
|
||||||
|
@@ -3,7 +3,6 @@ import { mdiHelpCircle } from "@mdi/js";
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import deepClone from "deep-clone-simple";
|
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
@@ -11,7 +10,6 @@ import {
|
|||||||
Condition,
|
Condition,
|
||||||
ManualAutomationConfig,
|
ManualAutomationConfig,
|
||||||
Trigger,
|
Trigger,
|
||||||
Clipboard,
|
|
||||||
} from "../../../data/automation";
|
} from "../../../data/automation";
|
||||||
import { Action } from "../../../data/script";
|
import { Action } from "../../../data/script";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
@@ -20,7 +18,6 @@ import { documentationUrl } from "../../../util/documentation-url";
|
|||||||
import "./action/ha-automation-action";
|
import "./action/ha-automation-action";
|
||||||
import "./condition/ha-automation-condition";
|
import "./condition/ha-automation-condition";
|
||||||
import "./trigger/ha-automation-trigger";
|
import "./trigger/ha-automation-trigger";
|
||||||
import { storage } from "../../../common/decorators/storage";
|
|
||||||
|
|
||||||
@customElement("manual-automation-editor")
|
@customElement("manual-automation-editor")
|
||||||
export class HaManualAutomationEditor extends LitElement {
|
export class HaManualAutomationEditor extends LitElement {
|
||||||
@@ -36,14 +33,6 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "automationClipboard",
|
|
||||||
state: true,
|
|
||||||
subscribe: false,
|
|
||||||
storage: "sessionStorage",
|
|
||||||
})
|
|
||||||
private _clipboard: Clipboard = {};
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
${this.disabled
|
${this.disabled
|
||||||
@@ -102,8 +91,6 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
@value-changed=${this._triggerChanged}
|
@value-changed=${this._triggerChanged}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@set-clipboard=${this._setClipboard}
|
|
||||||
.clipboard=${this._clipboard}
|
|
||||||
></ha-automation-trigger>
|
></ha-automation-trigger>
|
||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
@@ -133,8 +120,6 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
@value-changed=${this._conditionChanged}
|
@value-changed=${this._conditionChanged}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@set-clipboard=${this._setClipboard}
|
|
||||||
.clipboard=${this._clipboard}
|
|
||||||
></ha-automation-condition>
|
></ha-automation-condition>
|
||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
@@ -167,8 +152,6 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@set-clipboard=${this._setClipboard}
|
|
||||||
.clipboard=${this._clipboard}
|
|
||||||
></ha-automation-action>
|
></ha-automation-action>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -180,11 +163,6 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setClipboard(ev: CustomEvent): void {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this._clipboard = { ...this._clipboard, ...deepClone(ev.detail) };
|
|
||||||
}
|
|
||||||
|
|
||||||
private _conditionChanged(ev: CustomEvent): void {
|
private _conditionChanged(ev: CustomEvent): void {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
|
@@ -3,9 +3,9 @@ import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import {
|
import {
|
||||||
mdiCheck,
|
mdiCheck,
|
||||||
mdiContentDuplicate,
|
|
||||||
mdiContentCopy,
|
mdiContentCopy,
|
||||||
mdiContentCut,
|
mdiContentCut,
|
||||||
|
mdiContentDuplicate,
|
||||||
mdiDelete,
|
mdiDelete,
|
||||||
mdiDotsVertical,
|
mdiDotsVertical,
|
||||||
mdiIdentifier,
|
mdiIdentifier,
|
||||||
@@ -15,9 +15,10 @@ import {
|
|||||||
mdiStopCircleOutline,
|
mdiStopCircleOutline,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
||||||
@@ -30,7 +31,8 @@ import "../../../../components/ha-expansion-panel";
|
|||||||
import "../../../../components/ha-icon-button";
|
import "../../../../components/ha-icon-button";
|
||||||
import "../../../../components/ha-textfield";
|
import "../../../../components/ha-textfield";
|
||||||
import { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
import { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
||||||
import { subscribeTrigger, Trigger } from "../../../../data/automation";
|
import type { AutomationClipboard } from "../../../../data/automation";
|
||||||
|
import { Trigger, subscribeTrigger } from "../../../../data/automation";
|
||||||
import { describeTrigger } from "../../../../data/automation_i18n";
|
import { describeTrigger } from "../../../../data/automation_i18n";
|
||||||
import { validateConfig } from "../../../../data/config";
|
import { validateConfig } from "../../../../data/config";
|
||||||
import { fullEntitiesContext } from "../../../../data/context";
|
import { fullEntitiesContext } from "../../../../data/context";
|
||||||
@@ -51,6 +53,7 @@ import "./types/ha-automation-trigger-homeassistant";
|
|||||||
import "./types/ha-automation-trigger-mqtt";
|
import "./types/ha-automation-trigger-mqtt";
|
||||||
import "./types/ha-automation-trigger-numeric_state";
|
import "./types/ha-automation-trigger-numeric_state";
|
||||||
import "./types/ha-automation-trigger-persistent_notification";
|
import "./types/ha-automation-trigger-persistent_notification";
|
||||||
|
import "./types/ha-automation-trigger-conversation";
|
||||||
import "./types/ha-automation-trigger-state";
|
import "./types/ha-automation-trigger-state";
|
||||||
import "./types/ha-automation-trigger-sun";
|
import "./types/ha-automation-trigger-sun";
|
||||||
import "./types/ha-automation-trigger-tag";
|
import "./types/ha-automation-trigger-tag";
|
||||||
@@ -110,6 +113,14 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||||||
|
|
||||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||||
|
|
||||||
|
@storage({
|
||||||
|
key: "automationClipboard",
|
||||||
|
state: false,
|
||||||
|
subscribe: true,
|
||||||
|
storage: "sessionStorage",
|
||||||
|
})
|
||||||
|
public _clipboard?: AutomationClipboard;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||||
_entityReg!: EntityRegistryEntry[];
|
_entityReg!: EntityRegistryEntry[];
|
||||||
@@ -469,10 +480,10 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||||||
fireEvent(this, "duplicate");
|
fireEvent(this, "duplicate");
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
fireEvent(this, "set-clipboard", { trigger: this.trigger });
|
this._setClipboard();
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
fireEvent(this, "set-clipboard", { trigger: this.trigger });
|
this._setClipboard();
|
||||||
fireEvent(this, "value-changed", { value: null });
|
fireEvent(this, "value-changed", { value: null });
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
@@ -492,6 +503,13 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _setClipboard() {
|
||||||
|
this._clipboard = {
|
||||||
|
...this._clipboard,
|
||||||
|
trigger: this.trigger,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private _onDelete() {
|
private _onDelete() {
|
||||||
showConfirmationDialog(this, {
|
showConfirmationDialog(this, {
|
||||||
title: this.hass.localize(
|
title: this.hass.localize(
|
||||||
|
@@ -27,7 +27,7 @@ import "../../../../components/ha-button-menu";
|
|||||||
import "../../../../components/ha-button";
|
import "../../../../components/ha-button";
|
||||||
import type { HaSelect } from "../../../../components/ha-select";
|
import type { HaSelect } from "../../../../components/ha-select";
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
import { Trigger, Clipboard } from "../../../../data/automation";
|
import { Trigger, AutomationClipboard } from "../../../../data/automation";
|
||||||
import { TRIGGER_TYPES } from "../../../../data/trigger";
|
import { TRIGGER_TYPES } from "../../../../data/trigger";
|
||||||
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||||
import { SortableInstance } from "../../../../resources/sortable";
|
import { SortableInstance } from "../../../../resources/sortable";
|
||||||
@@ -43,6 +43,7 @@ import "./types/ha-automation-trigger-homeassistant";
|
|||||||
import "./types/ha-automation-trigger-mqtt";
|
import "./types/ha-automation-trigger-mqtt";
|
||||||
import "./types/ha-automation-trigger-numeric_state";
|
import "./types/ha-automation-trigger-numeric_state";
|
||||||
import "./types/ha-automation-trigger-persistent_notification";
|
import "./types/ha-automation-trigger-persistent_notification";
|
||||||
|
import "./types/ha-automation-trigger-conversation";
|
||||||
import "./types/ha-automation-trigger-state";
|
import "./types/ha-automation-trigger-state";
|
||||||
import "./types/ha-automation-trigger-sun";
|
import "./types/ha-automation-trigger-sun";
|
||||||
import "./types/ha-automation-trigger-tag";
|
import "./types/ha-automation-trigger-tag";
|
||||||
@@ -51,6 +52,7 @@ import "./types/ha-automation-trigger-time";
|
|||||||
import "./types/ha-automation-trigger-time_pattern";
|
import "./types/ha-automation-trigger-time_pattern";
|
||||||
import "./types/ha-automation-trigger-webhook";
|
import "./types/ha-automation-trigger-webhook";
|
||||||
import "./types/ha-automation-trigger-zone";
|
import "./types/ha-automation-trigger-zone";
|
||||||
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
|
|
||||||
const PASTE_VALUE = "__paste__";
|
const PASTE_VALUE = "__paste__";
|
||||||
|
|
||||||
@@ -66,7 +68,13 @@ export default class HaAutomationTrigger extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public reOrderMode = false;
|
@property({ type: Boolean }) public reOrderMode = false;
|
||||||
|
|
||||||
@property() public clipboard?: Clipboard;
|
@storage({
|
||||||
|
key: "automationClipboard",
|
||||||
|
state: true,
|
||||||
|
subscribe: true,
|
||||||
|
storage: "sessionStorage",
|
||||||
|
})
|
||||||
|
public _clipboard?: AutomationClipboard;
|
||||||
|
|
||||||
private _focusLastTriggerOnChange = false;
|
private _focusLastTriggerOnChange = false;
|
||||||
|
|
||||||
@@ -155,13 +163,13 @@ export default class HaAutomationTrigger extends LitElement {
|
|||||||
>
|
>
|
||||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||||
</ha-button>
|
</ha-button>
|
||||||
${this.clipboard?.trigger
|
${this._clipboard?.trigger
|
||||||
? html` <mwc-list-item .value=${PASTE_VALUE} graphic="icon">
|
? html` <mwc-list-item .value=${PASTE_VALUE} graphic="icon">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.triggers.paste"
|
"ui.panel.config.automation.editor.triggers.paste"
|
||||||
)}
|
)}
|
||||||
(${this.hass.localize(
|
(${this.hass.localize(
|
||||||
`ui.panel.config.automation.editor.triggers.type.${this.clipboard.trigger.platform}.label`
|
`ui.panel.config.automation.editor.triggers.type.${this._clipboard.trigger.platform}.label`
|
||||||
)})
|
)})
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
slot="graphic"
|
slot="graphic"
|
||||||
@@ -259,7 +267,7 @@ export default class HaAutomationTrigger extends LitElement {
|
|||||||
|
|
||||||
let triggers: Trigger[];
|
let triggers: Trigger[];
|
||||||
if (value === PASTE_VALUE) {
|
if (value === PASTE_VALUE) {
|
||||||
triggers = this.triggers.concat(deepClone(this.clipboard!.trigger));
|
triggers = this.triggers.concat(deepClone(this._clipboard!.trigger));
|
||||||
} else {
|
} else {
|
||||||
const platform = value as Trigger["platform"];
|
const platform = value as Trigger["platform"];
|
||||||
|
|
||||||
|
@@ -0,0 +1,174 @@
|
|||||||
|
import { mdiClose } from "@mdi/js";
|
||||||
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property, query } from "lit/decorators";
|
||||||
|
import { ensureArray } from "../../../../../common/array/ensure-array";
|
||||||
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import "../../../../../components/ha-textfield";
|
||||||
|
import type { HaTextField } from "../../../../../components/ha-textfield";
|
||||||
|
import { ConversationTrigger } from "../../../../../data/automation";
|
||||||
|
import { showConfirmationDialog } from "../../../../../dialogs/generic/show-dialog-box";
|
||||||
|
import { HomeAssistant } from "../../../../../types";
|
||||||
|
import { TriggerElement } from "../ha-automation-trigger-row";
|
||||||
|
|
||||||
|
@customElement("ha-automation-trigger-conversation")
|
||||||
|
export class HaConversationTrigger
|
||||||
|
extends LitElement
|
||||||
|
implements TriggerElement
|
||||||
|
{
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public trigger!: ConversationTrigger;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@query("#option_input", true) private _optionInput?: HaTextField;
|
||||||
|
|
||||||
|
public static get defaultConfig(): Omit<ConversationTrigger, "platform"> {
|
||||||
|
return { command: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const { command } = this.trigger;
|
||||||
|
const commands = command ? ensureArray(command) : [];
|
||||||
|
|
||||||
|
return html`${commands.length
|
||||||
|
? commands.map(
|
||||||
|
(option, index) => html`
|
||||||
|
<ha-textfield
|
||||||
|
class="option"
|
||||||
|
iconTrailing
|
||||||
|
.index=${index}
|
||||||
|
.value=${option}
|
||||||
|
@change=${this._updateOption}
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
@click=${this._removeOption}
|
||||||
|
slot="trailingIcon"
|
||||||
|
.path=${mdiClose}
|
||||||
|
></ha-icon-button>
|
||||||
|
</ha-textfield>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
: nothing}
|
||||||
|
<ha-textfield
|
||||||
|
class="flex-auto"
|
||||||
|
id="option_input"
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.triggers.type.conversation.add_sentence"
|
||||||
|
)}
|
||||||
|
@keydown=${this._handleKeyAdd}
|
||||||
|
@change=${this._addOption}
|
||||||
|
></ha-textfield>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleKeyAdd(ev: KeyboardEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (ev.key !== "Enter") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._addOption();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addOption() {
|
||||||
|
const input = this._optionInput;
|
||||||
|
if (!input?.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this.trigger,
|
||||||
|
command: this.trigger.command.length
|
||||||
|
? [
|
||||||
|
...(Array.isArray(this.trigger.command)
|
||||||
|
? this.trigger.command
|
||||||
|
: [this.trigger.command]),
|
||||||
|
input.value,
|
||||||
|
]
|
||||||
|
: input.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
input.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _updateOption(ev: Event) {
|
||||||
|
const index = (ev.target as any).index;
|
||||||
|
const command = [...this.trigger.command];
|
||||||
|
command.splice(index, 1, (ev.target as HaTextField).value);
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: { ...this.trigger, command },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _removeOption(ev: Event) {
|
||||||
|
const index = (ev.target as any).parentElement.index;
|
||||||
|
if (
|
||||||
|
!(await showConfirmationDialog(this, {
|
||||||
|
title: this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.triggers.type.conversation.delete"
|
||||||
|
),
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.triggers.type.conversation.confirm_delete"
|
||||||
|
),
|
||||||
|
destructive: true,
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let command: string[] | string;
|
||||||
|
if (!Array.isArray(this.trigger.command)) {
|
||||||
|
command = "";
|
||||||
|
} else {
|
||||||
|
command = [...this.trigger.command];
|
||||||
|
command.splice(index, 1);
|
||||||
|
}
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: { ...this.trigger, command },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
.layout {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
.option {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
mwc-button {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
ha-textfield {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
--textfield-icon-trailing-padding: 0;
|
||||||
|
}
|
||||||
|
ha-textfield > ha-icon-button {
|
||||||
|
position: relative;
|
||||||
|
right: -8px;
|
||||||
|
--mdc-icon-button-size: 36px;
|
||||||
|
--mdc-icon-size: 20px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
inset-inline-start: initial;
|
||||||
|
inset-inline-end: -8px;
|
||||||
|
direction: var(--direction);
|
||||||
|
}
|
||||||
|
#option_input {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-trigger-conversation": HaConversationTrigger;
|
||||||
|
}
|
||||||
|
}
|
@@ -96,6 +96,7 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
type,
|
type,
|
||||||
error: true,
|
error: true,
|
||||||
path,
|
path,
|
||||||
|
fullpath: `${type}/${path}`,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
result.push({
|
result.push({
|
||||||
@@ -103,6 +104,7 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
type,
|
type,
|
||||||
error: false,
|
error: false,
|
||||||
path,
|
path,
|
||||||
|
fullpath: `${type}/${path}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -154,6 +156,10 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
direction: "asc",
|
direction: "asc",
|
||||||
width: "25%",
|
width: "25%",
|
||||||
},
|
},
|
||||||
|
fullpath: {
|
||||||
|
title: "fullpath",
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
actions: {
|
actions: {
|
||||||
title: "",
|
title: "",
|
||||||
width: this.narrow ? undefined : "10%",
|
width: this.narrow ? undefined : "10%",
|
||||||
@@ -233,7 +239,7 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
.tabs=${configSections.automations}
|
.tabs=${configSections.automations}
|
||||||
.columns=${this._columns(this.narrow, this.hass.language)}
|
.columns=${this._columns(this.narrow, this.hass.language)}
|
||||||
.data=${this._processedBlueprints(this.blueprints)}
|
.data=${this._processedBlueprints(this.blueprints)}
|
||||||
id="path"
|
id="fullpath"
|
||||||
.noDataText=${this.hass.localize(
|
.noDataText=${this.hass.localize(
|
||||||
"ui.panel.config.blueprint.overview.no_blueprints"
|
"ui.panel.config.blueprint.overview.no_blueprints"
|
||||||
)}
|
)}
|
||||||
@@ -318,7 +324,7 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
|
|
||||||
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
|
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
|
||||||
const blueprint = this._processedBlueprints(this.blueprints).find(
|
const blueprint = this._processedBlueprints(this.blueprints).find(
|
||||||
(b) => b.path === ev.detail.id
|
(b) => b.fullpath === ev.detail.id
|
||||||
);
|
);
|
||||||
if (blueprint.error) {
|
if (blueprint.error) {
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
|
@@ -67,7 +67,9 @@ class DialogDeviceRegistryDetail extends LitElement {
|
|||||||
<ha-textfield
|
<ha-textfield
|
||||||
.value=${this._nameByUser}
|
.value=${this._nameByUser}
|
||||||
@input=${this._nameChanged}
|
@input=${this._nameChanged}
|
||||||
.label=${this.hass.localize("ui.panel.config.devices.name")}
|
.label=${this.hass.localize(
|
||||||
|
"ui.dialogs.device-registry-detail.name"
|
||||||
|
)}
|
||||||
.placeholder=${device.name || ""}
|
.placeholder=${device.name || ""}
|
||||||
.disabled=${this._submitting}
|
.disabled=${this._submitting}
|
||||||
dialogInitialFocus
|
dialogInitialFocus
|
||||||
@@ -87,10 +89,10 @@ class DialogDeviceRegistryDetail extends LitElement {
|
|||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.devices.enabled_label",
|
"ui.dialogs.device-registry-detail.enabled_label",
|
||||||
"type",
|
"type",
|
||||||
this.hass.localize(
|
this.hass.localize(
|
||||||
`ui.panel.config.devices.type.${
|
`ui.dialogs.device-registry-detail.type.${
|
||||||
device.entry_type || "device"
|
device.entry_type || "device"
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
@@ -99,10 +101,10 @@ class DialogDeviceRegistryDetail extends LitElement {
|
|||||||
<div class="secondary">
|
<div class="secondary">
|
||||||
${this._disabledBy && this._disabledBy !== "user"
|
${this._disabledBy && this._disabledBy !== "user"
|
||||||
? this.hass.localize(
|
? this.hass.localize(
|
||||||
"ui.panel.config.devices.enabled_cause",
|
"ui.dialogs.device-registry-detail.enabled_cause",
|
||||||
"type",
|
"type",
|
||||||
this.hass.localize(
|
this.hass.localize(
|
||||||
`ui.panel.config.devices.type.${
|
`ui.dialogs.device-registry-detail.type.${
|
||||||
device.entry_type || "device"
|
device.entry_type || "device"
|
||||||
}`
|
}`
|
||||||
),
|
),
|
||||||
@@ -113,7 +115,7 @@ class DialogDeviceRegistryDetail extends LitElement {
|
|||||||
)
|
)
|
||||||
: ""}
|
: ""}
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.devices.enabled_description"
|
"ui.dialogs.device-registry-detail.enabled_description"
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -132,7 +134,7 @@ class DialogDeviceRegistryDetail extends LitElement {
|
|||||||
@click=${this._updateEntry}
|
@click=${this._updateEntry}
|
||||||
.disabled=${this._submitting}
|
.disabled=${this._submitting}
|
||||||
>
|
>
|
||||||
${this.hass.localize("ui.panel.config.devices.update")}
|
${this.hass.localize("ui.dialogs.device-registry-detail.update")}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
</ha-dialog>
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
@@ -163,7 +165,7 @@ class DialogDeviceRegistryDetail extends LitElement {
|
|||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this._error =
|
this._error =
|
||||||
err.message ||
|
err.message ||
|
||||||
this.hass.localize("ui.panel.config.devices.unknown_error");
|
this.hass.localize("ui.dialogs.device-registry-detail.unknown_error");
|
||||||
} finally {
|
} finally {
|
||||||
this._submitting = false;
|
this._submitting = false;
|
||||||
}
|
}
|
||||||
|
@@ -11,10 +11,12 @@ import {
|
|||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
|
import { mdiContentCopy } from "@mdi/js";
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
|
import { computeObjectId } from "../../../common/entity/compute_object_id";
|
||||||
import { domainIcon } from "../../../common/entity/domain_icon";
|
import { domainIcon } from "../../../common/entity/domain_icon";
|
||||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
import { formatNumber } from "../../../common/number/format_number";
|
import { formatNumber } from "../../../common/number/format_number";
|
||||||
@@ -79,6 +81,8 @@ import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info
|
|||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import { showDeviceRegistryDetailDialog } from "../devices/device-registry-detail/show-dialog-device-registry-detail";
|
import { showDeviceRegistryDetailDialog } from "../devices/device-registry-detail/show-dialog-device-registry-detail";
|
||||||
|
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||||
|
import { showToast } from "../../../util/toast";
|
||||||
|
|
||||||
const OVERRIDE_DEVICE_CLASSES = {
|
const OVERRIDE_DEVICE_CLASSES = {
|
||||||
cover: [
|
cover: [
|
||||||
@@ -325,8 +329,6 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
|||||||
|
|
||||||
const domain = computeDomain(this.entry.entity_id);
|
const domain = computeDomain(this.entry.entity_id);
|
||||||
|
|
||||||
const invalidDomainUpdate = computeDomain(this._entityId.trim()) !== domain;
|
|
||||||
|
|
||||||
const invalidDefaultCode =
|
const invalidDefaultCode =
|
||||||
domain === "lock" &&
|
domain === "lock" &&
|
||||||
this._isInvalidDefaultCode(
|
this._isInvalidDefaultCode(
|
||||||
@@ -675,15 +677,23 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
|||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
<ha-textfield
|
<ha-textfield
|
||||||
error-message="Domain needs to stay the same"
|
class="entityId"
|
||||||
.value=${this._entityId}
|
.value=${computeObjectId(this._entityId)}
|
||||||
|
.prefix=${domain + "."}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.dialogs.entity_registry.editor.entity_id"
|
"ui.dialogs.entity_registry.editor.entity_id"
|
||||||
)}
|
)}
|
||||||
.invalid=${invalidDomainUpdate}
|
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
|
required
|
||||||
@input=${this._entityIdChanged}
|
@input=${this._entityIdChanged}
|
||||||
></ha-textfield>
|
iconTrailing
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
@click=${this._copyEntityId}
|
||||||
|
slot="trailingIcon"
|
||||||
|
.path=${mdiContentCopy}
|
||||||
|
></ha-icon-button>
|
||||||
|
</ha-textfield>
|
||||||
${!this.entry.device_id
|
${!this.entry.device_id
|
||||||
? html`<ha-area-picker
|
? html`<ha-area-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -1161,9 +1171,16 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
|||||||
this._icon = ev.detail.value;
|
this._icon = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _copyEntityId(): Promise<void> {
|
||||||
|
await copyToClipboard(this._entityId);
|
||||||
|
showToast(this, {
|
||||||
|
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private _entityIdChanged(ev): void {
|
private _entityIdChanged(ev): void {
|
||||||
fireEvent(this, "change");
|
fireEvent(this, "change");
|
||||||
this._entityId = ev.target.value;
|
this._entityId = `${computeDomain(this._origEntityId)}.${ev.target.value}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _deviceClassChanged(ev): void {
|
private _deviceClassChanged(ev): void {
|
||||||
@@ -1343,6 +1360,20 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
ha-textfield.entityId {
|
||||||
|
--text-field-prefix-padding-right: 0;
|
||||||
|
--textfield-icon-trailing-padding: 0;
|
||||||
|
}
|
||||||
|
ha-textfield.entityId > ha-icon-button {
|
||||||
|
position: relative;
|
||||||
|
right: -8px;
|
||||||
|
--mdc-icon-button-size: 36px;
|
||||||
|
--mdc-icon-size: 20px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
inset-inline-start: initial;
|
||||||
|
inset-inline-end: -8px;
|
||||||
|
direction: var(--direction);
|
||||||
|
}
|
||||||
ha-switch {
|
ha-switch {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
@@ -898,10 +898,14 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
|||||||
if (!this.domain || !isComponentLoaded(this.hass, "diagnostics")) {
|
if (!this.domain || !isComponentLoaded(this.hass, "diagnostics")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._diagnosticHandler = await fetchDiagnosticHandler(
|
try {
|
||||||
this.hass,
|
this._diagnosticHandler = await fetchDiagnosticHandler(
|
||||||
this.domain
|
this.hass,
|
||||||
);
|
this.domain
|
||||||
|
);
|
||||||
|
} catch (err: any) {
|
||||||
|
// No issue, as diagnostics are not required
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handleEnableDebugLogging() {
|
private async _handleEnableDebugLogging() {
|
||||||
|
@@ -135,13 +135,13 @@ class HaConfigIntegrations extends SubscribeMixin(HassRouterPage) {
|
|||||||
integrations.add(flow.handler);
|
integrations.add(flow.handler);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await this.hass.loadBackendTranslation(
|
const localize = await this.hass.loadBackendTranslation(
|
||||||
"config",
|
"config",
|
||||||
Array.from(integrations)
|
Array.from(integrations)
|
||||||
);
|
);
|
||||||
this._configEntriesInProgress = flowsInProgress.map((flow) => ({
|
this._configEntriesInProgress = flowsInProgress.map((flow) => ({
|
||||||
...flow,
|
...flow,
|
||||||
localized_title: localizeConfigFlowTitle(this.hass.localize, flow),
|
localized_title: localizeConfigFlowTitle(localize, flow),
|
||||||
}));
|
}));
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
@@ -1,19 +1,29 @@
|
|||||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@material/mwc-list";
|
import "@material/mwc-list";
|
||||||
|
import "@material/mwc-ripple";
|
||||||
|
import type { Ripple } from "@material/mwc-ripple";
|
||||||
|
import { RippleHandlers } from "@material/mwc-ripple/ripple-handlers";
|
||||||
|
import { mdiCloud, mdiPackageVariant } from "@mdi/js";
|
||||||
import {
|
import {
|
||||||
mdiCogOutline,
|
CSSResultGroup,
|
||||||
mdiDevices,
|
LitElement,
|
||||||
mdiHandExtendedOutline,
|
TemplateResult,
|
||||||
mdiPuzzleOutline,
|
css,
|
||||||
mdiShapeOutline,
|
html,
|
||||||
} from "@mdi/js";
|
nothing,
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
} from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import {
|
||||||
|
customElement,
|
||||||
|
eventOptions,
|
||||||
|
property,
|
||||||
|
queryAsync,
|
||||||
|
state,
|
||||||
|
} from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
|
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-icon-button";
|
|
||||||
import "../../../components/ha-icon-next";
|
import "../../../components/ha-icon-next";
|
||||||
import "../../../components/ha-list-item";
|
import "../../../components/ha-list-item";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
@@ -47,8 +57,12 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
|
|
||||||
@property() public logInfo?: IntegrationLogInfo;
|
@property() public logInfo?: IntegrationLogInfo;
|
||||||
|
|
||||||
|
@queryAsync("mwc-ripple") private _ripple!: Promise<Ripple | null>;
|
||||||
|
|
||||||
|
@state() private _shouldRenderRipple = false;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const state = this._getState(this.items);
|
const entryState = this._getState(this.items);
|
||||||
|
|
||||||
const debugLoggingEnabled =
|
const debugLoggingEnabled =
|
||||||
this.logInfo && this.logInfo.level === LogSeverity.DEBUG;
|
this.logInfo && this.logInfo.level === LogSeverity.DEBUG;
|
||||||
@@ -57,22 +71,35 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
<ha-card
|
<ha-card
|
||||||
outlined
|
outlined
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
"state-loaded": state === "loaded",
|
"state-loaded": entryState === "loaded",
|
||||||
"state-not-loaded": state === "not_loaded",
|
"state-not-loaded": entryState === "not_loaded",
|
||||||
"state-failed-unload": state === "failed_unload",
|
"state-failed-unload": entryState === "failed_unload",
|
||||||
"state-setup": state === "setup_in_progress",
|
"state-setup": entryState === "setup_in_progress",
|
||||||
"state-error": ERROR_STATES.includes(state),
|
"state-error": ERROR_STATES.includes(entryState),
|
||||||
"debug-logging": Boolean(debugLoggingEnabled),
|
"debug-logging": Boolean(debugLoggingEnabled),
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<a href=${`/config/integrations/integration/${this.domain}`}>
|
<a
|
||||||
|
href=${`/config/integrations/integration/${this.domain}`}
|
||||||
|
class="ripple-anchor"
|
||||||
|
@focus=${this.handleRippleFocus}
|
||||||
|
@blur=${this.handleRippleBlur}
|
||||||
|
@mouseenter=${this.handleRippleMouseEnter}
|
||||||
|
@mouseleave=${this.handleRippleMouseLeave}
|
||||||
|
@mousedown=${this.handleRippleActivate}
|
||||||
|
@mouseup=${this.handleRippleDeactivate}
|
||||||
|
@touchstart=${this.handleRippleActivate}
|
||||||
|
@touchend=${this.handleRippleDeactivate}
|
||||||
|
@touchcancel=${this.handleRippleDeactivate}
|
||||||
|
>
|
||||||
|
${this._shouldRenderRipple ? html`<mwc-ripple></mwc-ripple>` : ""}
|
||||||
<ha-integration-header
|
<ha-integration-header
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.domain=${this.domain}
|
.domain=${this.domain}
|
||||||
.localizedDomainName=${this.items[0].localized_domain_name}
|
.localizedDomainName=${this.items[0].localized_domain_name}
|
||||||
.banner=${state !== "loaded"
|
.banner=${entryState !== "loaded"
|
||||||
? this.hass.localize(
|
? this.hass.localize(
|
||||||
`ui.panel.config.integrations.config_entry.state.${state}`
|
`ui.panel.config.integrations.config_entry.state.${entryState}`
|
||||||
)
|
)
|
||||||
: debugLoggingEnabled
|
: debugLoggingEnabled
|
||||||
? this.hass.localize(
|
? this.hass.localize(
|
||||||
@@ -81,10 +108,12 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
: undefined}
|
: undefined}
|
||||||
.manifest=${this.manifest}
|
.manifest=${this.manifest}
|
||||||
>
|
>
|
||||||
<ha-icon-button
|
<ha-icon-next
|
||||||
slot="header-button"
|
slot="header-button"
|
||||||
.path=${mdiCogOutline}
|
.label=${this.hass.localize(
|
||||||
></ha-icon-button>
|
"ui.panel.config.integrations.config_entry.configure"
|
||||||
|
)}
|
||||||
|
></ha-icon-next>
|
||||||
</ha-integration-header>
|
</ha-integration-header>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -102,18 +131,14 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
const services = !devices.some((device) => device.entry_type !== "service");
|
const services = !devices.some((device) => device.entry_type !== "service");
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="content">
|
<div class="card-actions">
|
||||||
${devices.length > 0
|
${devices.length > 0
|
||||||
? html`<a
|
? html`<a
|
||||||
href=${devices.length === 1
|
href=${devices.length === 1
|
||||||
? `/config/devices/device/${devices[0].id}`
|
? `/config/devices/device/${devices[0].id}`
|
||||||
: `/config/devices/dashboard?historyBack=1&domain=${this.domain}`}
|
: `/config/devices/dashboard?historyBack=1&domain=${this.domain}`}
|
||||||
>
|
>
|
||||||
<ha-list-item hasMeta graphic="icon">
|
<ha-button>
|
||||||
<ha-svg-icon
|
|
||||||
.path=${services ? mdiHandExtendedOutline : mdiDevices}
|
|
||||||
slot="graphic"
|
|
||||||
></ha-svg-icon>
|
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
`ui.panel.config.integrations.config_entry.${
|
`ui.panel.config.integrations.config_entry.${
|
||||||
services ? "services" : "devices"
|
services ? "services" : "devices"
|
||||||
@@ -121,40 +146,57 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
"count",
|
"count",
|
||||||
devices.length
|
devices.length
|
||||||
)}
|
)}
|
||||||
<ha-icon-next slot="meta"></ha-icon-next>
|
</ha-button>
|
||||||
</ha-list-item>
|
|
||||||
</a>`
|
</a>`
|
||||||
: entities.length > 0
|
: entities.length > 0
|
||||||
? html`<a
|
? html`<a
|
||||||
href=${`/config/entities?historyBack=1&domain=${this.domain}`}
|
href=${`/config/entities?historyBack=1&domain=${this.domain}`}
|
||||||
>
|
>
|
||||||
<ha-list-item hasMeta graphic="icon">
|
<ha-button>
|
||||||
<ha-svg-icon
|
|
||||||
.path=${mdiShapeOutline}
|
|
||||||
slot="graphic"
|
|
||||||
></ha-svg-icon>
|
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
`ui.panel.config.integrations.config_entry.entities`,
|
`ui.panel.config.integrations.config_entry.entities`,
|
||||||
"count",
|
"count",
|
||||||
entities.length
|
entities.length
|
||||||
)}
|
)}
|
||||||
<ha-icon-next slot="meta"></ha-icon-next>
|
</ha-button>
|
||||||
</ha-list-item>
|
|
||||||
</a>`
|
</a>`
|
||||||
: html`<a href=${`/config/integrations/integration/${this.domain}`}>
|
: html`<a href=${`/config/integrations/integration/${this.domain}`}>
|
||||||
<ha-list-item hasMeta graphic="icon">
|
<ha-button>
|
||||||
<ha-svg-icon
|
|
||||||
.path=${mdiPuzzleOutline}
|
|
||||||
slot="graphic"
|
|
||||||
></ha-svg-icon>
|
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
`ui.panel.config.integrations.config_entry.entries`,
|
`ui.panel.config.integrations.config_entry.entries`,
|
||||||
"count",
|
"count",
|
||||||
this.items.length
|
this.items.length
|
||||||
)}
|
)}
|
||||||
<ha-icon-next slot="meta"></ha-icon-next>
|
</ha-button>
|
||||||
</ha-list-item>
|
|
||||||
</a>`}
|
</a>`}
|
||||||
|
<div class="icons">
|
||||||
|
${this.manifest && !this.manifest.is_built_in
|
||||||
|
? html`<span class="icon custom">
|
||||||
|
<ha-svg-icon .path=${mdiPackageVariant}></ha-svg-icon>
|
||||||
|
<simple-tooltip
|
||||||
|
animation-delay="0"
|
||||||
|
.position=${computeRTL(this.hass) ? "right" : "left"}
|
||||||
|
offset="4"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.custom_integration"
|
||||||
|
)}</simple-tooltip
|
||||||
|
>
|
||||||
|
</span>`
|
||||||
|
: nothing}
|
||||||
|
${this.manifest && this.manifest.iot_class?.startsWith("cloud_")
|
||||||
|
? html`<div class="icon cloud">
|
||||||
|
<ha-svg-icon .path=${mdiCloud}></ha-svg-icon>
|
||||||
|
<simple-tooltip
|
||||||
|
animation-delay="0"
|
||||||
|
.position=${computeRTL(this.hass) ? "right" : "left"}
|
||||||
|
offset="4"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.depends_on_cloud"
|
||||||
|
)}</simple-tooltip
|
||||||
|
>
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -164,14 +206,14 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
if (configEntry.length === 1) {
|
if (configEntry.length === 1) {
|
||||||
return configEntry[0].state;
|
return configEntry[0].state;
|
||||||
}
|
}
|
||||||
let state: ConfigEntry["state"];
|
let entryState: ConfigEntry["state"];
|
||||||
for (const entry of configEntry) {
|
for (const entry of configEntry) {
|
||||||
if (ERROR_STATES.includes(entry.state)) {
|
if (ERROR_STATES.includes(entry.state)) {
|
||||||
return entry.state;
|
return entry.state;
|
||||||
}
|
}
|
||||||
state = entry.state;
|
entryState = entry.state;
|
||||||
}
|
}
|
||||||
return state!;
|
return entryState!;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -206,6 +248,36 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private _rippleHandlers: RippleHandlers = new RippleHandlers(() => {
|
||||||
|
this._shouldRenderRipple = true;
|
||||||
|
return this._ripple;
|
||||||
|
});
|
||||||
|
|
||||||
|
@eventOptions({ passive: true })
|
||||||
|
private handleRippleActivate(evt?: Event) {
|
||||||
|
this._rippleHandlers.startPress(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleRippleDeactivate() {
|
||||||
|
this._rippleHandlers.endPress();
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleRippleFocus() {
|
||||||
|
this._rippleHandlers.startFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleRippleBlur() {
|
||||||
|
this._rippleHandlers.endFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleRippleMouseEnter() {
|
||||||
|
this._rippleHandlers.startHover();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleRippleMouseLeave() {
|
||||||
|
this._rippleHandlers.endHover();
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
@@ -213,12 +285,25 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
ha-card {
|
ha-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
--state-color: var(--divider-color, #e0e0e0);
|
--state-color: var(--divider-color, #e0e0e0);
|
||||||
--ha-card-border-color: var(--state-color);
|
--ha-card-border-color: var(--state-color);
|
||||||
--state-message-color: var(--state-color);
|
--state-message-color: var(--state-color);
|
||||||
}
|
}
|
||||||
|
.ripple-anchor {
|
||||||
|
flex-grow: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
ha-integration-header {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.card-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
.debug-logging {
|
.debug-logging {
|
||||||
--state-color: var(--warning-color);
|
--state-color: var(--warning-color);
|
||||||
--text-on-state-color: var(--primary-text-color);
|
--text-on-state-color: var(--primary-text-color);
|
||||||
@@ -251,9 +336,32 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
a ha-icon-button {
|
a ha-icon-next {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
.icons {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
border-radius: 50%;
|
||||||
|
color: var(--text-primary-color);
|
||||||
|
padding: 4px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
.icon.cloud {
|
||||||
|
background: var(--info-color);
|
||||||
|
}
|
||||||
|
.icon.custom {
|
||||||
|
background: var(--warning-color);
|
||||||
|
}
|
||||||
|
.icon ha-svg-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
simple-tooltip {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,7 @@
|
|||||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
import { LitElement, TemplateResult, css, html } from "lit";
|
||||||
import { mdiCloud, mdiPackageVariant } from "@mdi/js";
|
|
||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
|
||||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import { domainToName, IntegrationManifest } from "../../../data/integration";
|
import { IntegrationManifest, domainToName } from "../../../data/integration";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { brandsUrl } from "../../../util/brands-url";
|
import { brandsUrl } from "../../../util/brands-url";
|
||||||
|
|
||||||
@@ -23,8 +19,6 @@ export class HaIntegrationHeader extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public manifest?: IntegrationManifest;
|
@property({ attribute: false }) public manifest?: IntegrationManifest;
|
||||||
|
|
||||||
@property({ attribute: false }) public debugLoggingEnabled?: boolean;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
let primary: string;
|
let primary: string;
|
||||||
let secondary: string | undefined;
|
let secondary: string | undefined;
|
||||||
@@ -43,31 +37,8 @@ export class HaIntegrationHeader extends LitElement {
|
|||||||
primary = domainName;
|
primary = domainName;
|
||||||
}
|
}
|
||||||
|
|
||||||
const icons: [string, string][] = [];
|
|
||||||
|
|
||||||
if (this.manifest) {
|
|
||||||
if (!this.manifest.is_built_in) {
|
|
||||||
icons.push([
|
|
||||||
mdiPackageVariant,
|
|
||||||
this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.custom_integration"
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.manifest.iot_class?.startsWith("cloud_")) {
|
|
||||||
icons.push([
|
|
||||||
mdiCloud,
|
|
||||||
this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.depends_on_cloud"
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${!this.banner ? "" : html`<div class="banner">${this.banner}</div>`}
|
${!this.banner ? "" : html`<div class="banner">${this.banner}</div>`}
|
||||||
<slot name="above-header"></slot>
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
@@ -80,32 +51,6 @@ export class HaIntegrationHeader extends LitElement {
|
|||||||
@error=${this._onImageError}
|
@error=${this._onImageError}
|
||||||
@load=${this._onImageLoad}
|
@load=${this._onImageLoad}
|
||||||
/>
|
/>
|
||||||
${icons.length === 0
|
|
||||||
? ""
|
|
||||||
: html`
|
|
||||||
<div
|
|
||||||
class="icons ${classMap({
|
|
||||||
double: icons.length > 1,
|
|
||||||
cloud: Boolean(
|
|
||||||
this.manifest?.iot_class?.startsWith("cloud_")
|
|
||||||
),
|
|
||||||
})}"
|
|
||||||
>
|
|
||||||
${icons.map(
|
|
||||||
([icon, description]) => html`
|
|
||||||
<span>
|
|
||||||
<ha-svg-icon .path=${icon}></ha-svg-icon>
|
|
||||||
<simple-tooltip
|
|
||||||
animation-delay="0"
|
|
||||||
.position=${computeRTL(this.hass) ? "left" : "right"}
|
|
||||||
offset="4"
|
|
||||||
>${description}</simple-tooltip
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
`}
|
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<div class="primary" role="heading">${primary}</div>
|
<div class="primary" role="heading">${primary}</div>
|
||||||
<div class="secondary">${secondary}</div>
|
<div class="secondary">${secondary}</div>
|
||||||
@@ -139,14 +84,13 @@ export class HaIntegrationHeader extends LitElement {
|
|||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-top: 0px;
|
padding-top: 16px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 16px;
|
||||||
padding-inline-start: 16px;
|
padding-inline-start: 16px;
|
||||||
padding-inline-end: 8px;
|
padding-inline-end: 8px;
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
.header img {
|
.header img {
|
||||||
margin-top: 16px;
|
|
||||||
margin-inline-start: initial;
|
margin-inline-start: initial;
|
||||||
margin-inline-end: 16px;
|
margin-inline-end: 16px;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
@@ -166,11 +110,13 @@ export class HaIntegrationHeader extends LitElement {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.header-button {
|
.header-button {
|
||||||
margin-top: 8px;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 36px;
|
||||||
}
|
}
|
||||||
.primary {
|
.primary {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
margin-top: 16px;
|
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
@@ -179,41 +125,6 @@ export class HaIntegrationHeader extends LitElement {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
.icons {
|
|
||||||
background: var(--warning-color);
|
|
||||||
border: 1px solid var(--card-background-color);
|
|
||||||
border-radius: 14px;
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
position: absolute;
|
|
||||||
left: 40px;
|
|
||||||
top: 40px;
|
|
||||||
display: flex;
|
|
||||||
padding: 4px;
|
|
||||||
inset-inline-start: 40px;
|
|
||||||
inset-inline-end: initial;
|
|
||||||
}
|
|
||||||
.icons.cloud {
|
|
||||||
background: var(--info-color);
|
|
||||||
}
|
|
||||||
.icons.double {
|
|
||||||
background: var(--warning-color);
|
|
||||||
left: 28px;
|
|
||||||
inset-inline-start: 28px;
|
|
||||||
inset-inline-end: initial;
|
|
||||||
}
|
|
||||||
.icons ha-svg-icon {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.icons span:not(:first-child) ha-svg-icon {
|
|
||||||
margin-left: 4px;
|
|
||||||
margin-inline-start: 4px;
|
|
||||||
margin-inline-end: inherit;
|
|
||||||
}
|
|
||||||
simple-tooltip {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -114,6 +114,7 @@ export class HaBlueprintScriptEditor extends LitElement {
|
|||||||
.selector=${value.selector}
|
.selector=${value.selector}
|
||||||
.key=${key}
|
.key=${key}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
|
.required=${value?.default === undefined}
|
||||||
.value=${(this.config.use_blueprint.input &&
|
.value=${(this.config.use_blueprint.input &&
|
||||||
this.config.use_blueprint.input[key]) ??
|
this.config.use_blueprint.input[key]) ??
|
||||||
value?.default}
|
value?.default}
|
||||||
@@ -121,7 +122,7 @@ export class HaBlueprintScriptEditor extends LitElement {
|
|||||||
></ha-selector>`
|
></ha-selector>`
|
||||||
: html`<ha-textfield
|
: html`<ha-textfield
|
||||||
.key=${key}
|
.key=${key}
|
||||||
required
|
.required=${value?.default === undefined}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.value=${(this.config.use_blueprint.input &&
|
.value=${(this.config.use_blueprint.input &&
|
||||||
this.config.use_blueprint.input[key]) ??
|
this.config.use_blueprint.input[key]) ??
|
||||||
|
@@ -60,6 +60,8 @@ import { documentationUrl } from "../../../util/documentation-url";
|
|||||||
import { showToast } from "../../../util/toast";
|
import { showToast } from "../../../util/toast";
|
||||||
import "./blueprint-script-editor";
|
import "./blueprint-script-editor";
|
||||||
import "./manual-script-editor";
|
import "./manual-script-editor";
|
||||||
|
import { UNAVAILABLE } from "../../../data/entity";
|
||||||
|
import { validateConfig } from "../../../data/config";
|
||||||
|
|
||||||
export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@@ -92,6 +94,8 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
|
|
||||||
@query("ha-yaml-editor", true) private _yamlEditor?: HaYamlEditor;
|
@query("ha-yaml-editor", true) private _yamlEditor?: HaYamlEditor;
|
||||||
|
|
||||||
|
@state() private _validationErrors?: (string | TemplateResult)[];
|
||||||
|
|
||||||
private _schema = memoizeOne(
|
private _schema = memoizeOne(
|
||||||
(
|
(
|
||||||
hasID: boolean,
|
hasID: boolean,
|
||||||
@@ -160,6 +164,10 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stateObj = this._entityId
|
||||||
|
? this.hass.states[this._entityId]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const useBlueprint = "use_blueprint" in this._config;
|
const useBlueprint = "use_blueprint" in this._config;
|
||||||
|
|
||||||
const schema = this._schema(
|
const schema = this._schema(
|
||||||
@@ -302,6 +310,28 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
"yaml-mode": this._mode === "yaml",
|
"yaml-mode": this._mode === "yaml",
|
||||||
})}"
|
})}"
|
||||||
>
|
>
|
||||||
|
${this._errors || stateObj?.state === UNAVAILABLE
|
||||||
|
? html`
|
||||||
|
<ha-alert
|
||||||
|
alert-type="error"
|
||||||
|
.title=${stateObj?.state === UNAVAILABLE
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.script.editor.unavailable"
|
||||||
|
)
|
||||||
|
: undefined}
|
||||||
|
>
|
||||||
|
${this._errors || this._validationErrors}
|
||||||
|
</ha-alert>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${this._readOnly
|
||||||
|
? html`<ha-alert alert-type="warning">
|
||||||
|
${this.hass.localize("ui.panel.config.script.editor.read_only")}
|
||||||
|
<mwc-button slot="action" @click=${this._duplicate}>
|
||||||
|
${this.hass.localize("ui.panel.config.script.editor.migrate")}
|
||||||
|
</mwc-button>
|
||||||
|
</ha-alert>`
|
||||||
|
: ""}
|
||||||
${this._mode === "gui"
|
${this._mode === "gui"
|
||||||
? html`
|
? html`
|
||||||
<div
|
<div
|
||||||
@@ -312,13 +342,6 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
${this._config
|
${this._config
|
||||||
? html`
|
? html`
|
||||||
<div class="config-container">
|
<div class="config-container">
|
||||||
${this._errors
|
|
||||||
? html`
|
|
||||||
<ha-alert alert-type="error">
|
|
||||||
${this._errors}
|
|
||||||
</ha-alert>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
<ha-card outlined>
|
<ha-card outlined>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<ha-form
|
<ha-form
|
||||||
@@ -363,23 +386,6 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
`
|
`
|
||||||
: this._mode === "yaml"
|
: this._mode === "yaml"
|
||||||
? html`
|
? html`
|
||||||
${this._readOnly
|
|
||||||
? html`<ha-alert alert-type="warning">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.script.editor.read_only"
|
|
||||||
)}
|
|
||||||
<mwc-button slot="action" @click=${this._duplicate}>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.script.editor.migrate"
|
|
||||||
)}
|
|
||||||
</mwc-button>
|
|
||||||
</ha-alert>`
|
|
||||||
: ""}
|
|
||||||
${this._errors
|
|
||||||
? html`
|
|
||||||
<ha-alert alert-type="error">${this._errors}</ha-alert>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
<ha-yaml-editor
|
<ha-yaml-editor
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.defaultValue=${this._preprocessYaml()}
|
.defaultValue=${this._preprocessYaml()}
|
||||||
@@ -432,6 +438,12 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
this._dirty = false;
|
this._dirty = false;
|
||||||
this._readOnly = false;
|
this._readOnly = false;
|
||||||
this._config = this._normalizeConfig(config);
|
this._config = this._normalizeConfig(config);
|
||||||
|
const entity = this.entityRegistry.find(
|
||||||
|
(ent) =>
|
||||||
|
ent.platform === "script" && ent.unique_id === this.scriptId
|
||||||
|
);
|
||||||
|
this._entityId = entity?.entity_id;
|
||||||
|
this._checkValidation();
|
||||||
},
|
},
|
||||||
(resp) => {
|
(resp) => {
|
||||||
const entity = this.entityRegistry.find(
|
const entity = this.entityRegistry.find(
|
||||||
@@ -479,6 +491,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
if (changedProps.has("entityId") && this.entityId) {
|
if (changedProps.has("entityId") && this.entityId) {
|
||||||
getScriptStateConfig(this.hass, this.entityId).then((c) => {
|
getScriptStateConfig(this.hass, this.entityId).then((c) => {
|
||||||
this._config = this._normalizeConfig(c.config);
|
this._config = this._normalizeConfig(c.config);
|
||||||
|
this._checkValidation();
|
||||||
});
|
});
|
||||||
const regEntry = this.entityRegistry.find(
|
const regEntry = this.entityRegistry.find(
|
||||||
(ent) => ent.entity_id === this.entityId
|
(ent) => ent.entity_id === this.entityId
|
||||||
@@ -502,6 +515,28 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _checkValidation() {
|
||||||
|
this._validationErrors = undefined;
|
||||||
|
if (!this._entityId || !this._config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const stateObj = this.hass.states[this._entityId];
|
||||||
|
if (stateObj?.state !== UNAVAILABLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const validation = await validateConfig(this.hass, {
|
||||||
|
action: this._config.sequence,
|
||||||
|
});
|
||||||
|
this._validationErrors = Object.entries(validation).map(([key, value]) =>
|
||||||
|
value.valid
|
||||||
|
? ""
|
||||||
|
: html`${this.hass.localize(
|
||||||
|
`ui.panel.config.automation.editor.${key}s.header`
|
||||||
|
)}:
|
||||||
|
${value.error}<br />`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private _computeLabelCallback = (
|
private _computeLabelCallback = (
|
||||||
schema: SchemaUnion<ReturnType<typeof this._schema>>,
|
schema: SchemaUnion<ReturnType<typeof this._schema>>,
|
||||||
data: HaFormDataContainer
|
data: HaFormDataContainer
|
||||||
|
@@ -12,6 +12,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { differenceInDays } from "date-fns/esm";
|
import { differenceInDays } from "date-fns/esm";
|
||||||
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
|
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
|
||||||
import { relativeTime } from "../../../common/datetime/relative_time";
|
import { relativeTime } from "../../../common/datetime/relative_time";
|
||||||
@@ -49,6 +50,7 @@ import { showNewAutomationDialog } from "../automation/show-dialog-new-automatio
|
|||||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||||
import { findRelated } from "../../../data/search";
|
import { findRelated } from "../../../data/search";
|
||||||
import { fetchBlueprints } from "../../../data/blueprint";
|
import { fetchBlueprints } from "../../../data/blueprint";
|
||||||
|
import { UNAVAILABLE } from "../../../data/entity";
|
||||||
|
|
||||||
@customElement("ha-script-picker")
|
@customElement("ha-script-picker")
|
||||||
class HaScriptPicker extends LitElement {
|
class HaScriptPicker extends LitElement {
|
||||||
@@ -100,7 +102,13 @@ class HaScriptPicker extends LitElement {
|
|||||||
),
|
),
|
||||||
type: "icon",
|
type: "icon",
|
||||||
template: (_icon, script) =>
|
template: (_icon, script) =>
|
||||||
html`<ha-state-icon .state=${script}></ha-state-icon>`,
|
html`<ha-state-icon
|
||||||
|
.state=${script}
|
||||||
|
style=${styleMap({
|
||||||
|
color:
|
||||||
|
script.state === UNAVAILABLE ? "var(--error-color)" : "unset",
|
||||||
|
})}
|
||||||
|
></ha-state-icon>`,
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
title: this.hass.localize("ui.panel.config.script.picker.headers.name"),
|
title: this.hass.localize("ui.panel.config.script.picker.headers.name"),
|
||||||
|
@@ -1,13 +1,10 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import { mdiHelpCircle } from "@mdi/js";
|
import { mdiHelpCircle } from "@mdi/js";
|
||||||
import deepClone from "deep-clone-simple";
|
|
||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { storage } from "../../../common/decorators/storage";
|
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import { Clipboard } from "../../../data/automation";
|
|
||||||
import { Action, ScriptConfig } from "../../../data/script";
|
import { Action, ScriptConfig } from "../../../data/script";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
@@ -26,14 +23,6 @@ export class HaManualScriptEditor extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public config!: ScriptConfig;
|
@property({ attribute: false }) public config!: ScriptConfig;
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "automationClipboard",
|
|
||||||
state: true,
|
|
||||||
subscribe: false,
|
|
||||||
storage: "sessionStorage",
|
|
||||||
})
|
|
||||||
private _clipboard: Clipboard = {};
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
${this.disabled
|
${this.disabled
|
||||||
@@ -70,8 +59,6 @@ export class HaManualScriptEditor extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@set-clipboard=${this._setClipboard}
|
|
||||||
.clipboard=${this._clipboard}
|
|
||||||
></ha-automation-action>
|
></ha-automation-action>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -83,11 +70,6 @@ export class HaManualScriptEditor extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setClipboard(ev: CustomEvent): void {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this._clipboard = { ...this._clipboard, ...deepClone(ev.detail) };
|
|
||||||
}
|
|
||||||
|
|
||||||
private _duplicate() {
|
private _duplicate() {
|
||||||
fireEvent(this, "duplicate");
|
fireEvent(this, "duplicate");
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,8 @@ const mountSchema = memoizeOne(
|
|||||||
(
|
(
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
existing?: boolean,
|
existing?: boolean,
|
||||||
mountType?: SupervisorMountType
|
mountType?: SupervisorMountType,
|
||||||
|
showCIFSVersion?: boolean
|
||||||
) =>
|
) =>
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -90,6 +91,41 @@ const mountSchema = memoizeOne(
|
|||||||
] as const)
|
] as const)
|
||||||
: mountType === "cifs"
|
: mountType === "cifs"
|
||||||
? ([
|
? ([
|
||||||
|
...(showCIFSVersion
|
||||||
|
? ([
|
||||||
|
{
|
||||||
|
name: "version",
|
||||||
|
required: true,
|
||||||
|
selector: {
|
||||||
|
select: {
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: localize(
|
||||||
|
"ui.panel.config.storage.network_mounts.cifs_versions.auto"
|
||||||
|
),
|
||||||
|
value: "auto",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: localize(
|
||||||
|
"ui.panel.config.storage.network_mounts.cifs_versions.legacy",
|
||||||
|
{ version: "2.0" }
|
||||||
|
),
|
||||||
|
value: "2.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: localize(
|
||||||
|
"ui.panel.config.storage.network_mounts.cifs_versions.legacy",
|
||||||
|
{ version: "1.0" }
|
||||||
|
),
|
||||||
|
value: "1.0",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
mode: "dropdown",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as const)
|
||||||
|
: ([] as const)),
|
||||||
{
|
{
|
||||||
name: "share",
|
name: "share",
|
||||||
required: true,
|
required: true,
|
||||||
@@ -122,8 +158,12 @@ class ViewMountDialog extends LitElement {
|
|||||||
|
|
||||||
@state() private _validationError?: Record<string, string>;
|
@state() private _validationError?: Record<string, string>;
|
||||||
|
|
||||||
|
@state() private _validationWarning?: Record<string, string>;
|
||||||
|
|
||||||
@state() private _existing?: boolean;
|
@state() private _existing?: boolean;
|
||||||
|
|
||||||
|
@state() private _showCIFSVersion?: boolean;
|
||||||
|
|
||||||
@state() private _reloadMounts?: () => void;
|
@state() private _reloadMounts?: () => void;
|
||||||
|
|
||||||
public async showDialog(
|
public async showDialog(
|
||||||
@@ -132,6 +172,13 @@ class ViewMountDialog extends LitElement {
|
|||||||
this._data = dialogParams.mount;
|
this._data = dialogParams.mount;
|
||||||
this._existing = dialogParams.mount !== undefined;
|
this._existing = dialogParams.mount !== undefined;
|
||||||
this._reloadMounts = dialogParams.reloadMounts;
|
this._reloadMounts = dialogParams.reloadMounts;
|
||||||
|
if (
|
||||||
|
dialogParams.mount?.type === "cifs" &&
|
||||||
|
dialogParams.mount.version &&
|
||||||
|
dialogParams.mount.version !== "auto"
|
||||||
|
) {
|
||||||
|
this._showCIFSVersion = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeDialog(): void {
|
public closeDialog(): void {
|
||||||
@@ -139,7 +186,9 @@ class ViewMountDialog extends LitElement {
|
|||||||
this._waiting = undefined;
|
this._waiting = undefined;
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
this._validationError = undefined;
|
this._validationError = undefined;
|
||||||
|
this._validationWarning = undefined;
|
||||||
this._existing = undefined;
|
this._existing = undefined;
|
||||||
|
this._showCIFSVersion = undefined;
|
||||||
this._reloadMounts = undefined;
|
this._reloadMounts = undefined;
|
||||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
}
|
}
|
||||||
@@ -197,12 +246,15 @@ class ViewMountDialog extends LitElement {
|
|||||||
.schema=${mountSchema(
|
.schema=${mountSchema(
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
this._existing,
|
this._existing,
|
||||||
this._data?.type
|
this._data?.type,
|
||||||
|
this._showCIFSVersion
|
||||||
)}
|
)}
|
||||||
.error=${this._validationError}
|
.error=${this._validationError}
|
||||||
|
.warning=${this._validationWarning}
|
||||||
.computeLabel=${this._computeLabelCallback}
|
.computeLabel=${this._computeLabelCallback}
|
||||||
.computeHelper=${this._computeHelperCallback}
|
.computeHelper=${this._computeHelperCallback}
|
||||||
.computeError=${this._computeErrorCallback}
|
.computeError=${this._computeErrorCallback}
|
||||||
|
.computeWarning=${this._computeWarningCallback}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
dialogInitialFocus
|
dialogInitialFocus
|
||||||
></ha-form>
|
></ha-form>
|
||||||
@@ -256,12 +308,29 @@ class ViewMountDialog extends LitElement {
|
|||||||
`ui.panel.config.storage.network_mounts.errors.${error}`
|
`ui.panel.config.storage.network_mounts.errors.${error}`
|
||||||
) || error;
|
) || error;
|
||||||
|
|
||||||
|
private _computeWarningCallback = (warning: string): string =>
|
||||||
|
this.hass.localize(
|
||||||
|
// @ts-ignore
|
||||||
|
`ui.panel.config.storage.network_mounts.warnings.${warning}`
|
||||||
|
) || warning;
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent) {
|
private _valueChanged(ev: CustomEvent) {
|
||||||
this._validationError = {};
|
this._validationError = {};
|
||||||
|
this._validationWarning = {};
|
||||||
this._data = ev.detail.value;
|
this._data = ev.detail.value;
|
||||||
if (this._data?.name && !/^\w+$/.test(this._data.name)) {
|
if (this._data?.name && !/^\w+$/.test(this._data.name)) {
|
||||||
this._validationError.name = "invalid_name";
|
this._validationError.name = "invalid_name";
|
||||||
}
|
}
|
||||||
|
if (this._data?.type === "cifs" && !this._data.version) {
|
||||||
|
this._data.version = "auto";
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
this._data?.type === "cifs" &&
|
||||||
|
this._data.version &&
|
||||||
|
["1.0", "2.0"].includes(this._data.version)
|
||||||
|
) {
|
||||||
|
this._validationWarning.version = "not_recomeded_cifs_version";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _connectMount() {
|
private async _connectMount() {
|
||||||
@@ -276,6 +345,9 @@ class ViewMountDialog extends LitElement {
|
|||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this._error = extractApiErrorMessage(err);
|
this._error = extractApiErrorMessage(err);
|
||||||
this._waiting = false;
|
this._waiting = false;
|
||||||
|
if (this._data!.type === "cifs" && !this._showCIFSVersion) {
|
||||||
|
this._showCIFSVersion = true;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this._reloadMounts) {
|
if (this._reloadMounts) {
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@@ -14,6 +13,7 @@ import "../../../components/ha-circular-progress";
|
|||||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||||
import "../../../components/ha-formfield";
|
import "../../../components/ha-formfield";
|
||||||
import "../../../components/ha-switch";
|
import "../../../components/ha-switch";
|
||||||
|
import type { HaSwitch } from "../../../components/ha-switch";
|
||||||
import { createAuthForUser } from "../../../data/auth";
|
import { createAuthForUser } from "../../../data/auth";
|
||||||
import {
|
import {
|
||||||
createUser,
|
createUser,
|
||||||
@@ -25,6 +25,8 @@ import {
|
|||||||
import { ValueChangedEvent, HomeAssistant } from "../../../types";
|
import { ValueChangedEvent, HomeAssistant } from "../../../types";
|
||||||
import { haStyleDialog } from "../../../resources/styles";
|
import { haStyleDialog } from "../../../resources/styles";
|
||||||
import { AddUserDialogParams } from "./show-dialog-add-user";
|
import { AddUserDialogParams } from "./show-dialog-add-user";
|
||||||
|
import "../../../components/ha-textfield";
|
||||||
|
import type { HaTextField } from "../../../components/ha-textfield";
|
||||||
|
|
||||||
@customElement("dialog-add-user")
|
@customElement("dialog-add-user")
|
||||||
export class DialogAddUser extends LitElement {
|
export class DialogAddUser extends LitElement {
|
||||||
@@ -97,7 +99,7 @@ export class DialogAddUser extends LitElement {
|
|||||||
<div>
|
<div>
|
||||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||||
${this._allowChangeName
|
${this._allowChangeName
|
||||||
? html` <paper-input
|
? html`<ha-textfield
|
||||||
class="name"
|
class="name"
|
||||||
name="name"
|
name="name"
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@@ -105,15 +107,13 @@ export class DialogAddUser extends LitElement {
|
|||||||
)}
|
)}
|
||||||
.value=${this._name}
|
.value=${this._name}
|
||||||
required
|
required
|
||||||
auto-validate
|
|
||||||
autocapitalize="on"
|
|
||||||
.errorMessage=${this.hass.localize("ui.common.error_required")}
|
.errorMessage=${this.hass.localize("ui.common.error_required")}
|
||||||
@value-changed=${this._handleValueChanged}
|
@input=${this._handleValueChanged}
|
||||||
@blur=${this._maybePopulateUsername}
|
@blur=${this._maybePopulateUsername}
|
||||||
dialogInitialFocus
|
dialogInitialFocus
|
||||||
></paper-input>`
|
></ha-textfield>`
|
||||||
: ""}
|
: ""}
|
||||||
<paper-input
|
<ha-textfield
|
||||||
class="username"
|
class="username"
|
||||||
name="username"
|
name="username"
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@@ -121,14 +121,12 @@ export class DialogAddUser extends LitElement {
|
|||||||
)}
|
)}
|
||||||
.value=${this._username}
|
.value=${this._username}
|
||||||
required
|
required
|
||||||
auto-validate
|
@input=${this._handleValueChanged}
|
||||||
autocapitalize="none"
|
|
||||||
@value-changed=${this._handleValueChanged}
|
|
||||||
.errorMessage=${this.hass.localize("ui.common.error_required")}
|
.errorMessage=${this.hass.localize("ui.common.error_required")}
|
||||||
dialogInitialFocus
|
dialogInitialFocus
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
|
|
||||||
<paper-input
|
<ha-textfield
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.users.add_user.password"
|
"ui.panel.config.users.add_user.password"
|
||||||
)}
|
)}
|
||||||
@@ -136,18 +134,17 @@ export class DialogAddUser extends LitElement {
|
|||||||
name="password"
|
name="password"
|
||||||
.value=${this._password}
|
.value=${this._password}
|
||||||
required
|
required
|
||||||
auto-validate
|
@input=${this._handleValueChanged}
|
||||||
@value-changed=${this._handleValueChanged}
|
|
||||||
.errorMessage=${this.hass.localize("ui.common.error_required")}
|
.errorMessage=${this.hass.localize("ui.common.error_required")}
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
|
|
||||||
<paper-input
|
<ha-textfield
|
||||||
label=${this.hass.localize(
|
label=${this.hass.localize(
|
||||||
"ui.panel.config.users.add_user.password_confirm"
|
"ui.panel.config.users.add_user.password_confirm"
|
||||||
)}
|
)}
|
||||||
name="passwordConfirm"
|
name="passwordConfirm"
|
||||||
.value=${this._passwordConfirm}
|
.value=${this._passwordConfirm}
|
||||||
@value-changed=${this._handleValueChanged}
|
@input=${this._handleValueChanged}
|
||||||
required
|
required
|
||||||
type="password"
|
type="password"
|
||||||
.invalid=${this._password !== "" &&
|
.invalid=${this._password !== "" &&
|
||||||
@@ -156,7 +153,7 @@ export class DialogAddUser extends LitElement {
|
|||||||
.errorMessage=${this.hass.localize(
|
.errorMessage=${this.hass.localize(
|
||||||
"ui.panel.config.users.add_user.password_not_match"
|
"ui.panel.config.users.add_user.password_not_match"
|
||||||
)}
|
)}
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<ha-formfield
|
<ha-formfield
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@@ -232,19 +229,21 @@ export class DialogAddUser extends LitElement {
|
|||||||
|
|
||||||
private _handleValueChanged(ev: ValueChangedEvent<string>): void {
|
private _handleValueChanged(ev: ValueChangedEvent<string>): void {
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
const name = (ev.target as any).name;
|
const target = ev.target as HaTextField;
|
||||||
this[`_${name}`] = ev.detail.value;
|
this[`_${target.name}`] = target.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _adminChanged(ev): Promise<void> {
|
private async _adminChanged(ev: Event): Promise<void> {
|
||||||
this._isAdmin = ev.target.checked;
|
const target = ev.target as HaSwitch;
|
||||||
|
this._isAdmin = target.checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _localOnlyChanged(ev): void {
|
private _localOnlyChanged(ev: Event): void {
|
||||||
this._localOnly = ev.target.checked;
|
const target = ev.target as HaSwitch;
|
||||||
|
this._localOnly = target.checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _createUser(ev) {
|
private async _createUser(ev: Event) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
if (!this._name || !this._username || !this._password) {
|
if (!this._name || !this._username || !this._password) {
|
||||||
return;
|
return;
|
||||||
@@ -299,6 +298,10 @@ export class DialogAddUser extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
}
|
}
|
||||||
|
ha-textfield {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -35,6 +35,8 @@ export class AssistPipelineDebug extends LitElement {
|
|||||||
|
|
||||||
@state() private _events?: PipelineRunEvent[];
|
@state() private _events?: PipelineRunEvent[];
|
||||||
|
|
||||||
|
private _unsubRefreshEventsID?: number;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`<hass-subpage
|
return html`<hass-subpage
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
@@ -94,11 +96,27 @@ export class AssistPipelineDebug extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected willUpdate(changedProperties) {
|
protected willUpdate(changedProperties) {
|
||||||
|
let clearRefresh = false;
|
||||||
|
|
||||||
if (changedProperties.has("pipelineId")) {
|
if (changedProperties.has("pipelineId")) {
|
||||||
this._fetchRuns();
|
this._fetchRuns();
|
||||||
|
clearRefresh = true;
|
||||||
}
|
}
|
||||||
if (changedProperties.has("_runId")) {
|
if (changedProperties.has("_runId")) {
|
||||||
this._fetchEvents();
|
this._fetchEvents();
|
||||||
|
clearRefresh = true;
|
||||||
|
}
|
||||||
|
if (clearRefresh && this._unsubRefreshEventsID) {
|
||||||
|
clearTimeout(this._unsubRefreshEventsID);
|
||||||
|
this._unsubRefreshEventsID = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public disconnectedCallback(): void {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
if (this._unsubRefreshEventsID) {
|
||||||
|
clearTimeout(this._unsubRefreshEventsID);
|
||||||
|
this._unsubRefreshEventsID = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +162,17 @@ export class AssistPipelineDebug extends LitElement {
|
|||||||
title: "Failed to fetch events",
|
title: "Failed to fetch events",
|
||||||
text: e.message,
|
text: e.message,
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
this._events?.length &&
|
||||||
|
// If the last event is not a finish run event, the run is still ongoing.
|
||||||
|
// Refresh events automatically.
|
||||||
|
!["run-end", "error"].includes(this._events[this._events.length - 1].type)
|
||||||
|
) {
|
||||||
|
this._unsubRefreshEventsID = window.setTimeout(() => {
|
||||||
|
this._fetchEvents();
|
||||||
|
}, 2000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
258
src/panels/developer-tools/assist/developer-tools-assist.ts
Normal file
258
src/panels/developer-tools/assist/developer-tools-assist.ts
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
import { dump } from "js-yaml";
|
||||||
|
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||||
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import "../../../components/ha-button";
|
||||||
|
import "../../../components/ha-code-editor";
|
||||||
|
import "../../../components/ha-language-picker";
|
||||||
|
import "../../../components/ha-textarea";
|
||||||
|
import "../../../components/ha-absolute-time";
|
||||||
|
import type { HaTextArea } from "../../../components/ha-textarea";
|
||||||
|
import {
|
||||||
|
AssitDebugResult,
|
||||||
|
debugAgent,
|
||||||
|
listAgents,
|
||||||
|
} from "../../../data/conversation";
|
||||||
|
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||||
|
import { haStyle } from "../../../resources/styles";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { formatLanguageCode } from "../../../common/language/format_language";
|
||||||
|
import { storage } from "../../../common/decorators/storage";
|
||||||
|
|
||||||
|
type SentenceParsingResult = {
|
||||||
|
sentence: string;
|
||||||
|
language: string;
|
||||||
|
result: AssitDebugResult | null;
|
||||||
|
time: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
@customElement("developer-tools-assist")
|
||||||
|
class HaPanelDevAssist extends SubscribeMixin(LitElement) {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public narrow!: boolean;
|
||||||
|
|
||||||
|
@state() supportedLanguages?: string[];
|
||||||
|
|
||||||
|
@storage({
|
||||||
|
key: "assist_debug_language",
|
||||||
|
state: true,
|
||||||
|
subscribe: false,
|
||||||
|
storage: "localStorage",
|
||||||
|
})
|
||||||
|
_language?: string;
|
||||||
|
|
||||||
|
@state() _results: SentenceParsingResult[] = [];
|
||||||
|
|
||||||
|
@query("#sentences-input") _sentencesInput!: HaTextArea;
|
||||||
|
|
||||||
|
@state() _validInput = false;
|
||||||
|
|
||||||
|
private _languageChanged(ev) {
|
||||||
|
this._language = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleKeyDown(e: KeyboardEvent) {
|
||||||
|
if (e.code !== "Enter" || e.shiftKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
this._parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _textAreaInput(ev) {
|
||||||
|
const value = ev.target.value;
|
||||||
|
const valid = Boolean(value);
|
||||||
|
if (valid !== this._validInput) {
|
||||||
|
this._validInput = valid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _parse() {
|
||||||
|
const sentences = this._sentencesInput.value
|
||||||
|
.split("\n")
|
||||||
|
.filter((a) => a !== "");
|
||||||
|
const { results } = await debugAgent(this.hass, sentences, this._language!);
|
||||||
|
|
||||||
|
this._sentencesInput.value = "";
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const newResults: SentenceParsingResult[] = [];
|
||||||
|
sentences.forEach((sentence, index) => {
|
||||||
|
const result = results[index];
|
||||||
|
|
||||||
|
newResults.push({
|
||||||
|
sentence,
|
||||||
|
language: this._language!,
|
||||||
|
result,
|
||||||
|
time: now,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this._results = [...newResults, ...this._results];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchLanguages() {
|
||||||
|
const { agents } = await listAgents(this.hass);
|
||||||
|
const assistAgent = agents.find((agent) => agent.id === "homeassistant");
|
||||||
|
this.supportedLanguages =
|
||||||
|
assistAgent?.supported_languages === "*"
|
||||||
|
? undefined
|
||||||
|
: assistAgent?.supported_languages;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!this._language &&
|
||||||
|
this.supportedLanguages?.includes(this.hass.locale.language)
|
||||||
|
) {
|
||||||
|
this._language = this.hass.locale.language;
|
||||||
|
} else if (!this._language) {
|
||||||
|
this._language = "en";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(): void {
|
||||||
|
this._fetchLanguages();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<div class="content">
|
||||||
|
<ha-card header="Sentences parser" class="form">
|
||||||
|
<div class="card-content">
|
||||||
|
<p class="description">
|
||||||
|
Enter sentences and see how they will be parsed by Home Assistant.
|
||||||
|
Each line will be processed as individual sentence. Intents will
|
||||||
|
not be executed on your instance.
|
||||||
|
</p>
|
||||||
|
${this.supportedLanguages
|
||||||
|
? html`
|
||||||
|
<ha-language-picker
|
||||||
|
.languages=${this.supportedLanguages}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this._language}
|
||||||
|
@value-changed=${this._languageChanged}
|
||||||
|
></ha-language-picker>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
<ha-textarea
|
||||||
|
autogrow
|
||||||
|
label="Sentences"
|
||||||
|
id="sentences-input"
|
||||||
|
@input=${this._textAreaInput}
|
||||||
|
@keydown=${this._handleKeyDown}
|
||||||
|
></ha-textarea>
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<ha-button
|
||||||
|
@click=${this._parse}
|
||||||
|
.disabled=${!this._language || !this._validInput}
|
||||||
|
>
|
||||||
|
Parse sentences
|
||||||
|
</ha-button>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
|
||||||
|
${this._results.map((r) => {
|
||||||
|
const { sentence, result, language, time } = r;
|
||||||
|
const matched = result != null;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-card class="result">
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="sentence">
|
||||||
|
<p>${sentence}</p>
|
||||||
|
<p>${matched ? "✅" : "❌"}</p>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<p>
|
||||||
|
Language: ${formatLanguageCode(
|
||||||
|
language,
|
||||||
|
this.hass.locale
|
||||||
|
)} (${language})
|
||||||
|
</p>
|
||||||
|
<p>Execution time:
|
||||||
|
<ha-absolute-time .hass=${this.hass} .datetime=${time}>
|
||||||
|
</ha-absolute-time>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
${
|
||||||
|
result
|
||||||
|
? html`
|
||||||
|
<ha-code-editor
|
||||||
|
mode="yaml"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${dump(result).trimRight()}
|
||||||
|
read-only
|
||||||
|
dir="ltr"
|
||||||
|
></ha-code-editor>
|
||||||
|
`
|
||||||
|
: html`<ha-alert alert-type="error"
|
||||||
|
>No intent matched</ha-alert
|
||||||
|
>`
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
.content {
|
||||||
|
padding: 28px 20px 16px;
|
||||||
|
padding: max(28px, calc(12px + env(safe-area-inset-top)))
|
||||||
|
max(20px, calc(4px + env(safe-area-inset-right)))
|
||||||
|
max(16px, env(safe-area-inset-bottom))
|
||||||
|
max(20px, calc(4px + env(safe-area-inset-left)));
|
||||||
|
max-width: 1040px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
margin: 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
ha-textarea {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.card-actions {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.form {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.result {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.sentence {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.sentence p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.info p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
ha-code-editor,
|
||||||
|
ha-alert {
|
||||||
|
display: block;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"developer-tools-assist": HaPanelDevAssist;
|
||||||
|
}
|
||||||
|
}
|
@@ -45,6 +45,10 @@ class DeveloperToolsRouter extends HassRouterPage {
|
|||||||
tag: "developer-yaml-config",
|
tag: "developer-yaml-config",
|
||||||
load: () => import("./yaml_configuration/developer-yaml-config"),
|
load: () => import("./yaml_configuration/developer-yaml-config"),
|
||||||
},
|
},
|
||||||
|
assist: {
|
||||||
|
tag: "developer-tools-assist",
|
||||||
|
load: () => import("./assist/developer-tools-assist"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -65,6 +65,7 @@ class PanelDeveloperTools extends LitElement {
|
|||||||
"ui.panel.developer-tools.tabs.statistics.title"
|
"ui.panel.developer-tools.tabs.statistics.title"
|
||||||
)}
|
)}
|
||||||
</paper-tab>
|
</paper-tab>
|
||||||
|
<paper-tab page-name="assist">Assist</paper-tab>
|
||||||
</paper-tabs>
|
</paper-tabs>
|
||||||
</div>
|
</div>
|
||||||
<developer-tools-router
|
<developer-tools-router
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { mdiHelpCircle } from "@mdi/js";
|
import { mdiHelpCircle } from "@mdi/js";
|
||||||
import { ERR_CONNECTION_LOST } from "home-assistant-js-websocket";
|
import { ERR_CONNECTION_LOST } from "home-assistant-js-websocket";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
import { property, query, state } from "lit/decorators";
|
import { property, query, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { storage } from "../../../common/decorators/storage";
|
import { storage } from "../../../common/decorators/storage";
|
||||||
@@ -20,7 +20,7 @@ import "../../../components/ha-service-picker";
|
|||||||
import "../../../components/ha-yaml-editor";
|
import "../../../components/ha-yaml-editor";
|
||||||
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
|
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
|
||||||
import { forwardHaptic } from "../../../data/haptics";
|
import { forwardHaptic } from "../../../data/haptics";
|
||||||
import { ServiceAction } from "../../../data/script";
|
import { Action, ServiceAction } from "../../../data/script";
|
||||||
import {
|
import {
|
||||||
callExecuteScript,
|
callExecuteScript,
|
||||||
serviceCallWillDisconnect,
|
serviceCallWillDisconnect,
|
||||||
@@ -38,6 +38,8 @@ class HaPanelDevService extends LitElement {
|
|||||||
|
|
||||||
@state() private _uiAvailable = true;
|
@state() private _uiAvailable = true;
|
||||||
|
|
||||||
|
@state() private _response?: Record<string, any>;
|
||||||
|
|
||||||
@storage({
|
@storage({
|
||||||
key: "panel-dev-service-state-service-data",
|
key: "panel-dev-service-state-service-data",
|
||||||
state: true,
|
state: true,
|
||||||
@@ -52,7 +54,7 @@ class HaPanelDevService extends LitElement {
|
|||||||
})
|
})
|
||||||
private _yamlMode = false;
|
private _yamlMode = false;
|
||||||
|
|
||||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
@query("#yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||||
|
|
||||||
protected firstUpdated(params) {
|
protected firstUpdated(params) {
|
||||||
super.firstUpdated(params);
|
super.firstUpdated(params);
|
||||||
@@ -109,6 +111,7 @@ class HaPanelDevService extends LitElement {
|
|||||||
@value-changed=${this._serviceChanged}
|
@value-changed=${this._serviceChanged}
|
||||||
></ha-service-picker>
|
></ha-service-picker>
|
||||||
<ha-yaml-editor
|
<ha-yaml-editor
|
||||||
|
id="yaml-editor"
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.defaultValue=${this._serviceData}
|
.defaultValue=${this._serviceData}
|
||||||
@value-changed=${this._yamlChanged}
|
@value-changed=${this._yamlChanged}
|
||||||
@@ -160,7 +163,23 @@ class HaPanelDevService extends LitElement {
|
|||||||
</ha-progress-button>
|
</ha-progress-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
${this._response
|
||||||
|
? html`<div class="content">
|
||||||
|
<ha-card
|
||||||
|
.header=${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.services.response"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div class="card-content">
|
||||||
|
<ha-yaml-editor
|
||||||
|
readOnly
|
||||||
|
autoUpdate
|
||||||
|
.value=${this._response}
|
||||||
|
></ha-yaml-editor>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
${(this._yamlMode ? fields : this._filterSelectorFields(fields)).length
|
${(this._yamlMode ? fields : this._filterSelectorFields(fields)).length
|
||||||
? html`<div class="content">
|
? html`<div class="content">
|
||||||
<ha-expansion-panel
|
<ha-expansion-panel
|
||||||
@@ -175,7 +194,7 @@ class HaPanelDevService extends LitElement {
|
|||||||
.expanded=${this._yamlMode}
|
.expanded=${this._yamlMode}
|
||||||
>
|
>
|
||||||
${this._yamlMode
|
${this._yamlMode
|
||||||
? html` <div class="description">
|
? html`<div class="description">
|
||||||
<h3>
|
<h3>
|
||||||
${target
|
${target
|
||||||
? html`
|
? html`
|
||||||
@@ -317,10 +336,20 @@ class HaPanelDevService extends LitElement {
|
|||||||
if (!this._serviceData?.service) {
|
if (!this._serviceData?.service) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const [domain, service] = this._serviceData.service.split(".", 2);
|
||||||
|
const script: Action[] = [];
|
||||||
|
if ("response" in this.hass.services[domain][service]) {
|
||||||
|
script.push({
|
||||||
|
...this._serviceData,
|
||||||
|
response_variable: "service_result",
|
||||||
|
});
|
||||||
|
script.push({ stop: "done", response_variable: "service_result" });
|
||||||
|
} else {
|
||||||
|
script.push(this._serviceData);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await callExecuteScript(this.hass, [this._serviceData]);
|
this._response = (await callExecuteScript(this.hass, script)).response;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const [domain, service] = this._serviceData.service.split(".", 2);
|
|
||||||
if (
|
if (
|
||||||
err.error?.code === ERR_CONNECTION_LOST &&
|
err.error?.code === ERR_CONNECTION_LOST &&
|
||||||
serviceCallWillDisconnect(domain, service)
|
serviceCallWillDisconnect(domain, service)
|
||||||
|
@@ -10,6 +10,7 @@ import {
|
|||||||
RenderTemplateResult,
|
RenderTemplateResult,
|
||||||
subscribeRenderTemplate,
|
subscribeRenderTemplate,
|
||||||
} from "../../../data/ws-templates";
|
} from "../../../data/ws-templates";
|
||||||
|
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { documentationUrl } from "../../../util/documentation-url";
|
import { documentationUrl } from "../../../util/documentation-url";
|
||||||
@@ -142,6 +143,9 @@ class HaPanelDevTemplate extends LitElement {
|
|||||||
"ui.panel.developer-tools.tabs.templates.reset"
|
"ui.panel.developer-tools.tabs.templates.reset"
|
||||||
)}
|
)}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
|
<mwc-button @click=${this._clear}>
|
||||||
|
${this.hass.localize("ui.common.clear")}
|
||||||
|
</mwc-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="render-pane">
|
<div class="render-pane">
|
||||||
@@ -378,11 +382,42 @@ class HaPanelDevTemplate extends LitElement {
|
|||||||
localStorage["panel-dev-template-template"] = this._template;
|
localStorage["panel-dev-template-template"] = this._template;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _restoreDemo() {
|
private async _restoreDemo() {
|
||||||
|
if (
|
||||||
|
!(await showConfirmationDialog(this, {
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.templates.confirm_reset"
|
||||||
|
),
|
||||||
|
warning: true,
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this._template = DEMO_TEMPLATE;
|
this._template = DEMO_TEMPLATE;
|
||||||
this._subscribeTemplate();
|
this._subscribeTemplate();
|
||||||
delete localStorage["panel-dev-template-template"];
|
delete localStorage["panel-dev-template-template"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _clear() {
|
||||||
|
if (
|
||||||
|
!(await showConfirmationDialog(this, {
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.templates.confirm_clear"
|
||||||
|
),
|
||||||
|
warning: true,
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._unsubscribeTemplate();
|
||||||
|
this._template = "";
|
||||||
|
// Reset to empty result. Setting to 'undefined' results in a different visual
|
||||||
|
// behaviour compared to manually emptying the template input box.
|
||||||
|
this._templateResult = {
|
||||||
|
result: "",
|
||||||
|
listeners: { all: false, entities: [], domains: [], time: false },
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -217,6 +217,10 @@ export class HuiEnergyGasGraphCard
|
|||||||
plugins: {
|
plugins: {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
position: "nearest",
|
position: "nearest",
|
||||||
|
filter: (val) => val.formattedValue !== "0",
|
||||||
|
itemSort: function (a, b) {
|
||||||
|
return b.datasetIndex - a.datasetIndex;
|
||||||
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
title: (datasets) => {
|
title: (datasets) => {
|
||||||
if (dayDifference > 0) {
|
if (dayDifference > 0) {
|
||||||
|
@@ -213,6 +213,10 @@ export class HuiEnergySolarGraphCard
|
|||||||
plugins: {
|
plugins: {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
position: "nearest",
|
position: "nearest",
|
||||||
|
filter: (val) => val.formattedValue !== "0",
|
||||||
|
itemSort: function (a, b) {
|
||||||
|
return b.datasetIndex - a.datasetIndex;
|
||||||
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
title: (datasets) => {
|
title: (datasets) => {
|
||||||
if (dayDifference > 0) {
|
if (dayDifference > 0) {
|
||||||
|
@@ -209,6 +209,18 @@ export class HuiEnergyUsageGraphCard
|
|||||||
tooltip: {
|
tooltip: {
|
||||||
position: "nearest",
|
position: "nearest",
|
||||||
filter: (val) => val.formattedValue !== "0",
|
filter: (val) => val.formattedValue !== "0",
|
||||||
|
itemSort: function (a: any, b: any) {
|
||||||
|
if (a.raw?.y > 0 && b.raw?.y < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (b.raw?.y > 0 && a.raw?.y < 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (a.raw?.y > 0) {
|
||||||
|
return b.datasetIndex - a.datasetIndex;
|
||||||
|
}
|
||||||
|
return a.datasetIndex - b.datasetIndex;
|
||||||
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
title: (datasets) => {
|
title: (datasets) => {
|
||||||
if (dayDifference > 0) {
|
if (dayDifference > 0) {
|
||||||
|
@@ -217,6 +217,10 @@ export class HuiEnergyWaterGraphCard
|
|||||||
plugins: {
|
plugins: {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
position: "nearest",
|
position: "nearest",
|
||||||
|
filter: (val) => val.formattedValue !== "0",
|
||||||
|
itemSort: function (a, b) {
|
||||||
|
return b.datasetIndex - a.datasetIndex;
|
||||||
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
title: (datasets) => {
|
title: (datasets) => {
|
||||||
if (dayDifference > 0) {
|
if (dayDifference > 0) {
|
||||||
|
@@ -26,7 +26,10 @@ import { computeDomain } from "../../../common/entity/compute_domain";
|
|||||||
import { computeStateDisplaySingleEntity } from "../../../common/entity/compute_state_display";
|
import { computeStateDisplaySingleEntity } from "../../../common/entity/compute_state_display";
|
||||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||||
import { stateColorCss } from "../../../common/entity/state_color";
|
import {
|
||||||
|
stateColorCss,
|
||||||
|
stateColorBrightness,
|
||||||
|
} from "../../../common/entity/state_color";
|
||||||
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||||
import { iconColorCSS } from "../../../common/style/icon_color_css";
|
import { iconColorCSS } from "../../../common/style/icon_color_css";
|
||||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||||
@@ -41,7 +44,6 @@ import {
|
|||||||
themesContext,
|
themesContext,
|
||||||
} from "../../../data/context";
|
} from "../../../data/context";
|
||||||
import { EntityRegistryDisplayEntry } from "../../../data/entity_registry";
|
import { EntityRegistryDisplayEntry } from "../../../data/entity_registry";
|
||||||
import { LightEntity } from "../../../data/light";
|
|
||||||
import { ActionHandlerEvent } from "../../../data/lovelace";
|
import { ActionHandlerEvent } from "../../../data/lovelace";
|
||||||
import { FrontendLocaleData } from "../../../data/translation";
|
import { FrontendLocaleData } from "../../../data/translation";
|
||||||
import { Themes } from "../../../data/ws-themes";
|
import { Themes } from "../../../data/ws-themes";
|
||||||
@@ -213,9 +215,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
|||||||
.state=${stateObj}
|
.state=${stateObj}
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
color: colored ? this._computeColor(stateObj) : undefined,
|
color: colored ? this._computeColor(stateObj) : undefined,
|
||||||
filter: colored
|
filter: colored ? stateColorBrightness(stateObj) : undefined,
|
||||||
? this._computeBrightness(stateObj)
|
|
||||||
: undefined,
|
|
||||||
height: this._config.icon_height
|
height: this._config.icon_height
|
||||||
? this._config.icon_height
|
? this._config.icon_height
|
||||||
: "",
|
: "",
|
||||||
@@ -337,14 +337,6 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeBrightness(stateObj: HassEntity | LightEntity): string {
|
|
||||||
if (stateObj.attributes.brightness) {
|
|
||||||
const brightness = stateObj.attributes.brightness;
|
|
||||||
return `brightness(${(brightness + 245) / 5}%)`;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private _computeColor(stateObj: HassEntity): string | undefined {
|
private _computeColor(stateObj: HassEntity): string | undefined {
|
||||||
if (stateObj.attributes.rgb_color) {
|
if (stateObj.attributes.rgb_color) {
|
||||||
return `rgb(${stateObj.attributes.rgb_color.join(",")})`;
|
return `rgb(${stateObj.attributes.rgb_color.join(",")})`;
|
||||||
|
@@ -16,7 +16,10 @@ import { computeAttributeValueDisplay } from "../../../common/entity/compute_att
|
|||||||
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
||||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||||
import { stateColorCss } from "../../../common/entity/state_color";
|
import {
|
||||||
|
stateColorCss,
|
||||||
|
stateColorBrightness,
|
||||||
|
} from "../../../common/entity/state_color";
|
||||||
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||||
import {
|
import {
|
||||||
formatNumber,
|
formatNumber,
|
||||||
@@ -28,7 +31,6 @@ import "../../../components/ha-card";
|
|||||||
import "../../../components/ha-icon";
|
import "../../../components/ha-icon";
|
||||||
import { HVAC_ACTION_TO_MODE } from "../../../data/climate";
|
import { HVAC_ACTION_TO_MODE } from "../../../data/climate";
|
||||||
import { isUnavailableState } from "../../../data/entity";
|
import { isUnavailableState } from "../../../data/entity";
|
||||||
import { LightEntity } from "../../../data/light";
|
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { computeCardSize } from "../common/compute-card-size";
|
import { computeCardSize } from "../common/compute-card-size";
|
||||||
import { findEntities } from "../common/find-entities";
|
import { findEntities } from "../common/find-entities";
|
||||||
@@ -143,7 +145,7 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
|
|||||||
data-state=${stateObj.state}
|
data-state=${stateObj.state}
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
color: colored ? this._computeColor(stateObj) : undefined,
|
color: colored ? this._computeColor(stateObj) : undefined,
|
||||||
filter: colored ? this._computeBrightness(stateObj) : undefined,
|
filter: colored ? stateColorBrightness(stateObj) : undefined,
|
||||||
height: this._config.icon_height
|
height: this._config.icon_height
|
||||||
? this._config.icon_height
|
? this._config.icon_height
|
||||||
: "",
|
: "",
|
||||||
@@ -214,14 +216,6 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeBrightness(stateObj: HassEntity | LightEntity): string {
|
|
||||||
if (stateObj.attributes.brightness) {
|
|
||||||
const brightness = stateObj.attributes.brightness;
|
|
||||||
return `brightness(${(brightness + 245) / 5}%)`;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||||
// Side Effect used to update footer hass while keeping optimizations
|
// Side Effect used to update footer hass while keeping optimizations
|
||||||
if (this._footerElement) {
|
if (this._footerElement) {
|
||||||
|
@@ -215,7 +215,8 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
|||||||
background: var(--divider-color);
|
background: var(--divider-color);
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
margin: -4px 0;
|
margin-top: -4px;
|
||||||
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
.entity div {
|
.entity div {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@@ -1,25 +1,30 @@
|
|||||||
import { mdiDotsVertical } from "@mdi/js";
|
import { mdiDotsVertical, mdiPower, mdiWaterPercent } from "@mdi/js";
|
||||||
import "@thomasloven/round-slider";
|
import "@thomasloven/round-slider";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
svg,
|
css,
|
||||||
|
html,
|
||||||
nothing,
|
nothing,
|
||||||
|
svg,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { computeAttributeValueDisplay } from "../../../common/entity/compute_attribute_display";
|
import { computeAttributeValueDisplay } from "../../../common/entity/compute_attribute_display";
|
||||||
|
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
||||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||||
|
import { stateColorCss } from "../../../common/entity/state_color";
|
||||||
|
import { formatNumber } from "../../../common/number/format_number";
|
||||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
|
import type { HaCard } from "../../../components/ha-card";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import { isUnavailableState } from "../../../data/entity";
|
import { UNAVAILABLE, isUnavailableState } from "../../../data/entity";
|
||||||
import { HumidifierEntity } from "../../../data/humidifier";
|
import { HumidifierEntity } from "../../../data/humidifier";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { findEntities } from "../common/find-entities";
|
import { findEntities } from "../common/find-entities";
|
||||||
@@ -59,8 +64,10 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
@state() private _setHum?: number;
|
@state() private _setHum?: number;
|
||||||
|
|
||||||
|
@query("ha-card") private _card?: HaCard;
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
return 6;
|
return 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setConfig(config: HumidifierCardConfig): void {
|
public setConfig(config: HumidifierCardConfig): void {
|
||||||
@@ -88,6 +95,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
const name =
|
const name =
|
||||||
this._config!.name ||
|
this._config!.name ||
|
||||||
computeStateName(this.hass!.states[this._config!.entity]);
|
computeStateName(this.hass!.states[this._config!.entity]);
|
||||||
|
|
||||||
const targetHumidity =
|
const targetHumidity =
|
||||||
stateObj.attributes.humidity !== null &&
|
stateObj.attributes.humidity !== null &&
|
||||||
Number.isFinite(Number(stateObj.attributes.humidity))
|
Number.isFinite(Number(stateObj.attributes.humidity))
|
||||||
@@ -102,7 +110,6 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
? html` <round-slider disabled="true"></round-slider> `
|
? html` <round-slider disabled="true"></round-slider> `
|
||||||
: html`
|
: html`
|
||||||
<round-slider
|
<round-slider
|
||||||
class=${classMap({ "round-slider_off": stateObj.state === "off" })}
|
|
||||||
.value=${targetHumidity}
|
.value=${targetHumidity}
|
||||||
.min=${stateObj.attributes.min_humidity}
|
.min=${stateObj.attributes.min_humidity}
|
||||||
.max=${stateObj.attributes.max_humidity}
|
.max=${stateObj.attributes.max_humidity}
|
||||||
@@ -113,47 +120,89 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
></round-slider>
|
></round-slider>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const setValues = html`
|
const currentHumidity = svg`
|
||||||
<svg viewBox="0 0 24 20">
|
<svg viewBox="0 0 40 20">
|
||||||
<text x="50%" dx="1" y="73%" text-anchor="middle" id="set-values">
|
<text
|
||||||
${isUnavailableState(stateObj.state) ||
|
x="50%"
|
||||||
setHumidity === undefined ||
|
dx="1"
|
||||||
setHumidity === null
|
y="60%"
|
||||||
? ""
|
text-anchor="middle"
|
||||||
: svg`
|
style="font-size: 13px;"
|
||||||
${setHumidity.toFixed()}
|
>
|
||||||
<tspan dx="-3" dy="-6.5" style="font-size: 4px;">
|
${
|
||||||
%
|
stateObj.state !== UNAVAILABLE &&
|
||||||
</tspan>
|
stateObj.attributes.current_humidity != null &&
|
||||||
`}
|
!isNaN(stateObj.attributes.current_humidity)
|
||||||
|
? svg`
|
||||||
|
${formatNumber(
|
||||||
|
stateObj.attributes.current_humidity,
|
||||||
|
this.hass.locale
|
||||||
|
)}
|
||||||
|
<tspan dx="-3" dy="-6.5" style="font-size: 4px;">
|
||||||
|
%
|
||||||
|
</tspan>
|
||||||
|
`
|
||||||
|
: nothing
|
||||||
|
}
|
||||||
</text>
|
</text>
|
||||||
</svg>
|
</svg>
|
||||||
`;
|
`;
|
||||||
const currentMode = html`
|
|
||||||
<svg viewBox="0 0 40 10" id="humidity">
|
const setValues = svg`
|
||||||
<text x="50%" y="50%" text-anchor="middle" id="set-mode">
|
<svg id="set-values">
|
||||||
${this.hass!.localize(`state.default.${stateObj.state}`)}
|
<g>
|
||||||
${stateObj.attributes.mode && !isUnavailableState(stateObj.state)
|
<text text-anchor="middle" class="set-value">
|
||||||
? html`
|
${
|
||||||
-
|
stateObj.state !== UNAVAILABLE && setHumidity != null
|
||||||
${computeAttributeValueDisplay(
|
? formatNumber(setHumidity, this.hass.locale, {
|
||||||
this.hass.localize,
|
maximumFractionDigits: 0,
|
||||||
stateObj,
|
})
|
||||||
this.hass.locale,
|
: nothing
|
||||||
this.hass.config,
|
}
|
||||||
this.hass.entities,
|
</text>
|
||||||
"mode"
|
<text
|
||||||
)}
|
dy="22"
|
||||||
`
|
text-anchor="middle"
|
||||||
: ""}
|
id="set-mode"
|
||||||
</text>
|
>
|
||||||
|
${computeStateDisplay(
|
||||||
|
this.hass.localize,
|
||||||
|
stateObj,
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.config,
|
||||||
|
this.hass.entities
|
||||||
|
)}
|
||||||
|
${
|
||||||
|
stateObj.state !== UNAVAILABLE && stateObj.attributes.mode
|
||||||
|
? html`
|
||||||
|
-
|
||||||
|
${computeAttributeValueDisplay(
|
||||||
|
this.hass.localize,
|
||||||
|
stateObj,
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.config,
|
||||||
|
this.hass.entities,
|
||||||
|
"mode"
|
||||||
|
)}
|
||||||
|
`
|
||||||
|
: nothing
|
||||||
|
}
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card>
|
<ha-card
|
||||||
|
style=${styleMap({
|
||||||
|
"--mode-color": stateColorCss(stateObj),
|
||||||
|
})}
|
||||||
|
>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.path=${mdiDotsVertical}
|
.path=${mdiDotsVertical}
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.cards.show_more_info"
|
||||||
|
)}
|
||||||
class="more-info"
|
class="more-info"
|
||||||
@click=${this._handleMoreInfo}
|
@click=${this._handleMoreInfo}
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -164,19 +213,35 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
<div id="slider">
|
<div id="slider">
|
||||||
${slider}
|
${slider}
|
||||||
<div id="slider-center">
|
<div id="slider-center">
|
||||||
<ha-icon-button
|
<div id="humidity">${currentHumidity} ${setValues}</div>
|
||||||
class="toggle-button"
|
|
||||||
.disabled=${isUnavailableState(stateObj.state)}
|
|
||||||
@click=${this._toggle}
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
${setValues}
|
|
||||||
</ha-icon-button>
|
|
||||||
${currentMode}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="info" .title=${name}>${name}</div>
|
<div id="info" .title=${name}>
|
||||||
|
<div id="modes">
|
||||||
|
<ha-icon-button
|
||||||
|
class=${classMap({ "selected-icon": stateObj.state === "on" })}
|
||||||
|
@click=${this._turnOn}
|
||||||
|
tabindex="0"
|
||||||
|
.path=${mdiWaterPercent}
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
`component.humidifier.entity_component._.state.on`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
</ha-icon-button>
|
||||||
|
<ha-icon-button
|
||||||
|
class=${classMap({ "selected-icon": stateObj.state === "off" })}
|
||||||
|
@click=${this._turnOff}
|
||||||
|
tabindex="0"
|
||||||
|
.path=${mdiPower}
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
`component.humidifier.entity_component._.state.off`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
</ha-icon-button>
|
||||||
|
</div>
|
||||||
|
${name}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`;
|
`;
|
||||||
@@ -210,6 +275,15 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
) {
|
) {
|
||||||
applyThemesOnElement(this, this.hass.themes, this._config.theme);
|
applyThemesOnElement(this, this.hass.themes, this._config.theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stateObj = this.hass.states[this._config.entity];
|
||||||
|
if (!stateObj) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!oldHass || oldHass.states[this._config.entity] !== stateObj) {
|
||||||
|
this._rescale_svg();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public willUpdate(changedProps: PropertyValues) {
|
public willUpdate(changedProps: PropertyValues) {
|
||||||
@@ -229,6 +303,27 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _rescale_svg() {
|
||||||
|
// Set the viewbox of the SVG containing the set temperature to perfectly
|
||||||
|
// fit the text
|
||||||
|
// That way it will auto-scale correctly
|
||||||
|
// This is not done to the SVG containing the current temperature, because
|
||||||
|
// it should not be centered on the text, but only on the value
|
||||||
|
const card = this._card;
|
||||||
|
if (card) {
|
||||||
|
card.updateComplete.then(() => {
|
||||||
|
const svgRoot = this.shadowRoot!.querySelector("#set-values")!;
|
||||||
|
const box = svgRoot.querySelector("g")!.getBBox()!;
|
||||||
|
svgRoot.setAttribute(
|
||||||
|
"viewBox",
|
||||||
|
`${box.x} ${box!.y} ${box.width} ${box.height}`
|
||||||
|
);
|
||||||
|
svgRoot.setAttribute("width", `${box.width}`);
|
||||||
|
svgRoot.setAttribute("height", `${box.height}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _getSetHum(stateObj: HassEntity): undefined | number {
|
private _getSetHum(stateObj: HassEntity): undefined | number {
|
||||||
if (isUnavailableState(stateObj.state)) {
|
if (isUnavailableState(stateObj.state)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -248,8 +343,14 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _toggle(): void {
|
private _turnOn(): void {
|
||||||
this.hass!.callService("humidifier", "toggle", {
|
this.hass!.callService("humidifier", "turn_on", {
|
||||||
|
entity_id: this._config!.entity,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _turnOff(): void {
|
||||||
|
this.hass!.callService("humidifier", "turn_off", {
|
||||||
entity_id: this._config!.entity,
|
entity_id: this._config!.entity,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -273,6 +374,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
--name-font-size: 1.2rem;
|
--name-font-size: 1.2rem;
|
||||||
--brightness-font-size: 1.2rem;
|
--brightness-font-size: 1.2rem;
|
||||||
--rail-border-color: transparent;
|
--rail-border-color: transparent;
|
||||||
|
--mode-color: var(--state-inactive-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.more-info {
|
.more-info {
|
||||||
@@ -280,11 +382,11 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
inset-inline-end: 0px;
|
||||||
|
inset-inline-start: initial;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
z-index: 25;
|
z-index: 1;
|
||||||
inset-inline-start: initial;
|
|
||||||
inset-inline-end: 0;
|
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +402,6 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
position: relative;
|
position: relative;
|
||||||
direction: ltr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#slider {
|
#slider {
|
||||||
@@ -313,13 +414,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
round-slider {
|
round-slider {
|
||||||
--round-slider-path-color: var(--slider-track-color);
|
--round-slider-path-color: var(--slider-track-color);
|
||||||
--round-slider-bar-color: var(--primary-color);
|
--round-slider-bar-color: var(--mode-color);
|
||||||
padding-bottom: 10%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.round-slider_off {
|
|
||||||
--round-slider-path-color: var(--slider-track-color);
|
|
||||||
--round-slider-bar-color: var(--disabled-text-color);
|
|
||||||
padding-bottom: 10%;
|
padding-bottom: 10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,37 +428,28 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
top: 20px;
|
top: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#humidity {
|
#humidity {
|
||||||
max-width: 80%;
|
position: absolute;
|
||||||
transform: translate(0, 350%);
|
transform: translate(-50%, -50%);
|
||||||
|
width: 100%;
|
||||||
|
height: 50%;
|
||||||
|
top: 45%;
|
||||||
|
left: 50%;
|
||||||
|
direction: ltr;
|
||||||
}
|
}
|
||||||
|
|
||||||
#set-values {
|
#set-values {
|
||||||
font-size: 13px;
|
max-width: 80%;
|
||||||
font-family: var(--paper-font-body1_-_font-family);
|
transform: translate(0, -50%);
|
||||||
font-weight: var(--paper-font-body1_-_font-weight);
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#set-mode {
|
#set-mode {
|
||||||
fill: var(--secondary-text-color);
|
fill: var(--secondary-text-color);
|
||||||
font-size: 4px;
|
font-size: 16px;
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-button {
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
width: 60%;
|
|
||||||
height: auto;
|
|
||||||
position: absolute;
|
|
||||||
max-width: calc(100% - 40px);
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 100%;
|
|
||||||
top: 39%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
--mdc-icon-button-size: 100%;
|
|
||||||
--mdc-icon-size: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#info {
|
#info {
|
||||||
@@ -375,6 +461,16 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
font-size: var(--name-font-size);
|
font-size: var(--name-font-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#modes > * {
|
||||||
|
color: var(--disabled-text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#modes .selected-icon {
|
||||||
|
color: var(--mode-color);
|
||||||
|
}
|
||||||
|
|
||||||
text {
|
text {
|
||||||
fill: var(--primary-text-color);
|
fill: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
|
@@ -30,6 +30,7 @@ import { hasConfigOrEntityChanged } from "../common/has-changed";
|
|||||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||||
import { LightCardConfig } from "./types";
|
import { LightCardConfig } from "./types";
|
||||||
|
import { stateColorBrightness } from "../../../common/entity/state_color";
|
||||||
|
|
||||||
@customElement("hui-light-card")
|
@customElement("hui-light-card")
|
||||||
export class HuiLightCard extends LitElement implements LovelaceCard {
|
export class HuiLightCard extends LitElement implements LovelaceCard {
|
||||||
@@ -239,8 +240,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
|||||||
if (stateObj.state === "off" || !stateObj.attributes.brightness) {
|
if (stateObj.state === "off" || !stateObj.attributes.brightness) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
const brightness = stateObj.attributes.brightness;
|
return stateColorBrightness(stateObj);
|
||||||
return `brightness(${(brightness + 245) / 5}%)`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeColor(stateObj: LightEntity): string {
|
private _computeColor(stateObj: LightEntity): string {
|
||||||
|
@@ -166,16 +166,19 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
|||||||
style="font-size: 13px;"
|
style="font-size: 13px;"
|
||||||
>
|
>
|
||||||
${
|
${
|
||||||
stateObj.attributes.current_temperature !== null &&
|
stateObj.state !== UNAVAILABLE &&
|
||||||
|
stateObj.attributes.current_temperature != null &&
|
||||||
!isNaN(stateObj.attributes.current_temperature)
|
!isNaN(stateObj.attributes.current_temperature)
|
||||||
? svg`${formatNumber(
|
? svg`
|
||||||
stateObj.attributes.current_temperature,
|
${formatNumber(
|
||||||
this.hass.locale
|
stateObj.attributes.current_temperature,
|
||||||
)}
|
this.hass.locale
|
||||||
<tspan dx="-3" dy="-6.5" style="font-size: 4px;">
|
)}
|
||||||
${this.hass.config.unit_system.temperature}
|
<tspan dx="-3" dy="-6.5" style="font-size: 4px;">
|
||||||
</tspan>`
|
${this.hass.config.unit_system.temperature}
|
||||||
: ""
|
</tspan>
|
||||||
|
`
|
||||||
|
: nothing
|
||||||
}
|
}
|
||||||
</text>
|
</text>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -186,42 +189,14 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
|||||||
<g>
|
<g>
|
||||||
<text text-anchor="middle" class="set-value">
|
<text text-anchor="middle" class="set-value">
|
||||||
${
|
${
|
||||||
stateObj.state === UNAVAILABLE
|
stateObj.state !== UNAVAILABLE && this._setTemp != null
|
||||||
? this.hass.localize("state.default.unavailable")
|
? Array.isArray(this._setTemp)
|
||||||
: this._setTemp === undefined || this._setTemp === null
|
|
||||||
? ""
|
|
||||||
: Array.isArray(this._setTemp)
|
|
||||||
? this._stepSize === 1
|
|
||||||
? svg`
|
? svg`
|
||||||
${formatNumber(this._setTemp[0], this.hass.locale, {
|
${this._formatSetTemp(this._setTemp[0])} -
|
||||||
maximumFractionDigits: 0,
|
${this._formatSetTemp(this._setTemp[1])}
|
||||||
})} -
|
`
|
||||||
${formatNumber(this._setTemp[1], this.hass.locale, {
|
: this._formatSetTemp(this._setTemp)
|
||||||
maximumFractionDigits: 0,
|
: nothing
|
||||||
})}
|
|
||||||
`
|
|
||||||
: svg`
|
|
||||||
${formatNumber(this._setTemp[0], this.hass.locale, {
|
|
||||||
minimumFractionDigits: 1,
|
|
||||||
maximumFractionDigits: 1,
|
|
||||||
})} -
|
|
||||||
${formatNumber(this._setTemp[1], this.hass.locale, {
|
|
||||||
minimumFractionDigits: 1,
|
|
||||||
maximumFractionDigits: 1,
|
|
||||||
})}
|
|
||||||
`
|
|
||||||
: this._stepSize === 1
|
|
||||||
? svg`
|
|
||||||
${formatNumber(this._setTemp, this.hass.locale, {
|
|
||||||
maximumFractionDigits: 0,
|
|
||||||
})}
|
|
||||||
`
|
|
||||||
: svg`
|
|
||||||
${formatNumber(this._setTemp, this.hass.locale, {
|
|
||||||
minimumFractionDigits: 1,
|
|
||||||
maximumFractionDigits: 1,
|
|
||||||
})}
|
|
||||||
`
|
|
||||||
}
|
}
|
||||||
</text>
|
</text>
|
||||||
<text
|
<text
|
||||||
@@ -230,7 +205,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
|||||||
id="set-mode"
|
id="set-mode"
|
||||||
>
|
>
|
||||||
${
|
${
|
||||||
stateObj.attributes.hvac_action
|
stateObj.state !== UNAVAILABLE && stateObj.attributes.hvac_action
|
||||||
? computeAttributeValueDisplay(
|
? computeAttributeValueDisplay(
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
@@ -248,6 +223,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
${
|
${
|
||||||
|
stateObj.state !== UNAVAILABLE &&
|
||||||
stateObj.attributes.preset_mode &&
|
stateObj.attributes.preset_mode &&
|
||||||
stateObj.attributes.preset_mode !== CLIMATE_PRESET_NONE
|
stateObj.attributes.preset_mode !== CLIMATE_PRESET_NONE
|
||||||
? html`
|
? html`
|
||||||
@@ -261,7 +237,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
|||||||
"preset_mode"
|
"preset_mode"
|
||||||
)}
|
)}
|
||||||
`
|
`
|
||||||
: ""
|
: nothing
|
||||||
}
|
}
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
@@ -374,6 +350,17 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _formatSetTemp(temp: number) {
|
||||||
|
return this._stepSize === 1
|
||||||
|
? formatNumber(temp, this.hass!.locale, {
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
})
|
||||||
|
: formatNumber(temp, this.hass!.locale, {
|
||||||
|
minimumFractionDigits: 1,
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private _rescale_svg() {
|
private _rescale_svg() {
|
||||||
// Set the viewbox of the SVG containing the set temperature to perfectly
|
// Set the viewbox of the SVG containing the set temperature to perfectly
|
||||||
// fit the text
|
// fit the text
|
||||||
|
@@ -3,11 +3,11 @@ import { RippleHandlers } from "@material/mwc-ripple/ripple-handlers";
|
|||||||
import { mdiExclamationThick, mdiHelp } from "@mdi/js";
|
import { mdiExclamationThick, mdiHelp } from "@mdi/js";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
|
||||||
LitElement,
|
LitElement,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
nothing,
|
nothing,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import {
|
import {
|
||||||
@@ -37,13 +37,14 @@ import "../../../components/tile/ha-tile-image";
|
|||||||
import "../../../components/tile/ha-tile-info";
|
import "../../../components/tile/ha-tile-info";
|
||||||
import { cameraUrlWithWidthHeight } from "../../../data/camera";
|
import { cameraUrlWithWidthHeight } from "../../../data/camera";
|
||||||
import {
|
import {
|
||||||
computeCoverPositionStateDisplay,
|
|
||||||
CoverEntity,
|
CoverEntity,
|
||||||
|
computeCoverPositionStateDisplay,
|
||||||
} from "../../../data/cover";
|
} from "../../../data/cover";
|
||||||
import { isUnavailableState } from "../../../data/entity";
|
import { isUnavailableState } from "../../../data/entity";
|
||||||
import { computeFanSpeedStateDisplay, FanEntity } from "../../../data/fan";
|
import { FanEntity, computeFanSpeedStateDisplay } from "../../../data/fan";
|
||||||
import { LightEntity } from "../../../data/light";
|
import type { HumidifierEntity } from "../../../data/humidifier";
|
||||||
import { ActionHandlerEvent } from "../../../data/lovelace";
|
import type { LightEntity } from "../../../data/light";
|
||||||
|
import type { ActionHandlerEvent } from "../../../data/lovelace";
|
||||||
import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../../../data/sensor";
|
import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../../../data/sensor";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||||
@@ -51,15 +52,15 @@ import { findEntities } from "../common/find-entities";
|
|||||||
import { handleAction } from "../common/handle-action";
|
import { handleAction } from "../common/handle-action";
|
||||||
import "../components/hui-timestamp-display";
|
import "../components/hui-timestamp-display";
|
||||||
import { createTileFeatureElement } from "../create-element/create-tile-feature-element";
|
import { createTileFeatureElement } from "../create-element/create-tile-feature-element";
|
||||||
import { LovelaceTileFeatureConfig } from "../tile-features/types";
|
import type { LovelaceTileFeatureConfig } from "../tile-features/types";
|
||||||
import {
|
import type {
|
||||||
LovelaceCard,
|
LovelaceCard,
|
||||||
LovelaceCardEditor,
|
LovelaceCardEditor,
|
||||||
LovelaceTileFeature,
|
LovelaceTileFeature,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import { HuiErrorCard } from "./hui-error-card";
|
import type { HuiErrorCard } from "./hui-error-card";
|
||||||
import { computeTileBadge } from "./tile/badges/tile-badge";
|
import { computeTileBadge } from "./tile/badges/tile-badge";
|
||||||
import { ThermostatCardConfig, TileCardConfig } from "./types";
|
import type { ThermostatCardConfig, TileCardConfig } from "./types";
|
||||||
|
|
||||||
const TIMESTAMP_STATE_DOMAINS = ["button", "input_button", "scene"];
|
const TIMESTAMP_STATE_DOMAINS = ["button", "input_button", "scene"];
|
||||||
|
|
||||||
@@ -224,6 +225,15 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (domain === "humidifier" && stateActive(stateObj)) {
|
||||||
|
const humidity = (stateObj as HumidifierEntity).attributes.humidity;
|
||||||
|
if (humidity) {
|
||||||
|
return `${Math.round(humidity)}${blankBeforePercent(
|
||||||
|
this.hass!.locale
|
||||||
|
)}%`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const stateDisplay = computeStateDisplay(
|
const stateDisplay = computeStateDisplay(
|
||||||
this.hass!.localize,
|
this.hass!.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
|
@@ -7,14 +7,17 @@ import {
|
|||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
|
nothing,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, queryAssignedNodes } from "lit/decorators";
|
import { customElement, property, queryAssignedNodes } from "lit/decorators";
|
||||||
|
import deepClone from "deep-clone-simple";
|
||||||
|
import { storage } from "../../../common/decorators/storage";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import "../../../components/ha-button-menu";
|
import "../../../components/ha-button-menu";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import { saveConfig } from "../../../data/lovelace";
|
import { saveConfig, LovelaceCardConfig } from "../../../data/lovelace";
|
||||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { showSaveSuccessToast } from "../../../util/toast-saved-success";
|
import { showSaveSuccessToast } from "../../../util/toast-saved-success";
|
||||||
@@ -32,8 +35,18 @@ export class HuiCardOptions extends LitElement {
|
|||||||
|
|
||||||
@property() public path?: [number, number];
|
@property() public path?: [number, number];
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public showPosition = false;
|
||||||
|
|
||||||
@queryAssignedNodes() private _assignedNodes?: NodeListOf<LovelaceCard>;
|
@queryAssignedNodes() private _assignedNodes?: NodeListOf<LovelaceCard>;
|
||||||
|
|
||||||
|
@storage({
|
||||||
|
key: "lovelaceClipboard",
|
||||||
|
state: false,
|
||||||
|
subscribe: false,
|
||||||
|
storage: "sessionStorage",
|
||||||
|
})
|
||||||
|
protected _clipboard?: LovelaceCardConfig;
|
||||||
|
|
||||||
public getCardSize() {
|
public getCardSize() {
|
||||||
return this._assignedNodes ? computeCardSize(this._assignedNodes[0]) : 1;
|
return this._assignedNodes ? computeCardSize(this._assignedNodes[0]) : 1;
|
||||||
}
|
}
|
||||||
@@ -58,7 +71,7 @@ export class HuiCardOptions extends LitElement {
|
|||||||
"ui.panel.lovelace.editor.edit_card.edit"
|
"ui.panel.lovelace.editor.edit_card.edit"
|
||||||
)}</mwc-button
|
)}</mwc-button
|
||||||
>
|
>
|
||||||
<div>
|
<div class="right">
|
||||||
<slot name="buttons"></slot>
|
<slot name="buttons"></slot>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass!.localize(
|
.label=${this.hass!.localize(
|
||||||
@@ -71,6 +84,22 @@ export class HuiCardOptions extends LitElement {
|
|||||||
.length ===
|
.length ===
|
||||||
this.path![1] + 1}
|
this.path![1] + 1}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
|
${this.showPosition
|
||||||
|
? html`<div class="position-badge">
|
||||||
|
${this.path![1] + 1}
|
||||||
|
<simple-tooltip
|
||||||
|
>${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.edit_card.position",
|
||||||
|
"position",
|
||||||
|
`${this.path![1] + 1}`,
|
||||||
|
"total",
|
||||||
|
`${
|
||||||
|
this.lovelace!.config.views[this.path![0]].cards!.length
|
||||||
|
}`
|
||||||
|
)}</simple-tooltip
|
||||||
|
>
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass!.localize(
|
.label=${this.hass!.localize(
|
||||||
"ui.panel.lovelace.editor.edit_card.move_up"
|
"ui.panel.lovelace.editor.edit_card.move_up"
|
||||||
@@ -98,6 +127,16 @@ export class HuiCardOptions extends LitElement {
|
|||||||
"ui.panel.lovelace.editor.edit_card.duplicate"
|
"ui.panel.lovelace.editor.edit_card.duplicate"
|
||||||
)}</mwc-list-item
|
)}</mwc-list-item
|
||||||
>
|
>
|
||||||
|
<mwc-list-item
|
||||||
|
>${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.edit_card.copy"
|
||||||
|
)}</mwc-list-item
|
||||||
|
>
|
||||||
|
<mwc-list-item
|
||||||
|
>${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.edit_card.cut"
|
||||||
|
)}</mwc-list-item
|
||||||
|
>
|
||||||
<mwc-list-item class="delete-item">
|
<mwc-list-item class="delete-item">
|
||||||
${this.hass!.localize(
|
${this.hass!.localize(
|
||||||
"ui.panel.lovelace.editor.edit_card.delete"
|
"ui.panel.lovelace.editor.edit_card.delete"
|
||||||
@@ -135,6 +174,23 @@ export class HuiCardOptions extends LitElement {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.position-badge {
|
||||||
|
display: inline-block;
|
||||||
|
width: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-weight: 400;
|
||||||
|
text-align: center;
|
||||||
|
background-color: var(--app-header-edit-background-color, #455a64);
|
||||||
|
color: var(--app-header-edit-text-color, white);
|
||||||
|
}
|
||||||
|
|
||||||
ha-icon-button {
|
ha-icon-button {
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
@@ -163,7 +219,13 @@ export class HuiCardOptions extends LitElement {
|
|||||||
this._duplicateCard();
|
this._duplicateCard();
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
this._deleteCard();
|
this._copyCard();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
this._cutCard();
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
this._deleteCard(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,6 +245,17 @@ export class HuiCardOptions extends LitElement {
|
|||||||
fireEvent(this, "ll-edit-card", { path: this.path! });
|
fireEvent(this, "ll-edit-card", { path: this.path! });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _cutCard(): void {
|
||||||
|
this._copyCard();
|
||||||
|
this._deleteCard(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _copyCard(): void {
|
||||||
|
const cardConfig =
|
||||||
|
this.lovelace!.config.views[this.path![0]].cards![this.path![1]];
|
||||||
|
this._clipboard = deepClone(cardConfig);
|
||||||
|
}
|
||||||
|
|
||||||
private _cardUp(): void {
|
private _cardUp(): void {
|
||||||
const lovelace = this.lovelace!;
|
const lovelace = this.lovelace!;
|
||||||
const path = this.path!;
|
const path = this.path!;
|
||||||
@@ -236,8 +309,8 @@ export class HuiCardOptions extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _deleteCard(): void {
|
private _deleteCard(confirm: boolean): void {
|
||||||
fireEvent(this, "ll-delete-card", { path: this.path! });
|
fireEvent(this, "ll-delete-card", { path: this.path!, confirm });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -15,6 +15,7 @@ import { classMap } from "lit/directives/class-map";
|
|||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { until } from "lit/directives/until";
|
import { until } from "lit/directives/until";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import "../../../../components/ha-circular-progress";
|
import "../../../../components/ha-circular-progress";
|
||||||
import "../../../../components/search-input";
|
import "../../../../components/search-input";
|
||||||
@@ -49,6 +50,14 @@ interface CardElement {
|
|||||||
export class HuiCardPicker extends LitElement {
|
export class HuiCardPicker extends LitElement {
|
||||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
|
@storage({
|
||||||
|
key: "lovelaceClipboard",
|
||||||
|
state: true,
|
||||||
|
subscribe: true,
|
||||||
|
storage: "sessionStorage",
|
||||||
|
})
|
||||||
|
private _clipboard?: LovelaceCardConfig;
|
||||||
|
|
||||||
@state() private _cards: CardElement[] = [];
|
@state() private _cards: CardElement[] = [];
|
||||||
|
|
||||||
public lovelace?: LovelaceConfig;
|
public lovelace?: LovelaceConfig;
|
||||||
@@ -114,6 +123,37 @@ export class HuiCardPicker extends LitElement {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div class="cards-container">
|
<div class="cards-container">
|
||||||
|
${this._clipboard
|
||||||
|
? html`
|
||||||
|
${until(
|
||||||
|
this._renderCardElement(
|
||||||
|
{
|
||||||
|
type: this._clipboard.type,
|
||||||
|
showElement: true,
|
||||||
|
isCustom: false,
|
||||||
|
name: this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.card.generic.paste"
|
||||||
|
),
|
||||||
|
description: `${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.card.generic.paste_description",
|
||||||
|
{
|
||||||
|
type: this._clipboard.type,
|
||||||
|
}
|
||||||
|
)}`,
|
||||||
|
},
|
||||||
|
this._clipboard
|
||||||
|
),
|
||||||
|
html`
|
||||||
|
<div class="card spinner">
|
||||||
|
<ha-circular-progress
|
||||||
|
active
|
||||||
|
alt="Loading"
|
||||||
|
></ha-circular-progress>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
${this._filterCards(this._cards, this._filter).map(
|
${this._filterCards(this._cards, this._filter).map(
|
||||||
(cardElement: CardElement) => cardElement.element
|
(cardElement: CardElement) => cardElement.element
|
||||||
)}
|
)}
|
||||||
@@ -272,7 +312,10 @@ export class HuiCardPicker extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _renderCardElement(card: Card): Promise<TemplateResult> {
|
private async _renderCardElement(
|
||||||
|
card: Card,
|
||||||
|
config?: LovelaceCardConfig
|
||||||
|
): Promise<TemplateResult> {
|
||||||
let { type } = card;
|
let { type } = card;
|
||||||
const { showElement, isCustom, name, description } = card;
|
const { showElement, isCustom, name, description } = card;
|
||||||
const customCard = isCustom ? getCustomCardEntry(type) : undefined;
|
const customCard = isCustom ? getCustomCardEntry(type) : undefined;
|
||||||
@@ -281,15 +324,17 @@ export class HuiCardPicker extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let element: LovelaceCard | undefined;
|
let element: LovelaceCard | undefined;
|
||||||
let cardConfig: LovelaceCardConfig = { type };
|
let cardConfig: LovelaceCardConfig = config ?? { type };
|
||||||
|
|
||||||
if (this.hass && this.lovelace) {
|
if (this.hass && this.lovelace) {
|
||||||
cardConfig = await getCardStubConfig(
|
if (!config) {
|
||||||
this.hass,
|
cardConfig = await getCardStubConfig(
|
||||||
type,
|
this.hass,
|
||||||
this._unusedEntities!,
|
type,
|
||||||
this._usedEntities!
|
this._unusedEntities!,
|
||||||
);
|
this._usedEntities!
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (showElement) {
|
if (showElement) {
|
||||||
try {
|
try {
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@material/mwc-tab-bar/mwc-tab-bar";
|
import "@material/mwc-tab-bar/mwc-tab-bar";
|
||||||
import "@material/mwc-tab/mwc-tab";
|
import "@material/mwc-tab/mwc-tab";
|
||||||
|
import { mdiContentCopy } from "@mdi/js";
|
||||||
|
import deepClone from "deep-clone-simple";
|
||||||
import type { MDCTabBarActivatedEvent } from "@material/tab-bar";
|
import type { MDCTabBarActivatedEvent } from "@material/tab-bar";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
@@ -13,6 +15,7 @@ import {
|
|||||||
optional,
|
optional,
|
||||||
string,
|
string,
|
||||||
} from "superstruct";
|
} from "superstruct";
|
||||||
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||||
import "../../../../components/entity/ha-entity-picker";
|
import "../../../../components/entity/ha-entity-picker";
|
||||||
@@ -56,6 +59,14 @@ export class HuiConditionalCardEditor
|
|||||||
|
|
||||||
@property({ attribute: false }) public lovelace?: LovelaceConfig;
|
@property({ attribute: false }) public lovelace?: LovelaceConfig;
|
||||||
|
|
||||||
|
@storage({
|
||||||
|
key: "lovelaceClipboard",
|
||||||
|
state: false,
|
||||||
|
subscribe: false,
|
||||||
|
storage: "sessionStorage",
|
||||||
|
})
|
||||||
|
protected _clipboard?: LovelaceCardConfig;
|
||||||
|
|
||||||
@state() private _config?: ConditionalCardConfig;
|
@state() private _config?: ConditionalCardConfig;
|
||||||
|
|
||||||
@state() private _GUImode = true;
|
@state() private _GUImode = true;
|
||||||
@@ -114,6 +125,14 @@ export class HuiConditionalCardEditor
|
|||||||
: "ui.panel.lovelace.editor.edit_card.show_visual_editor"
|
: "ui.panel.lovelace.editor.edit_card.show_visual_editor"
|
||||||
)}
|
)}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
|
|
||||||
|
<ha-icon-button
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.edit_card.copy"
|
||||||
|
)}
|
||||||
|
.path=${mdiContentCopy}
|
||||||
|
@click=${this._handleCopyCard}
|
||||||
|
></ha-icon-button>
|
||||||
<mwc-button @click=${this._handleReplaceCard}
|
<mwc-button @click=${this._handleReplaceCard}
|
||||||
>${this.hass!.localize(
|
>${this.hass!.localize(
|
||||||
"ui.panel.lovelace.editor.card.conditional.change_type"
|
"ui.panel.lovelace.editor.card.conditional.change_type"
|
||||||
@@ -238,6 +257,13 @@ export class HuiConditionalCardEditor
|
|||||||
fireEvent(this, "config-changed", { config: this._config });
|
fireEvent(this, "config-changed", { config: this._config });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected _handleCopyCard() {
|
||||||
|
if (!this._config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._clipboard = deepClone(this._config.card);
|
||||||
|
}
|
||||||
|
|
||||||
private _handleCardChanged(ev: HASSDomEvent<ConfigChangedEvent>): void {
|
private _handleCardChanged(ev: HASSDomEvent<ConfigChangedEvent>): void {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
if (!this._config) {
|
if (!this._config) {
|
||||||
|
@@ -1,6 +1,14 @@
|
|||||||
import { mdiArrowLeft, mdiArrowRight, mdiDelete, mdiPlus } from "@mdi/js";
|
import {
|
||||||
|
mdiArrowLeft,
|
||||||
|
mdiArrowRight,
|
||||||
|
mdiDelete,
|
||||||
|
mdiContentCut,
|
||||||
|
mdiContentCopy,
|
||||||
|
mdiPlus,
|
||||||
|
} from "@mdi/js";
|
||||||
import "@polymer/paper-tabs";
|
import "@polymer/paper-tabs";
|
||||||
import "@polymer/paper-tabs/paper-tab";
|
import "@polymer/paper-tabs/paper-tab";
|
||||||
|
import deepClone from "deep-clone-simple";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import {
|
import {
|
||||||
@@ -12,6 +20,7 @@ import {
|
|||||||
optional,
|
optional,
|
||||||
string,
|
string,
|
||||||
} from "superstruct";
|
} from "superstruct";
|
||||||
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||||
import "../../../../components/ha-icon-button";
|
import "../../../../components/ha-icon-button";
|
||||||
import { LovelaceCardConfig, LovelaceConfig } from "../../../../data/lovelace";
|
import { LovelaceCardConfig, LovelaceConfig } from "../../../../data/lovelace";
|
||||||
@@ -43,6 +52,14 @@ export class HuiStackCardEditor
|
|||||||
|
|
||||||
@property({ attribute: false }) public lovelace?: LovelaceConfig;
|
@property({ attribute: false }) public lovelace?: LovelaceConfig;
|
||||||
|
|
||||||
|
@storage({
|
||||||
|
key: "lovelaceClipboard",
|
||||||
|
state: false,
|
||||||
|
subscribe: false,
|
||||||
|
storage: "sessionStorage",
|
||||||
|
})
|
||||||
|
protected _clipboard?: LovelaceCardConfig;
|
||||||
|
|
||||||
@state() protected _config?: StackCardConfig;
|
@state() protected _config?: StackCardConfig;
|
||||||
|
|
||||||
@state() protected _selectedCard = 0;
|
@state() protected _selectedCard = 0;
|
||||||
@@ -129,6 +146,22 @@ export class HuiStackCardEditor
|
|||||||
.move=${1}
|
.move=${1}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
|
|
||||||
|
<ha-icon-button
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.edit_card.copy"
|
||||||
|
)}
|
||||||
|
.path=${mdiContentCopy}
|
||||||
|
@click=${this._handleCopyCard}
|
||||||
|
></ha-icon-button>
|
||||||
|
|
||||||
|
<ha-icon-button
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.edit_card.cut"
|
||||||
|
)}
|
||||||
|
.path=${mdiContentCut}
|
||||||
|
@click=${this._handleCutCard}
|
||||||
|
></ha-icon-button>
|
||||||
|
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass!.localize(
|
.label=${this.hass!.localize(
|
||||||
"ui.panel.lovelace.editor.edit_card.delete"
|
"ui.panel.lovelace.editor.edit_card.delete"
|
||||||
@@ -191,6 +224,18 @@ export class HuiStackCardEditor
|
|||||||
fireEvent(this, "config-changed", { config: this._config });
|
fireEvent(this, "config-changed", { config: this._config });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected _handleCopyCard() {
|
||||||
|
if (!this._config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._clipboard = deepClone(this._config.cards[this._selectedCard]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _handleCutCard() {
|
||||||
|
this._handleCopyCard();
|
||||||
|
this._handleDeleteCard();
|
||||||
|
}
|
||||||
|
|
||||||
protected _handleDeleteCard() {
|
protected _handleDeleteCard() {
|
||||||
if (!this._config) {
|
if (!this._config) {
|
||||||
return;
|
return;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { html, LitElement, nothing, PropertyValues, TemplateResult } from "lit";
|
import { html, LitElement, nothing, PropertyValues, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "../../../components/ha-date-input";
|
import "../../../components/ha-date-input";
|
||||||
import { isUnavailableState } from "../../../data/entity";
|
import { isUnavailableState, UNAVAILABLE } from "../../../data/entity";
|
||||||
import { setDateValue } from "../../../data/date";
|
import { setDateValue } from "../../../data/date";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||||
@@ -41,14 +41,16 @@ class HuiDateEntityRow extends LitElement implements LovelaceRow {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const unavailable = isUnavailableState(stateObj.state);
|
const unavailable = stateObj.state === UNAVAILABLE;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
||||||
<ha-date-input
|
<ha-date-input
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
.disabled=${unavailable}
|
.disabled=${unavailable}
|
||||||
.value=${unavailable ? "" : stateObj.state}
|
.value=${isUnavailableState(stateObj.state)
|
||||||
|
? undefined
|
||||||
|
: stateObj.state}
|
||||||
@value-changed=${this._dateChanged}
|
@value-changed=${this._dateChanged}
|
||||||
>
|
>
|
||||||
</ha-date-input>
|
</ha-date-input>
|
||||||
@@ -57,7 +59,9 @@ class HuiDateEntityRow extends LitElement implements LovelaceRow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _dateChanged(ev: CustomEvent<{ value: string }>): void {
|
private _dateChanged(ev: CustomEvent<{ value: string }>): void {
|
||||||
setDateValue(this.hass!, this._config!.entity, ev.detail.value);
|
if (ev.detail.value) {
|
||||||
|
setDateValue(this.hass!, this._config!.entity, ev.detail.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user