mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-06 19:56:33 +00:00
Compare commits
9 Commits
20230705.1
...
persistent
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ecba22d301 | ||
![]() |
72172cabc2 | ||
![]() |
4ffd31974c | ||
![]() |
1faef71dcb | ||
![]() |
1e5c35c158 | ||
![]() |
2e3ce4ae9e | ||
![]() |
30f2a49fbf | ||
![]() |
101e9323a7 | ||
![]() |
4526a46a56 |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -8,4 +8,4 @@ plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||
spec: "@yarnpkg/plugin-interactive-tools"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.6.1.cjs
|
||||
yarnPath: .yarn/releases/yarn-3.6.0.cjs
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import fs from "fs/promises";
|
||||
import gulp from "gulp";
|
||||
import mapStream from "map-stream";
|
||||
import transform from "gulp-json-transform";
|
||||
|
||||
const inDirFrontend = "translations/frontend";
|
||||
const inDirBackend = "translations/backend";
|
||||
@@ -42,31 +41,7 @@ function checkHtml() {
|
||||
});
|
||||
}
|
||||
|
||||
function convertBackendTranslations(data, _file) {
|
||||
const output = { component: {} };
|
||||
if (!data.component) {
|
||||
return output;
|
||||
}
|
||||
Object.keys(data.component).forEach((domain) => {
|
||||
if (!("entity_component" in data.component[domain])) {
|
||||
return;
|
||||
}
|
||||
output.component[domain] = { entity_component: {} };
|
||||
Object.keys(data.component[domain].entity_component).forEach((key) => {
|
||||
output.component[domain].entity_component[key] =
|
||||
data.component[domain].entity_component[key];
|
||||
});
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
gulp.task("convert-backend-translations", function () {
|
||||
return gulp
|
||||
.src([`${inDirBackend}/*.json`])
|
||||
.pipe(transform((data, file) => convertBackendTranslations(data, file)))
|
||||
.pipe(gulp.dest(inDirBackend));
|
||||
});
|
||||
|
||||
// Backend translations do not currently pass HTML check so are excluded here for now
|
||||
gulp.task("check-translations-html", function () {
|
||||
return gulp.src([`${inDirFrontend}/*.json`]).pipe(checkHtml());
|
||||
});
|
||||
|
@@ -142,5 +142,4 @@ module.exports = {
|
||||
createCastConfig,
|
||||
createHassioConfig,
|
||||
createGalleryConfig,
|
||||
createRollupConfig,
|
||||
};
|
||||
|
@@ -253,5 +253,4 @@ module.exports = {
|
||||
createCastConfig,
|
||||
createHassioConfig,
|
||||
createGalleryConfig,
|
||||
createWebpackConfig,
|
||||
};
|
||||
|
@@ -1,21 +1,21 @@
|
||||
import { framework } from "../receiver/cast_framework";
|
||||
import { cast } from "chromecast-caf-receiver";
|
||||
|
||||
const castContext = framework.CastReceiverContext.getInstance();
|
||||
const castContext = cast.framework.CastReceiverContext.getInstance();
|
||||
|
||||
const playerManager = castContext.getPlayerManager();
|
||||
|
||||
playerManager.setMessageInterceptor(
|
||||
framework.messages.MessageType.LOAD,
|
||||
cast.framework.messages.MessageType.LOAD,
|
||||
(loadRequestData) => {
|
||||
const media = loadRequestData.media;
|
||||
// Special handling if it came from Google Assistant
|
||||
if (media.entity) {
|
||||
media.contentId = media.entity;
|
||||
media.streamType = framework.messages.StreamType.LIVE;
|
||||
media.streamType = cast.framework.messages.StreamType.LIVE;
|
||||
media.contentType = "application/vnd.apple.mpegurl";
|
||||
// @ts-ignore
|
||||
media.hlsVideoSegmentFormat =
|
||||
framework.messages.HlsVideoSegmentFormat.FMP4;
|
||||
cast.framework.messages.HlsVideoSegmentFormat.FMP4;
|
||||
}
|
||||
return loadRequestData;
|
||||
}
|
||||
|
@@ -1,3 +1,3 @@
|
||||
import { framework } from "./cast_framework";
|
||||
import { cast } from "chromecast-caf-receiver";
|
||||
|
||||
export const castContext = framework.CastReceiverContext.getInstance();
|
||||
export const castContext = cast.framework.CastReceiverContext.getInstance();
|
||||
|
@@ -1,3 +0,0 @@
|
||||
import type { cast as ReceiverCast } from "chromecast-caf-receiver";
|
||||
|
||||
export const framework = (cast as unknown as typeof ReceiverCast).framework;
|
@@ -1,4 +1,4 @@
|
||||
import { framework } from "./cast_framework";
|
||||
import { cast } from "chromecast-caf-receiver";
|
||||
import { CAST_NS } from "../../../src/cast/const";
|
||||
import { HassMessage } from "../../../src/cast/receiver_messages";
|
||||
import "../../../src/resources/custom-card-support";
|
||||
@@ -34,14 +34,14 @@ const setTouchControlsVisibility = (visible: boolean) => {
|
||||
let timeOut: number | undefined;
|
||||
|
||||
const playDummyMedia = (viewTitle?: string) => {
|
||||
const loadRequestData = new framework.messages.LoadRequestData();
|
||||
const loadRequestData = new cast.framework.messages.LoadRequestData();
|
||||
loadRequestData.autoplay = true;
|
||||
loadRequestData.media = new framework.messages.MediaInformation();
|
||||
loadRequestData.media = new cast.framework.messages.MediaInformation();
|
||||
loadRequestData.media.contentId =
|
||||
"https://cast.home-assistant.io/images/google-nest-hub.png";
|
||||
loadRequestData.media.contentType = "image/jpeg";
|
||||
loadRequestData.media.streamType = framework.messages.StreamType.NONE;
|
||||
const metadata = new framework.messages.GenericMediaMetadata();
|
||||
loadRequestData.media.streamType = cast.framework.messages.StreamType.NONE;
|
||||
const metadata = new cast.framework.messages.GenericMediaMetadata();
|
||||
metadata.title = viewTitle;
|
||||
loadRequestData.media.metadata = metadata;
|
||||
|
||||
@@ -86,10 +86,10 @@ const showMediaPlayer = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const options = new framework.CastReceiverOptions();
|
||||
const options = new cast.framework.CastReceiverOptions();
|
||||
options.disableIdleTimeout = true;
|
||||
options.customNamespaces = {
|
||||
[CAST_NS]: framework.system.MessageType.JSON,
|
||||
[CAST_NS]: cast.framework.system.MessageType.JSON,
|
||||
};
|
||||
|
||||
castContext.addCustomMessageListener(
|
||||
@@ -98,7 +98,8 @@ castContext.addCustomMessageListener(
|
||||
(ev: ReceivedMessage<HassMessage>) => {
|
||||
// We received a show Lovelace command, stop media from playing, hide media player and show Lovelace controller
|
||||
if (
|
||||
playerManager.getPlayerState() !== framework.messages.PlayerState.IDLE
|
||||
playerManager.getPlayerState() !==
|
||||
cast.framework.messages.PlayerState.IDLE
|
||||
) {
|
||||
playerManager.stop();
|
||||
} else {
|
||||
@@ -113,7 +114,7 @@ castContext.addCustomMessageListener(
|
||||
const playerManager = castContext.getPlayerManager();
|
||||
|
||||
playerManager.setMessageInterceptor(
|
||||
framework.messages.MessageType.LOAD,
|
||||
cast.framework.messages.MessageType.LOAD,
|
||||
(loadRequestData) => {
|
||||
if (
|
||||
loadRequestData.media.contentId ===
|
||||
@@ -127,24 +128,25 @@ playerManager.setMessageInterceptor(
|
||||
// Special handling if it came from Google Assistant
|
||||
if (media.entity) {
|
||||
media.contentId = media.entity;
|
||||
media.streamType = framework.messages.StreamType.LIVE;
|
||||
media.streamType = cast.framework.messages.StreamType.LIVE;
|
||||
media.contentType = "application/vnd.apple.mpegurl";
|
||||
// @ts-ignore
|
||||
media.hlsVideoSegmentFormat =
|
||||
framework.messages.HlsVideoSegmentFormat.FMP4;
|
||||
cast.framework.messages.HlsVideoSegmentFormat.FMP4;
|
||||
}
|
||||
return loadRequestData;
|
||||
}
|
||||
);
|
||||
|
||||
playerManager.addEventListener(
|
||||
framework.events.EventType.MEDIA_STATUS,
|
||||
cast.framework.events.EventType.MEDIA_STATUS,
|
||||
(event) => {
|
||||
if (
|
||||
event.mediaStatus?.playerState === framework.messages.PlayerState.IDLE &&
|
||||
event.mediaStatus?.playerState ===
|
||||
cast.framework.messages.PlayerState.IDLE &&
|
||||
event.mediaStatus?.idleReason &&
|
||||
event.mediaStatus?.idleReason !==
|
||||
framework.messages.IdleReason.INTERRUPTED
|
||||
cast.framework.messages.IdleReason.INTERRUPTED
|
||||
) {
|
||||
// media finished or stopped, return to default Lovelace
|
||||
showLovelaceController();
|
||||
|
@@ -162,7 +162,6 @@ export class DemoAutomationDescribeAction extends LitElement {
|
||||
super.firstUpdated(changedProps);
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("config", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
|
||||
|
@@ -89,7 +89,6 @@ export class DemoAutomationDescribeCondition extends LitElement {
|
||||
super.firstUpdated(changedProps);
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("config", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
|
||||
|
@@ -40,7 +40,6 @@ const triggers = [
|
||||
},
|
||||
{ platform: "sun", event: "sunset" },
|
||||
{ platform: "time_pattern" },
|
||||
{ platform: "time_pattern", hours: "*", minutes: "/5", seconds: "10" },
|
||||
{ platform: "webhook" },
|
||||
{ platform: "persistent_notification" },
|
||||
{
|
||||
@@ -52,11 +51,6 @@ const triggers = [
|
||||
{ platform: "tag" },
|
||||
{ platform: "time", at: "15:32" },
|
||||
{ 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" },
|
||||
];
|
||||
|
||||
@@ -106,7 +100,6 @@ export class DemoAutomationDescribeTrigger extends LitElement {
|
||||
super.firstUpdated(changedProps);
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("config", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
|
||||
|
@@ -25,7 +25,6 @@ import { HaDeviceTrigger } from "../../../../src/panels/config/automation/trigge
|
||||
import { HaStateTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-state";
|
||||
import { HaMQTTTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt";
|
||||
import "../../../../src/panels/config/automation/trigger/ha-automation-trigger";
|
||||
import { HaConversationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-conversation";
|
||||
|
||||
const SCHEMAS: { name: string; triggers: Trigger[] }[] = [
|
||||
{
|
||||
@@ -113,16 +112,6 @@ const SCHEMAS: { name: string; triggers: Trigger[] }[] = [
|
||||
name: "Device Trigger",
|
||||
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")
|
||||
|
@@ -10,23 +10,23 @@ export class DemoHaCircularSlider extends LitElement {
|
||||
private current = 22;
|
||||
|
||||
@state()
|
||||
private low = 19;
|
||||
private value = 19;
|
||||
|
||||
@state()
|
||||
private high = 25;
|
||||
|
||||
@state()
|
||||
private changingLow?: number;
|
||||
private changingValue?: number;
|
||||
|
||||
@state()
|
||||
private changingHigh?: number;
|
||||
|
||||
private _lowChanged(ev) {
|
||||
this.low = ev.detail.value;
|
||||
private _valueChanged(ev) {
|
||||
this.value = ev.detail.value;
|
||||
}
|
||||
|
||||
private _lowChanging(ev) {
|
||||
this.changingLow = ev.detail.value;
|
||||
private _valueChanging(ev) {
|
||||
this.changingValue = ev.detail.value;
|
||||
}
|
||||
|
||||
private _highChanged(ev) {
|
||||
@@ -63,40 +63,19 @@ export class DemoHaCircularSlider extends LitElement {
|
||||
<div class="card-content">
|
||||
<p class="title"><b>Single</b></p>
|
||||
<ha-control-circular-slider
|
||||
@value-changed=${this._lowChanged}
|
||||
@value-changing=${this._lowChanging}
|
||||
.value=${this.low}
|
||||
@value-changed=${this._valueChanged}
|
||||
@value-changing=${this._valueChanging}
|
||||
.value=${this.value}
|
||||
.current=${this.current}
|
||||
step="1"
|
||||
min="10"
|
||||
max="30"
|
||||
></ha-control-circular-slider>
|
||||
<div>
|
||||
Low: ${this.low} °C
|
||||
Value: ${this.value} °C
|
||||
<br />
|
||||
Changing:
|
||||
${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` : "-"}
|
||||
${this.changingValue != null ? `${this.changingValue} °C` : "-"}
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -105,11 +84,11 @@ export class DemoHaCircularSlider extends LitElement {
|
||||
<p class="title"><b>Dual</b></p>
|
||||
<ha-control-circular-slider
|
||||
dual
|
||||
@low-changed=${this._lowChanged}
|
||||
@low-changing=${this._lowChanging}
|
||||
@low-changed=${this._valueChanged}
|
||||
@low-changing=${this._valueChanging}
|
||||
@high-changed=${this._highChanged}
|
||||
@high-changing=${this._highChanging}
|
||||
.low=${this.low}
|
||||
.low=${this.value}
|
||||
.high=${this.high}
|
||||
.current=${this.current}
|
||||
step="1"
|
||||
@@ -117,10 +96,10 @@ export class DemoHaCircularSlider extends LitElement {
|
||||
max="30"
|
||||
></ha-control-circular-slider>
|
||||
<div>
|
||||
Low value: ${this.low} °C
|
||||
Low value: ${this.value} °C
|
||||
<br />
|
||||
Low changing:
|
||||
${this.changingLow != null ? `${this.changingLow} °C` : "-"}
|
||||
${this.changingValue != null ? `${this.changingValue} °C` : "-"}
|
||||
<br />
|
||||
High value: ${this.high} °C
|
||||
<br />
|
||||
@@ -153,10 +132,6 @@ export class DemoHaCircularSlider extends LitElement {
|
||||
--control-circular-slider-background: #ff9800;
|
||||
--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] {
|
||||
--control-circular-slider-high-color: #2196f3;
|
||||
--control-circular-slider-low-color: #ff9800;
|
||||
|
@@ -265,8 +265,6 @@ export class DemoIntegrationCard extends LitElement {
|
||||
></ha-config-flow-card>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<div class="container">
|
||||
${configEntries.map(
|
||||
(info) => html`
|
||||
<ha-integration-card
|
||||
@@ -340,10 +338,10 @@ export class DemoIntegrationCard extends LitElement {
|
||||
return css`
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
grid-gap: 8px 8px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
grid-gap: 16px 16px;
|
||||
padding: 8px 16px 16px;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
|
||||
.container > * {
|
||||
|
@@ -114,22 +114,11 @@ class HassioAddonInfo extends LitElement {
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
private _fetchDataTimeout?: number;
|
||||
|
||||
private _addonStoreInfo = memoizeOne(
|
||||
(slug: string, storeAddons: StoreAddon[]) =>
|
||||
storeAddons.find((addon) => addon.slug === slug)
|
||||
);
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
|
||||
if (this._fetchDataTimeout) {
|
||||
clearInterval(this._fetchDataTimeout);
|
||||
this._fetchDataTimeout = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const addonStoreInfo =
|
||||
!this.addon.detached && !this.addon.available
|
||||
@@ -603,10 +592,7 @@ class HassioAddonInfo extends LitElement {
|
||||
</ha-progress-button>
|
||||
`
|
||||
: html`
|
||||
<ha-progress-button
|
||||
@click=${this._startClicked}
|
||||
.progress=${this.addon.state === "startup"}
|
||||
>
|
||||
<ha-progress-button @click=${this._startClicked}>
|
||||
${this.supervisor.localize("addon.dashboard.start")}
|
||||
</ha-progress-button>
|
||||
`
|
||||
@@ -686,36 +672,9 @@ class HassioAddonInfo extends LitElement {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.has("addon")) {
|
||||
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> {
|
||||
if ("state" in this.addon && this.addon.state === "started") {
|
||||
this._metrics = await fetchHassioStats(
|
||||
|
@@ -137,9 +137,6 @@ class HassioAddons extends LitElement {
|
||||
--mdc-text-field-fill-color: var(--sidebar-background-color);
|
||||
--mdc-text-field-idle-line-color: var(--divider-color);
|
||||
}
|
||||
.content {
|
||||
margin-bottom: 72px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -16,7 +16,6 @@ import "../../../src/components/ha-icon-button";
|
||||
import {
|
||||
fetchHassioAddonInfo,
|
||||
HassioAddonDetails,
|
||||
startHassioAddon,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import {
|
||||
@@ -24,10 +23,7 @@ import {
|
||||
validateHassioSession,
|
||||
} from "../../../src/data/hassio/ingress";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import "../../../src/layouts/hass-subpage";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
@@ -38,20 +34,17 @@ class HassioIngressView extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
@property() public route!: Route;
|
||||
|
||||
@property({ type: Boolean }) public ingressPanel = false;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
@property() public ingressPanel = false;
|
||||
|
||||
@state() private _addon?: HassioAddonDetails;
|
||||
|
||||
@state() private _loadingMessage?: string;
|
||||
@property({ type: Boolean })
|
||||
public narrow = false;
|
||||
|
||||
private _sessionKeepAlive?: number;
|
||||
|
||||
private _fetchDataTimeout?: number;
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
|
||||
@@ -59,23 +52,16 @@ class HassioIngressView extends LitElement {
|
||||
clearInterval(this._sessionKeepAlive);
|
||||
this._sessionKeepAlive = undefined;
|
||||
}
|
||||
if (this._fetchDataTimeout) {
|
||||
clearInterval(this._fetchDataTimeout);
|
||||
this._fetchDataTimeout = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._addon) {
|
||||
return html`<hass-loading-screen
|
||||
.message=${this._loadingMessage}
|
||||
></hass-loading-screen>`;
|
||||
return html` <hass-loading-screen></hass-loading-screen> `;
|
||||
}
|
||||
|
||||
const iframe = html`<iframe
|
||||
title=${this._addon.name}
|
||||
src=${this._addon.ingress_url!}
|
||||
@load=${this._checkLoaded}
|
||||
>
|
||||
</iframe>`;
|
||||
|
||||
@@ -139,20 +125,19 @@ class HassioIngressView extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues) {
|
||||
super.willUpdate(changedProps);
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (!changedProps.has("route")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const addon = this.route.path.substring(1);
|
||||
const addon = this.route.path.substr(1);
|
||||
|
||||
const oldRoute = changedProps.get("route") as this["route"] | undefined;
|
||||
const oldAddon = oldRoute ? oldRoute.path.substring(1) : undefined;
|
||||
const oldAddon = oldRoute ? oldRoute.path.substr(1) : undefined;
|
||||
|
||||
if (addon && addon !== oldAddon) {
|
||||
this._loadingMessage = undefined;
|
||||
this._fetchData(addon);
|
||||
}
|
||||
}
|
||||
@@ -160,42 +145,23 @@ class HassioIngressView extends LitElement {
|
||||
private async _fetchData(addonSlug: string) {
|
||||
const createSessionPromise = createHassioSession(this.hass);
|
||||
|
||||
let addon: HassioAddonDetails;
|
||||
let addon;
|
||||
|
||||
try {
|
||||
addon = await fetchHassioAddonInfo(this.hass, addonSlug);
|
||||
} catch (err: any) {
|
||||
await this.updateComplete;
|
||||
await showAlertDialog(this, {
|
||||
text:
|
||||
this.supervisor.localize("ingress.error_addon_info") ||
|
||||
"Unable to fetch add-on info to start Ingress",
|
||||
text: "Unable to fetch add-on info to start Ingress",
|
||||
title: "Supervisor",
|
||||
});
|
||||
await nextRender();
|
||||
navigate("/hassio/store", { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!addon.version) {
|
||||
await this.updateComplete;
|
||||
await showAlertDialog(this, {
|
||||
text:
|
||||
this.supervisor.localize("ingress.error_addon_not_installed") ||
|
||||
"The add-on is not installed. Please install it first",
|
||||
title: addon.name,
|
||||
});
|
||||
await nextRender();
|
||||
navigate(`/hassio/addon/${addon.slug}/info`, { replace: true });
|
||||
history.back();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!addon.ingress_url) {
|
||||
await this.updateComplete;
|
||||
await showAlertDialog(this, {
|
||||
text:
|
||||
this.supervisor.localize("ingress.error_addon_not_supported") ||
|
||||
"This add-on does not support Ingress",
|
||||
text: "Add-on does not support Ingress",
|
||||
title: addon.name,
|
||||
});
|
||||
await nextRender();
|
||||
@@ -203,81 +169,23 @@ class HassioIngressView extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!addon.state || !["startup", "started"].includes(addon.state)) {
|
||||
await this.updateComplete;
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
text:
|
||||
this.supervisor.localize("ingress.error_addon_not_running") ||
|
||||
"The add-on is not running. Do you want to start it now?",
|
||||
title: addon.name,
|
||||
confirmText:
|
||||
this.supervisor.localize("ingress.start_addon") || "Start add-on",
|
||||
dismissText: this.supervisor.localize("common.no") || "No",
|
||||
});
|
||||
if (confirm) {
|
||||
try {
|
||||
this._loadingMessage =
|
||||
this.supervisor.localize("ingress.addon_starting") ||
|
||||
"The add-on is starting, this can take some time...";
|
||||
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") ||
|
||||
"Error starting the add-on",
|
||||
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._loadingMessage =
|
||||
this.supervisor.localize("ingress.addon_starting") ||
|
||||
"The add-on is starting, this can take some time...";
|
||||
|
||||
this._fetchDataTimeout = window.setTimeout(() => {
|
||||
this._fetchData(addonSlug);
|
||||
}, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
if (addon.state !== "started") {
|
||||
await showAlertDialog(this, {
|
||||
text: "Add-on is not running. Please start it first",
|
||||
title: addon.name,
|
||||
});
|
||||
await nextRender();
|
||||
navigate(`/hassio/addon/${addon.slug}/info`, { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
this._loadingMessage = undefined;
|
||||
|
||||
if (this._fetchDataTimeout) {
|
||||
clearInterval(this._fetchDataTimeout);
|
||||
this._fetchDataTimeout = undefined;
|
||||
}
|
||||
|
||||
let session: string;
|
||||
let session;
|
||||
|
||||
try {
|
||||
session = await createSessionPromise;
|
||||
} catch (err: any) {
|
||||
if (this._sessionKeepAlive) {
|
||||
clearInterval(this._sessionKeepAlive);
|
||||
}
|
||||
await showAlertDialog(this, {
|
||||
text:
|
||||
this.supervisor.localize("ingress.error_creating_session") ||
|
||||
"Unable to create an Ingress session",
|
||||
text: "Unable to create an Ingress session",
|
||||
title: addon.name,
|
||||
});
|
||||
await nextRender();
|
||||
@@ -299,34 +207,6 @@ class HassioIngressView extends LitElement {
|
||||
this._addon = addon;
|
||||
}
|
||||
|
||||
private async _checkLoaded(ev): Promise<void> {
|
||||
if (!this._addon) {
|
||||
return;
|
||||
}
|
||||
if (ev.target.contentDocument.body.textContent === "502: Bad Gateway") {
|
||||
await this.updateComplete;
|
||||
showConfirmationDialog(this, {
|
||||
text:
|
||||
this.supervisor.localize("ingress.error_addon_not_ready") ||
|
||||
"The add-on seems to not be ready, it might still be starting. Do you want to try again?",
|
||||
title: this._addon.name,
|
||||
confirmText: this.supervisor.localize("ingress.retry") || "Retry",
|
||||
dismissText: this.supervisor.localize("common.no") || "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 {
|
||||
fireEvent(this, "hass-toggle-menu");
|
||||
}
|
||||
|
@@ -23,7 +23,6 @@ import {
|
||||
SupervisorObject,
|
||||
supervisorCollection,
|
||||
SupervisorKeys,
|
||||
cleanupSupervisorCollection,
|
||||
} from "../../src/data/supervisor/supervisor";
|
||||
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
|
||||
import { urlSyncMixin } from "../../src/state/url-sync-mixin";
|
||||
@@ -68,10 +67,6 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
||||
this._unsubs[unsub]();
|
||||
delete this._unsubs[unsub];
|
||||
});
|
||||
Object.keys(this._collections).forEach((collection) => {
|
||||
cleanupSupervisorCollection(this.hass.connection, collection);
|
||||
});
|
||||
this._collections = {};
|
||||
this.removeEventListener(
|
||||
"supervisor-collection-refresh",
|
||||
this._handleSupervisorStoreRefreshEvent
|
||||
@@ -119,9 +114,7 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
||||
private async _handleSupervisorStoreRefreshEvent(ev) {
|
||||
const collection = ev.detail.collection;
|
||||
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
|
||||
if (collection in this._collections) {
|
||||
this._collections[collection].refresh();
|
||||
}
|
||||
this._collections[collection].refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -136,17 +129,11 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
||||
if (this._unsubs[collection]) {
|
||||
this._unsubs[collection]();
|
||||
}
|
||||
try {
|
||||
this._unsubs[collection] = this._collections[collection].subscribe(
|
||||
(data) =>
|
||||
this._updateSupervisor({
|
||||
[collection]: data,
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
}
|
||||
this._unsubs[collection] = this._collections[collection].subscribe((data) =>
|
||||
this._updateSupervisor({
|
||||
[collection]: data,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private async _initSupervisor(): Promise<void> {
|
||||
|
44
package.json
44
package.json
@@ -27,13 +27,13 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.22.5",
|
||||
"@braintree/sanitize-url": "6.0.2",
|
||||
"@codemirror/autocomplete": "6.8.1",
|
||||
"@codemirror/autocomplete": "6.8.0",
|
||||
"@codemirror/commands": "6.2.4",
|
||||
"@codemirror/language": "6.8.0",
|
||||
"@codemirror/legacy-modes": "6.3.2",
|
||||
"@codemirror/search": "6.5.0",
|
||||
"@codemirror/state": "6.2.1",
|
||||
"@codemirror/view": "6.14.0",
|
||||
"@codemirror/view": "6.13.2",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "6.10.0",
|
||||
"@formatjs/intl-displaynames": "6.5.0",
|
||||
@@ -47,13 +47,12 @@
|
||||
"@fullcalendar/daygrid": "6.1.8",
|
||||
"@fullcalendar/interaction": "6.1.8",
|
||||
"@fullcalendar/list": "6.1.8",
|
||||
"@fullcalendar/luxon3": "6.1.8",
|
||||
"@fullcalendar/timegrid": "6.1.8",
|
||||
"@lezer/highlight": "1.1.6",
|
||||
"@lit-labs/context": "0.3.3",
|
||||
"@lit-labs/motion": "1.0.3",
|
||||
"@lit-labs/virtualizer": "2.0.3",
|
||||
"@lrnwebcomponents/simple-tooltip": "7.0.11",
|
||||
"@lrnwebcomponents/simple-tooltip": "7.0.2",
|
||||
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/mwc-button": "0.27.0",
|
||||
@@ -79,7 +78,7 @@
|
||||
"@material/mwc-top-app-bar": "0.27.0",
|
||||
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/web": "=1.0.0-pre.12",
|
||||
"@material/web": "=1.0.0-pre.10",
|
||||
"@mdi/js": "7.2.96",
|
||||
"@mdi/svg": "7.2.96",
|
||||
"@polymer/app-layout": "3.1.0",
|
||||
@@ -94,8 +93,8 @@
|
||||
"@polymer/paper-toast": "3.0.1",
|
||||
"@polymer/polymer": "3.5.1",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@vaadin/combo-box": "24.1.2",
|
||||
"@vaadin/vaadin-themable-mixin": "24.1.2",
|
||||
"@vaadin/combo-box": "24.1.1",
|
||||
"@vaadin/vaadin-themable-mixin": "24.1.1",
|
||||
"@vibrant/color": "3.2.1-alpha.1",
|
||||
"@vibrant/core": "3.2.1-alpha.1",
|
||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||
@@ -113,15 +112,14 @@
|
||||
"deep-freeze": "0.0.1",
|
||||
"fuse.js": "6.6.2",
|
||||
"google-timezones-json": "1.1.0",
|
||||
"hls.js": "1.4.7",
|
||||
"home-assistant-js-websocket": "8.1.0",
|
||||
"hls.js": "1.4.6",
|
||||
"home-assistant-js-websocket": "8.0.1",
|
||||
"idb-keyval": "6.2.1",
|
||||
"intl-messageformat": "10.5.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-draw": "1.0.4",
|
||||
"lit": "2.7.5",
|
||||
"luxon": "3.3.0",
|
||||
"marked": "4.3.0",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
@@ -163,7 +161,7 @@
|
||||
"@octokit/rest": "19.0.13",
|
||||
"@open-wc/dev-server-hmr": "0.1.4",
|
||||
"@rollup/plugin-babel": "6.0.3",
|
||||
"@rollup/plugin-commonjs": "25.0.2",
|
||||
"@rollup/plugin-commonjs": "25.0.1",
|
||||
"@rollup/plugin-json": "6.0.0",
|
||||
"@rollup/plugin-node-resolve": "15.1.0",
|
||||
"@rollup/plugin-replace": "5.0.2",
|
||||
@@ -176,23 +174,22 @@
|
||||
"@types/js-yaml": "4.0.5",
|
||||
"@types/leaflet": "1.9.3",
|
||||
"@types/leaflet-draw": "1.0.7",
|
||||
"@types/luxon": "3.3.0",
|
||||
"@types/marked": "4.3.1",
|
||||
"@types/mocha": "10.0.1",
|
||||
"@types/qrcode": "1.5.1",
|
||||
"@types/qrcode": "1.5.0",
|
||||
"@types/serve-handler": "6.1.1",
|
||||
"@types/sortablejs": "1.15.1",
|
||||
"@types/tar": "6.1.5",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "5.60.1",
|
||||
"@typescript-eslint/parser": "5.60.1",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.11",
|
||||
"@typescript-eslint/parser": "5.59.11",
|
||||
"@web/dev-server": "0.1.38",
|
||||
"@web/dev-server-rollup": "0.4.1",
|
||||
"babel-loader": "9.1.2",
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"chai": "4.3.7",
|
||||
"del": "7.0.0",
|
||||
"eslint": "8.44.0",
|
||||
"eslint": "8.43.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-airbnb-typescript": "17.0.0",
|
||||
"eslint-config-prettier": "8.8.0",
|
||||
@@ -206,7 +203,7 @@
|
||||
"esprima": "4.0.1",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.1.1",
|
||||
"glob": "10.3.1",
|
||||
"glob": "10.2.7",
|
||||
"gulp": "4.0.2",
|
||||
"gulp-flatmap": "1.0.2",
|
||||
"gulp-json-transform": "0.4.8",
|
||||
@@ -217,7 +214,7 @@
|
||||
"husky": "8.0.3",
|
||||
"instant-mocha": "1.5.1",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "13.2.3",
|
||||
"lint-staged": "13.2.2",
|
||||
"lit-analyzer": "2.0.0-pre.3",
|
||||
"lodash.template": "4.5.0",
|
||||
"magic-string": "0.30.0",
|
||||
@@ -233,16 +230,16 @@
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"rollup-plugin-visualizer": "5.9.2",
|
||||
"serve-handler": "6.1.5",
|
||||
"sinon": "15.2.0",
|
||||
"sinon": "15.1.2",
|
||||
"source-map-url": "0.4.1",
|
||||
"systemjs": "6.14.1",
|
||||
"tar": "6.1.15",
|
||||
"terser-webpack-plugin": "5.3.9",
|
||||
"ts-lit-plugin": "2.0.0-pre.1",
|
||||
"typescript": "5.1.6",
|
||||
"typescript": "5.1.3",
|
||||
"vinyl-buffer": "1.0.1",
|
||||
"vinyl-source-stream": "2.0.0",
|
||||
"webpack": "5.88.1",
|
||||
"webpack": "5.87.0",
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-dev-server": "4.15.1",
|
||||
"webpack-manifest-plugin": "5.0.0",
|
||||
@@ -253,12 +250,11 @@
|
||||
"resolutions": {
|
||||
"@polymer/polymer": "patch:@polymer/polymer@3.5.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
|
||||
"@material/mwc-button@^0.25.3": "^0.27.0",
|
||||
"sortablejs@1.15.0": "patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch",
|
||||
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||
"sortablejs@1.15.0": "patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch"
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "always"
|
||||
},
|
||||
"packageManager": "yarn@3.6.1"
|
||||
"packageManager": "yarn@3.6.0"
|
||||
}
|
||||
|
@@ -1,10 +1,10 @@
|
||||
[build-system]
|
||||
requires = ["setuptools~=68.0", "wheel~=0.40.0"]
|
||||
requires = ["setuptools~=62.3", "wheel~=0.37.1"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20230705.1"
|
||||
version = "20230608.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
2
setup.cfg
Normal file
2
setup.cfg
Normal file
@@ -0,0 +1,2 @@
|
||||
# Setuptools v62.3 doesn't support editable installs with just 'pyproject.toml' (PEP 660).
|
||||
# Keep this file until it does!
|
@@ -182,7 +182,6 @@ export const DOMAINS_WITH_CARD = [
|
||||
"input_select",
|
||||
"input_number",
|
||||
"input_text",
|
||||
"humidifier",
|
||||
"lock",
|
||||
"media_player",
|
||||
"number",
|
||||
|
@@ -50,7 +50,7 @@ export const computeStateDisplay = (
|
||||
entities: HomeAssistant["entities"],
|
||||
state?: string
|
||||
): string => {
|
||||
const entity = entities?.[stateObj.entity_id] as
|
||||
const entity = entities[stateObj.entity_id] as
|
||||
| EntityRegistryDisplayEntry
|
||||
| undefined;
|
||||
|
||||
@@ -169,6 +169,12 @@ 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`
|
||||
if (
|
||||
domain === "counter" ||
|
||||
|
@@ -15,7 +15,6 @@ import {
|
||||
mdiCheckCircleOutline,
|
||||
mdiClock,
|
||||
mdiCloseCircleOutline,
|
||||
mdiCrosshairsQuestion,
|
||||
mdiFan,
|
||||
mdiFanOff,
|
||||
mdiGestureTapButton,
|
||||
@@ -32,7 +31,6 @@ import {
|
||||
mdiPowerPlugOff,
|
||||
mdiRestart,
|
||||
mdiRobot,
|
||||
mdiRobotConfused,
|
||||
mdiRobotOff,
|
||||
mdiSpeaker,
|
||||
mdiSpeakerOff,
|
||||
@@ -93,19 +91,13 @@ export const domainIconWithoutDefault = (
|
||||
return alarmPanelIcon(compareState);
|
||||
|
||||
case "automation":
|
||||
return compareState === "unavailable"
|
||||
? mdiRobotConfused
|
||||
: compareState === "off"
|
||||
? mdiRobotOff
|
||||
: mdiRobot;
|
||||
return compareState === "off" ? mdiRobotOff : mdiRobot;
|
||||
|
||||
case "binary_sensor":
|
||||
return binarySensorIcon(compareState, stateObj);
|
||||
|
||||
case "button":
|
||||
switch (stateObj?.attributes.device_class) {
|
||||
case "identify":
|
||||
return mdiCrosshairsQuestion;
|
||||
case "restart":
|
||||
return mdiRestart;
|
||||
case "update":
|
||||
|
@@ -30,7 +30,6 @@ export const FIXED_DOMAIN_STATES = {
|
||||
lock: ["jammed", "locked", "locking", "unlocked", "unlocking"],
|
||||
media_player: ["idle", "off", "paused", "playing", "standby"],
|
||||
person: ["home", "not_home"],
|
||||
plant: ["ok", "problem"],
|
||||
remote: ["on", "off"],
|
||||
scene: [],
|
||||
schedule: ["on", "off"],
|
||||
@@ -135,7 +134,6 @@ const FIXED_DOMAIN_ATTRIBUTE_STATES = {
|
||||
},
|
||||
humidifier: {
|
||||
device_class: ["humidifier", "dehumidifier"],
|
||||
action: ["off", "idle", "humidifying", "drying"],
|
||||
},
|
||||
media_player: {
|
||||
device_class: ["tv", "speaker", "receiver"],
|
||||
|
@@ -110,15 +110,3 @@ export const stateColorProperties = (
|
||||
|
||||
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,13 +17,12 @@ export const stripPrefixFromEntityName = (
|
||||
|
||||
if (lowerCasedEntityName.startsWith(lowerCasedPrefixWithSuffix)) {
|
||||
const newName = entityName.substring(lowerCasedPrefixWithSuffix.length);
|
||||
if (newName.length) {
|
||||
// If first word already has an upper case letter (e.g. from brand name)
|
||||
// leave as-is, otherwise capitalize the first word.
|
||||
return hasUpperCase(newName.substr(0, newName.indexOf(" ")))
|
||||
? newName
|
||||
: newName[0].toUpperCase() + newName.slice(1);
|
||||
}
|
||||
|
||||
// If first word already has an upper case letter (e.g. from brand name)
|
||||
// leave as-is, otherwise capitalize the first word.
|
||||
return hasUpperCase(newName.substr(0, newName.indexOf(" ")))
|
||||
? newName
|
||||
: newName[0].toUpperCase() + newName.slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,27 +0,0 @@
|
||||
import memoizeOne from "memoize-one";
|
||||
import "../../resources/intl-polyfill";
|
||||
import { FrontendLocaleData } from "../../data/translation";
|
||||
|
||||
export const formatListWithAnds = (
|
||||
locale: FrontendLocaleData,
|
||||
list: string[]
|
||||
) => formatConjunctionList(locale).format(list);
|
||||
|
||||
export const formatListWithOrs = (locale: FrontendLocaleData, list: string[]) =>
|
||||
formatDisjunctionList(locale).format(list);
|
||||
|
||||
const formatConjunctionList = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.ListFormat(locale.language, {
|
||||
style: "long",
|
||||
type: "conjunction",
|
||||
})
|
||||
);
|
||||
|
||||
const formatDisjunctionList = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.ListFormat(locale.language, {
|
||||
style: "long",
|
||||
type: "disjunction",
|
||||
})
|
||||
);
|
@@ -1,19 +0,0 @@
|
||||
// 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: {
|
||||
beginAtZero: this.chartType === "bar",
|
||||
beginAtZero: false,
|
||||
ticks: {
|
||||
maxTicksLimit: 7,
|
||||
},
|
||||
|
@@ -349,7 +349,6 @@ export class HaDataTable extends LitElement {
|
||||
class="mdc-data-table__content scroller ha-scrollbar"
|
||||
@scroll=${this._saveScrollPos}
|
||||
.items=${this._items}
|
||||
.keyFunction=${this._keyFunction}
|
||||
.renderItem=${this._renderRow}
|
||||
></lit-virtualizer>
|
||||
`}
|
||||
@@ -358,8 +357,6 @@ export class HaDataTable extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _keyFunction = (row: DataTableRowData) => row[this.id] || row;
|
||||
|
||||
private _renderRow = (row: DataTableRowData, index: number) => {
|
||||
// not sure how this happens...
|
||||
if (!row) {
|
||||
|
@@ -17,16 +17,6 @@ import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-svg-icon";
|
||||
import "./state-badge";
|
||||
import {
|
||||
fuzzyFilterSort,
|
||||
ScorableTextItem,
|
||||
} from "../../common/string/filter/sequence-matching";
|
||||
|
||||
interface StatisticItem extends ScorableTextItem {
|
||||
id: string;
|
||||
name: string;
|
||||
state?: HassEntity;
|
||||
}
|
||||
|
||||
@customElement("ha-statistic-picker")
|
||||
export class HaStatisticPicker extends LitElement {
|
||||
@@ -85,11 +75,11 @@ export class HaStatisticPicker extends LitElement {
|
||||
|
||||
private _init = false;
|
||||
|
||||
private _statistics: StatisticItem[] = [];
|
||||
|
||||
private _rowRenderer: ComboBoxLitRenderer<StatisticItem> = (
|
||||
item
|
||||
) => html`<mwc-list-item graphic="avatar" twoline>
|
||||
private _rowRenderer: ComboBoxLitRenderer<{
|
||||
id: string;
|
||||
name: string;
|
||||
state?: HassEntity;
|
||||
}> = (item) => html`<mwc-list-item graphic="avatar" twoline>
|
||||
${item.state
|
||||
? html`<state-badge slot="graphic" .stateObj=${item.state}></state-badge>`
|
||||
: ""}
|
||||
@@ -115,7 +105,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
includeUnitClass?: string | string[],
|
||||
includeDeviceClass?: string | string[],
|
||||
entitiesOnly?: boolean
|
||||
): StatisticItem[] => {
|
||||
): Array<{ id: string; name: string; state?: HassEntity }> => {
|
||||
if (!statisticIds.length) {
|
||||
return [
|
||||
{
|
||||
@@ -123,7 +113,6 @@ export class HaStatisticPicker extends LitElement {
|
||||
name: this.hass.localize(
|
||||
"ui.components.statistic-picker.no_statistics"
|
||||
),
|
||||
strings: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -157,28 +146,26 @@ export class HaStatisticPicker extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
const output: StatisticItem[] = [];
|
||||
const output: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
state?: HassEntity;
|
||||
}> = [];
|
||||
statisticIds.forEach((meta) => {
|
||||
const entityState = this.hass.states[meta.statistic_id];
|
||||
if (!entityState) {
|
||||
if (!entitiesOnly) {
|
||||
const id = meta.statistic_id;
|
||||
const name = getStatisticLabel(this.hass, meta.statistic_id, meta);
|
||||
output.push({
|
||||
id,
|
||||
name,
|
||||
strings: [id, name],
|
||||
id: meta.statistic_id,
|
||||
name: getStatisticLabel(this.hass, meta.statistic_id, meta),
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
const id = meta.statistic_id;
|
||||
const name = getStatisticLabel(this.hass, meta.statistic_id, meta);
|
||||
output.push({
|
||||
id,
|
||||
name,
|
||||
id: meta.statistic_id,
|
||||
name: getStatisticLabel(this.hass, meta.statistic_id, meta),
|
||||
state: entityState,
|
||||
strings: [id, name],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -187,7 +174,6 @@ export class HaStatisticPicker extends LitElement {
|
||||
{
|
||||
id: "",
|
||||
name: this.hass.localize("ui.components.statistic-picker.no_match"),
|
||||
strings: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -203,7 +189,6 @@ export class HaStatisticPicker extends LitElement {
|
||||
name: this.hass.localize(
|
||||
"ui.components.statistic-picker.missing_entity"
|
||||
),
|
||||
strings: [],
|
||||
});
|
||||
|
||||
return output;
|
||||
@@ -231,7 +216,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
) {
|
||||
this._init = true;
|
||||
if (this.hasUpdated) {
|
||||
this._statistics = this._getStatistics(
|
||||
(this.comboBox as any).items = this._getStatistics(
|
||||
this.statisticIds!,
|
||||
this.includeStatisticsUnitOfMeasurement,
|
||||
this.includeUnitClass,
|
||||
@@ -240,7 +225,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
);
|
||||
} else {
|
||||
this.updateComplete.then(() => {
|
||||
this._statistics = this._getStatistics(
|
||||
(this.comboBox as any).items = this._getStatistics(
|
||||
this.statisticIds!,
|
||||
this.includeStatisticsUnitOfMeasurement,
|
||||
this.includeUnitClass,
|
||||
@@ -263,13 +248,11 @@ export class HaStatisticPicker extends LitElement {
|
||||
.renderer=${this._rowRenderer}
|
||||
.disabled=${this.disabled}
|
||||
.allowCustomValue=${this.allowCustomEntity}
|
||||
.filteredItems=${this._statistics}
|
||||
item-value-path="id"
|
||||
item-id-path="id"
|
||||
item-label-path="name"
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._statisticChanged}
|
||||
@filter-changed=${this._filterChanged}
|
||||
></ha-combo-box>
|
||||
`;
|
||||
}
|
||||
@@ -298,14 +281,6 @@ export class HaStatisticPicker extends LitElement {
|
||||
this._opened = ev.detail.value;
|
||||
}
|
||||
|
||||
private _filterChanged(ev: CustomEvent): void {
|
||||
const target = ev.target as HaComboBox;
|
||||
const filterString = ev.detail.value.toLowerCase();
|
||||
target.filteredItems = filterString.length
|
||||
? fuzzyFilterSort<StatisticItem>(filterString, this._statistics)
|
||||
: this._statistics;
|
||||
}
|
||||
|
||||
private _setValue(value: string) {
|
||||
this.value = value;
|
||||
setTimeout(() => {
|
||||
|
@@ -13,10 +13,7 @@ import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import {
|
||||
stateColorCss,
|
||||
stateColorBrightness,
|
||||
} from "../../common/entity/state_color";
|
||||
import { stateColorCss } from "../../common/entity/state_color";
|
||||
import { iconColorCSS } from "../../common/style/icon_color_css";
|
||||
import { cameraUrlWithWidthHeight } from "../../data/camera";
|
||||
import { HVAC_ACTION_TO_MODE } from "../../data/climate";
|
||||
@@ -156,7 +153,8 @@ export class StateBadge extends LitElement {
|
||||
// eslint-disable-next-line
|
||||
console.warn(errorMessage);
|
||||
}
|
||||
iconStyle.filter = stateColorBrightness(stateObj);
|
||||
// lowest brightness will be around 50% (that's pretty dark)
|
||||
iconStyle.filter = `brightness(${(brightness + 245) / 5}%)`;
|
||||
}
|
||||
if (stateObj.attributes.hvac_action) {
|
||||
const hvacAction = stateObj.attributes.hvac_action;
|
||||
|
@@ -68,9 +68,6 @@ export class HaControlCircularSlider extends LitElement {
|
||||
@property({ type: Boolean })
|
||||
public dual?: boolean;
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public inverted?: boolean;
|
||||
|
||||
@property({ type: String })
|
||||
public label?: string;
|
||||
|
||||
@@ -83,15 +80,15 @@ export class HaControlCircularSlider extends LitElement {
|
||||
@property({ type: Number })
|
||||
public value?: number;
|
||||
|
||||
@property({ type: Number })
|
||||
public current?: number;
|
||||
|
||||
@property({ type: Number })
|
||||
public low?: number;
|
||||
|
||||
@property({ type: Number })
|
||||
public high?: number;
|
||||
|
||||
@property({ type: Number })
|
||||
public current?: number;
|
||||
|
||||
@property({ type: Number })
|
||||
public step = 1;
|
||||
|
||||
@@ -101,15 +98,6 @@ export class HaControlCircularSlider extends LitElement {
|
||||
@property({ type: Number })
|
||||
public max = 100;
|
||||
|
||||
@state()
|
||||
public _localValue?: number = this.value;
|
||||
|
||||
@state()
|
||||
public _localLow?: number = this.low;
|
||||
|
||||
@state()
|
||||
public _localHigh?: number = this.high;
|
||||
|
||||
@state()
|
||||
public _activeSlider?: ActiveSlider;
|
||||
|
||||
@@ -132,36 +120,17 @@ export class HaControlCircularSlider extends LitElement {
|
||||
|
||||
private _boundedValue(value: number) {
|
||||
const min =
|
||||
this._activeSlider === "high"
|
||||
? Math.min(this._localLow ?? this.max)
|
||||
: this.min;
|
||||
this._activeSlider === "high" ? Math.min(this.low ?? this.max) : this.min;
|
||||
const max =
|
||||
this._activeSlider === "low"
|
||||
? Math.max(this._localHigh ?? this.min)
|
||||
: this.max;
|
||||
this._activeSlider === "low" ? Math.max(this.high ?? this.min) : this.max;
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
super.firstUpdated(changedProps);
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
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 {
|
||||
super.connectedCallback();
|
||||
this._setupListeners();
|
||||
@@ -195,8 +164,8 @@ export class HaControlCircularSlider extends LitElement {
|
||||
|
||||
private _findActiveSlider(value: number): ActiveSlider {
|
||||
if (!this.dual) return "value";
|
||||
const low = Math.max(this._localLow ?? this.min, this.min);
|
||||
const high = Math.min(this._localHigh ?? this.max, this.max);
|
||||
const low = Math.max(this.low ?? this.min, this.min);
|
||||
const high = Math.min(this.high ?? this.max, this.max);
|
||||
if (low >= value) {
|
||||
return "low";
|
||||
}
|
||||
@@ -209,29 +178,13 @@ export class HaControlCircularSlider extends LitElement {
|
||||
}
|
||||
|
||||
private _setActiveValue(value: number) {
|
||||
switch (this._activeSlider) {
|
||||
case "high":
|
||||
this._localHigh = value;
|
||||
break;
|
||||
case "low":
|
||||
this._localLow = value;
|
||||
break;
|
||||
case "value":
|
||||
this._localValue = value;
|
||||
break;
|
||||
}
|
||||
if (!this._activeSlider) return;
|
||||
this[this._activeSlider] = value;
|
||||
}
|
||||
|
||||
private _getActiveValue(): number | undefined {
|
||||
switch (this._activeSlider) {
|
||||
case "high":
|
||||
return this._localHigh;
|
||||
case "low":
|
||||
return this._localLow;
|
||||
case "value":
|
||||
return this._localValue;
|
||||
}
|
||||
return undefined;
|
||||
if (!this._activeSlider) return undefined;
|
||||
return this[this._activeSlider];
|
||||
}
|
||||
|
||||
_setupListeners() {
|
||||
@@ -282,7 +235,6 @@ export class HaControlCircularSlider extends LitElement {
|
||||
const raw = this._percentageToValue(percentage);
|
||||
const bounded = this._boundedValue(raw);
|
||||
const stepped = this._steppedValue(bounded);
|
||||
this._setActiveValue(stepped);
|
||||
if (this._activeSlider) {
|
||||
fireEvent(this, `${this._activeSlider}-changing`, {
|
||||
value: undefined,
|
||||
@@ -388,41 +340,23 @@ 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 {
|
||||
const trackPath = arc({ x: 0, y: 0, start: 0, end: MAX_ANGLE, r: RADIUS });
|
||||
|
||||
const lowValue = this.dual ? this._localLow : this._localValue;
|
||||
const highValue = this._localHigh;
|
||||
const maxRatio = MAX_ANGLE / 360;
|
||||
|
||||
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 highPercentage = this._valueToPercentage(highValue ?? this.max);
|
||||
|
||||
const [lowStrokeDasharray, lowStrokeDashOffset] = this._strokeDashArc(
|
||||
lowPercentage,
|
||||
this.inverted
|
||||
);
|
||||
const lowArcLength = lowPercentage * f * maxRatio;
|
||||
const lowStrokeDasharray = `${lowArcLength} ${f - lowArcLength}`;
|
||||
|
||||
const [highStrokeDasharray, highStrokeDashOffset] = this._strokeDashArc(
|
||||
highPercentage,
|
||||
true
|
||||
);
|
||||
const highArcLength = (1 - highPercentage) * f * maxRatio;
|
||||
const highStrokeDasharray = `${highArcLength} ${f - highArcLength}`;
|
||||
const highStrokeDashOffset = `${highArcLength + f * (1 - maxRatio)}`;
|
||||
|
||||
const currentPercentage = this._valueToPercentage(this.current ?? 0);
|
||||
const currentAngle = currentPercentage * MAX_ANGLE;
|
||||
@@ -447,31 +381,27 @@ export class HaControlCircularSlider extends LitElement {
|
||||
</g>
|
||||
<g id="display">
|
||||
<path class="background" d=${trackPath} />
|
||||
${lowValue != null
|
||||
? svg`
|
||||
<circle
|
||||
.id=${this.dual ? "low" : "value"}
|
||||
class="track"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r=${RADIUS}
|
||||
stroke-dasharray=${lowStrokeDasharray}
|
||||
stroke-dashoffset=${lowStrokeDashOffset}
|
||||
role="slider"
|
||||
tabindex="0"
|
||||
aria-valuemin=${this.min}
|
||||
aria-valuemax=${this.max}
|
||||
aria-valuenow=${
|
||||
lowValue != null ? this._steppedValue(lowValue) : undefined
|
||||
}
|
||||
aria-disabled=${this.disabled}
|
||||
aria-label=${ifDefined(this.lowLabel ?? this.label)}
|
||||
@keydown=${this._handleKeyDown}
|
||||
@keyup=${this._handleKeyUp}
|
||||
/>
|
||||
`
|
||||
: nothing}
|
||||
${this.dual && highValue != null
|
||||
<circle
|
||||
.id=${this.dual ? "low" : "value"}
|
||||
class="track"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r=${RADIUS}
|
||||
stroke-dasharray=${lowStrokeDasharray}
|
||||
stroke-dashoffset="0"
|
||||
role="slider"
|
||||
tabindex="0"
|
||||
aria-valuemin=${this.min}
|
||||
aria-valuemax=${this.max}
|
||||
aria-valuenow=${lowValue != null
|
||||
? this._steppedValue(lowValue)
|
||||
: undefined}
|
||||
aria-disabled=${this.disabled}
|
||||
aria-label=${ifDefined(this.lowLabel ?? this.label)}
|
||||
@keydown=${this._handleKeyDown}
|
||||
@keyup=${this._handleKeyUp}
|
||||
/>
|
||||
${this.dual
|
||||
? svg`
|
||||
<circle
|
||||
id="high"
|
||||
@@ -566,7 +496,6 @@ export class HaControlCircularSlider extends LitElement {
|
||||
fill: none;
|
||||
stroke: var(--control-circular-slider-background);
|
||||
opacity: var(--control-circular-slider-background-opacity);
|
||||
transition: stroke 180ms ease-in-out, opacity 180ms ease-in-out;
|
||||
stroke-linecap: round;
|
||||
stroke-width: 24px;
|
||||
}
|
||||
@@ -578,8 +507,7 @@ export class HaControlCircularSlider extends LitElement {
|
||||
stroke-width: 24px;
|
||||
transition: stroke-width 300ms ease-in-out,
|
||||
stroke-dasharray 300ms ease-in-out,
|
||||
stroke-dashoffset 300ms ease-in-out, stroke 180ms ease-in-out,
|
||||
opacity 180ms ease-in-out;
|
||||
stroke-dashoffset 300ms ease-in-out;
|
||||
}
|
||||
|
||||
.track:focus-visible {
|
||||
|
@@ -34,8 +34,6 @@ const getValue = (obj, item) =>
|
||||
|
||||
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")
|
||||
export class HaForm extends LitElement implements HaFormElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -46,14 +44,10 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
|
||||
@property() public error?: Record<string, string>;
|
||||
|
||||
@property() public warning?: Record<string, string>;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property() public computeError?: (schema: any, error) => string;
|
||||
|
||||
@property() public computeWarning?: (schema: any, warning) => string;
|
||||
|
||||
@property() public computeLabel?: (
|
||||
schema: any,
|
||||
data: HaFormDataContainer
|
||||
@@ -104,7 +98,6 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
: ""}
|
||||
${this.schema.map((item) => {
|
||||
const error = getError(this.error, item);
|
||||
const warning = getWarning(this.warning, item);
|
||||
|
||||
return html`
|
||||
${error
|
||||
@@ -113,12 +106,6 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
${this._computeError(error, item)}
|
||||
</ha-alert>
|
||||
`
|
||||
: warning
|
||||
? html`
|
||||
<ha-alert own-margin alert-type="warning">
|
||||
${this._computeWarning(warning, item)}
|
||||
</ha-alert>
|
||||
`
|
||||
: ""}
|
||||
${"selector" in item
|
||||
? html`<ha-selector
|
||||
@@ -200,13 +187,6 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
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 {
|
||||
return css`
|
||||
.root > * {
|
||||
|
@@ -1,137 +0,0 @@
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { computeAttributeValueDisplay } from "../common/entity/compute_attribute_display";
|
||||
import { computeStateDisplay } from "../common/entity/compute_state_display";
|
||||
import { formatNumber } from "../common/number/format_number";
|
||||
import { blankBeforePercent } from "../common/translations/blank_before_percent";
|
||||
import { isUnavailableState, OFF } from "../data/entity";
|
||||
import { HumidifierEntity } from "../data/humidifier";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
@customElement("ha-humidifier-state")
|
||||
class HaHumidifierState extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj!: HumidifierEntity;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const currentStatus = this._computeCurrentStatus();
|
||||
|
||||
return html`<div class="target">
|
||||
${!isUnavailableState(this.stateObj.state)
|
||||
? html`<span class="state-label">
|
||||
${this._localizeState()}
|
||||
${this.stateObj.attributes.mode
|
||||
? html`-
|
||||
${computeAttributeValueDisplay(
|
||||
this.hass.localize,
|
||||
this.stateObj,
|
||||
this.hass.locale,
|
||||
this.hass.config,
|
||||
this.hass.entities,
|
||||
"mode"
|
||||
)}`
|
||||
: ""}
|
||||
</span>
|
||||
<div class="unit">${this._computeTarget()}</div>`
|
||||
: this._localizeState()}
|
||||
</div>
|
||||
|
||||
${currentStatus && !isUnavailableState(this.stateObj.state)
|
||||
? html`<div class="current">
|
||||
${this.hass.localize("ui.card.climate.currently")}:
|
||||
<div class="unit">${currentStatus}</div>
|
||||
</div>`
|
||||
: ""}`;
|
||||
}
|
||||
|
||||
private _computeCurrentStatus(): string | undefined {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (this.stateObj.attributes.current_humidity != null) {
|
||||
return `${formatNumber(
|
||||
this.stateObj.attributes.current_humidity,
|
||||
this.hass.locale
|
||||
)}${blankBeforePercent(this.hass.locale)}%`;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _computeTarget(): string {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (this.stateObj.attributes.humidity != null) {
|
||||
return `${formatNumber(
|
||||
this.stateObj.attributes.humidity,
|
||||
this.hass.locale
|
||||
)}${blankBeforePercent(this.hass.locale)}%`;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private _localizeState(): string {
|
||||
if (isUnavailableState(this.stateObj.state)) {
|
||||
return this.hass.localize(`state.default.${this.stateObj.state}`);
|
||||
}
|
||||
|
||||
const stateString = computeStateDisplay(
|
||||
this.hass.localize,
|
||||
this.stateObj,
|
||||
this.hass.locale,
|
||||
this.hass.config,
|
||||
this.hass.entities
|
||||
);
|
||||
|
||||
return this.stateObj.attributes.action && this.stateObj.state !== OFF
|
||||
? `${computeAttributeValueDisplay(
|
||||
this.hass.localize,
|
||||
this.stateObj,
|
||||
this.hass.locale,
|
||||
this.hass.config,
|
||||
this.hass.entities,
|
||||
"action"
|
||||
)} (${stateString})`
|
||||
: stateString;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.target {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.current {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.state-label {
|
||||
font-weight: bold;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.unit {
|
||||
display: inline-block;
|
||||
direction: ltr;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-humidifier-state": HaHumidifierState;
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
import { mdiMenu } from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { subscribeNotifications } from "../data/persistent_notification";
|
||||
@@ -17,8 +17,6 @@ class HaMenuButton extends LitElement {
|
||||
|
||||
@state() private _hasNotifications = false;
|
||||
|
||||
@state() private _show = false;
|
||||
|
||||
private _alwaysVisible = false;
|
||||
|
||||
private _attachNotifOnConnect = false;
|
||||
@@ -42,10 +40,7 @@ class HaMenuButton extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._show) {
|
||||
return nothing;
|
||||
}
|
||||
protected render(): TemplateResult {
|
||||
const hasNotifications =
|
||||
this._hasNotifications &&
|
||||
(this.narrow || this.hass.dockedSidebar === "always_hidden");
|
||||
@@ -71,32 +66,32 @@ class HaMenuButton extends LitElement {
|
||||
(Number((window.parent as any).frontendVersion) || 0) < 20190710;
|
||||
}
|
||||
|
||||
protected willUpdate(changedProps) {
|
||||
super.willUpdate(changedProps);
|
||||
protected updated(changedProps) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (!changedProps.has("narrow") && !changedProps.has("hass")) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
|
||||
const oldShowButton =
|
||||
oldNarrow || oldHass?.dockedSidebar === "always_hidden";
|
||||
const showButton =
|
||||
this.narrow || this.hass.dockedSidebar === "always_hidden";
|
||||
let oldNarrow: boolean | undefined;
|
||||
let newNarrow: boolean | undefined;
|
||||
if (changedProps.has("narrow")) {
|
||||
oldNarrow = changedProps.get("narrow");
|
||||
newNarrow = this.narrow;
|
||||
} else if (oldHass) {
|
||||
oldNarrow = oldHass.dockedSidebar === "always_hidden";
|
||||
newNarrow = this.hass.dockedSidebar === "always_hidden";
|
||||
}
|
||||
|
||||
if (this.hasUpdated && oldShowButton === showButton) {
|
||||
if (oldNarrow === newNarrow) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._show = showButton || this._alwaysVisible;
|
||||
this.style.display = newNarrow || this._alwaysVisible ? "initial" : "none";
|
||||
|
||||
if (!showButton) {
|
||||
if (!newNarrow) {
|
||||
if (this._unsubNotifications) {
|
||||
this._unsubNotifications();
|
||||
this._unsubNotifications = undefined;
|
||||
|
@@ -249,16 +249,12 @@ export class HaServiceControl extends LitElement {
|
||||
) {
|
||||
const targetSelector = target ? { target } : { target: {} };
|
||||
const targetEntities =
|
||||
ensureArray(
|
||||
value?.target?.entity_id || value?.data?.entity_id
|
||||
)?.slice() || [];
|
||||
ensureArray(value?.target?.entity_id || value?.data?.entity_id) || [];
|
||||
const targetDevices =
|
||||
ensureArray(
|
||||
value?.target?.device_id || value?.data?.device_id
|
||||
)?.slice() || [];
|
||||
ensureArray(value?.target?.device_id || value?.data?.device_id) || [];
|
||||
const targetAreas = ensureArray(
|
||||
value?.target?.area_id || value?.data?.area_id
|
||||
)?.slice();
|
||||
);
|
||||
if (targetAreas) {
|
||||
targetAreas.forEach((areaId) => {
|
||||
const expanded = expandAreaTarget(
|
||||
|
@@ -160,14 +160,6 @@ export class HaTextField extends TextFieldBase {
|
||||
.mdc-text-field__input[type="number"] {
|
||||
direction: var(--direction);
|
||||
}
|
||||
.mdc-text-field__affix--prefix {
|
||||
padding-right: var(--text-field-prefix-padding-right, 2px);
|
||||
}
|
||||
|
||||
.mdc-text-field:not(.mdc-text-field--disabled)
|
||||
.mdc-text-field__affix--prefix {
|
||||
color: var(--mdc-text-field-label-ink-color);
|
||||
}
|
||||
`,
|
||||
// safari workaround - must be explicit
|
||||
document.dir === "rtl"
|
||||
|
@@ -107,11 +107,6 @@ export interface NumericStateTrigger extends BaseTrigger {
|
||||
for?: string | number | ForDict;
|
||||
}
|
||||
|
||||
export interface ConversationTrigger extends BaseTrigger {
|
||||
platform: "conversation";
|
||||
command: string | string[];
|
||||
}
|
||||
|
||||
export interface SunTrigger extends BaseTrigger {
|
||||
platform: "sun";
|
||||
offset: number;
|
||||
@@ -183,7 +178,6 @@ export type Trigger =
|
||||
| HassTrigger
|
||||
| NumericStateTrigger
|
||||
| SunTrigger
|
||||
| ConversationTrigger
|
||||
| TimePatternTrigger
|
||||
| WebhookTrigger
|
||||
| PersistentNotificationTrigger
|
||||
@@ -393,7 +387,7 @@ export const testCondition = (
|
||||
variables,
|
||||
});
|
||||
|
||||
export type AutomationClipboard = {
|
||||
export type Clipboard = {
|
||||
trigger?: Trigger;
|
||||
condition?: Condition;
|
||||
action?: Action;
|
||||
|
@@ -12,7 +12,6 @@ import {
|
||||
} from "../common/entity/compute_attribute_display";
|
||||
import { computeStateDisplay } from "../common/entity/compute_state_display";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import "../resources/intl-polyfill";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { Condition, ForDict, Trigger } from "./automation";
|
||||
import {
|
||||
@@ -22,6 +21,7 @@ import {
|
||||
localizeDeviceAutomationTrigger,
|
||||
} from "./device_automation";
|
||||
import { EntityRegistryEntry } from "./entity_registry";
|
||||
import "../resources/intl-polyfill";
|
||||
import { FrontendLocaleData } from "./translation";
|
||||
|
||||
const triggerTranslationBaseKey =
|
||||
@@ -81,26 +81,6 @@ export const describeTrigger = (
|
||||
hass: HomeAssistant,
|
||||
entityRegistry: EntityRegistryEntry[],
|
||||
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) {
|
||||
return trigger.alias;
|
||||
@@ -397,10 +377,12 @@ const tryDescribeTrigger = (
|
||||
}
|
||||
|
||||
// Time Pattern Trigger
|
||||
if (trigger.platform === "time_pattern") {
|
||||
if (!trigger.seconds && !trigger.minutes && !trigger.hours) {
|
||||
return "When a time pattern matches";
|
||||
}
|
||||
if (
|
||||
trigger.platform === "time_pattern" &&
|
||||
(trigger.seconds !== undefined ||
|
||||
trigger.minutes !== undefined ||
|
||||
trigger.hours !== undefined)
|
||||
) {
|
||||
let result = "Trigger ";
|
||||
if (trigger.seconds !== undefined) {
|
||||
const seconds_all = trigger.seconds === "*";
|
||||
@@ -608,24 +590,6 @@ const tryDescribeTrigger = (
|
||||
);
|
||||
}
|
||||
|
||||
// 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
|
||||
if (trigger.platform === "persistent_notification") {
|
||||
return "When a persistent notification is updated";
|
||||
@@ -661,26 +625,6 @@ export const describeCondition = (
|
||||
hass: HomeAssistant,
|
||||
entityRegistry: EntityRegistryEntry[],
|
||||
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) {
|
||||
return condition.alias;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { IntegrationManifest, IntegrationType } from "./integration";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { IntegrationType } from "./integration";
|
||||
|
||||
export interface ConfigEntry {
|
||||
entry_id: string;
|
||||
@@ -143,23 +143,3 @@ export const enableConfigEntry = (hass: HomeAssistant, configEntryId: string) =>
|
||||
entry_id: configEntryId,
|
||||
disabled_by: null,
|
||||
});
|
||||
|
||||
export const sortConfigEntries = (
|
||||
configEntries: ConfigEntry[],
|
||||
manifestLookup: { [domain: string]: IntegrationManifest }
|
||||
): ConfigEntry[] => {
|
||||
const sortedConfigEntries = [...configEntries];
|
||||
|
||||
const getScore = (entry: ConfigEntry) => {
|
||||
const manifest = manifestLookup[entry.domain] as
|
||||
| IntegrationManifest
|
||||
| undefined;
|
||||
const isHelper = manifest?.integration_type === "helper";
|
||||
return isHelper ? -1 : 1;
|
||||
};
|
||||
|
||||
const configEntriesCompare = (a: ConfigEntry, b: ConfigEntry) =>
|
||||
getScore(b) - getScore(a);
|
||||
|
||||
return sortedConfigEntries.sort(configEntriesCompare);
|
||||
};
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { ensureArray } from "../common/array/ensure-array";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
interface IntentTarget {
|
||||
@@ -53,30 +52,16 @@ export interface ConversationResult {
|
||||
| IntentResultError;
|
||||
}
|
||||
|
||||
export interface AgentInfo {
|
||||
attribution?: { name: string; url: string };
|
||||
}
|
||||
|
||||
export interface Agent {
|
||||
id: string;
|
||||
name: 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 = (
|
||||
hass: HomeAssistant,
|
||||
text: string,
|
||||
@@ -102,6 +87,15 @@ export const listAgents = (
|
||||
country,
|
||||
});
|
||||
|
||||
export const getAgentInfo = (
|
||||
hass: HomeAssistant,
|
||||
agent_id?: string
|
||||
): Promise<AgentInfo> =>
|
||||
hass.callWS({
|
||||
type: "conversation/agent/info",
|
||||
agent_id,
|
||||
});
|
||||
|
||||
export const prepareConversation = (
|
||||
hass: HomeAssistant,
|
||||
language?: string
|
||||
@@ -110,16 +104,3 @@ export const prepareConversation = (
|
||||
type: "conversation/prepare",
|
||||
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,13 +23,7 @@ export type AddonStartup =
|
||||
| "services"
|
||||
| "application"
|
||||
| "once";
|
||||
export type AddonState =
|
||||
| "startup"
|
||||
| "started"
|
||||
| "stopped"
|
||||
| "unknown"
|
||||
| "error"
|
||||
| null;
|
||||
export type AddonState = "started" | "stopped" | null;
|
||||
export type AddonRepository = "core" | "local" | string;
|
||||
|
||||
interface AddonTranslations {
|
||||
|
@@ -2,19 +2,20 @@ import {
|
||||
HassEntityAttributeBase,
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { FIXED_DOMAIN_STATES } from "../common/entity/get_states";
|
||||
import { UNAVAILABLE_STATES } from "./entity";
|
||||
|
||||
export type HumidifierState = "on" | "off";
|
||||
|
||||
export type HumidifierAction = "off" | "idle" | "humidifying" | "drying";
|
||||
type HumidifierState =
|
||||
| (typeof FIXED_DOMAIN_STATES.humidifier)[number]
|
||||
| (typeof UNAVAILABLE_STATES)[number];
|
||||
|
||||
export type HumidifierEntity = HassEntityBase & {
|
||||
state: HumidifierState;
|
||||
attributes: HassEntityAttributeBase & {
|
||||
humidity?: number;
|
||||
current_humidity?: number;
|
||||
min_humidity?: number;
|
||||
max_humidity?: number;
|
||||
mode?: string;
|
||||
action?: HumidifierAction;
|
||||
available_modes?: string[];
|
||||
};
|
||||
};
|
||||
|
@@ -7,7 +7,6 @@ import {
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeStateDisplay } from "../common/entity/compute_state_display";
|
||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
import { autoCaseNoun } from "../common/translations/auto_case_noun";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import { HaEntityPickerEntityFilterFunc } from "../components/entity/ha-entity-picker";
|
||||
import { HomeAssistant } from "../types";
|
||||
@@ -360,21 +359,15 @@ export const localizeStateMessage = (
|
||||
case "vibration":
|
||||
if (isOn) {
|
||||
return localize(`${LOGBOOK_LOCALIZE_PATH}.detected_device_class`, {
|
||||
device_class: autoCaseNoun(
|
||||
localize(
|
||||
`component.binary_sensor.entity_component.${device_class}.name`
|
||||
),
|
||||
hass.language
|
||||
device_class: localize(
|
||||
`component.binary_sensor.device_class.${device_class}`
|
||||
),
|
||||
});
|
||||
}
|
||||
if (isOff) {
|
||||
return localize(`${LOGBOOK_LOCALIZE_PATH}.cleared_device_class`, {
|
||||
device_class: autoCaseNoun(
|
||||
localize(
|
||||
`component.binary_sensor.entity_component.${device_class}.name`
|
||||
),
|
||||
hass.language
|
||||
device_class: localize(
|
||||
`component.binary_sensor.device_class.${device_class}`
|
||||
),
|
||||
});
|
||||
}
|
||||
|
@@ -51,7 +51,6 @@ export const serviceActionStruct: Describe<ServiceAction> = assign(
|
||||
entity_id: optional(string()),
|
||||
target: optional(targetStruct),
|
||||
data: optional(object()),
|
||||
response_variable: optional(string()),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -117,7 +116,6 @@ export interface ServiceAction extends BaseAction {
|
||||
entity_id?: string;
|
||||
target?: HassServiceTarget;
|
||||
data?: Record<string, unknown>;
|
||||
response_variable?: string;
|
||||
}
|
||||
|
||||
export interface DeviceAction extends BaseAction {
|
||||
@@ -223,7 +221,6 @@ export interface VariablesAction extends BaseAction {
|
||||
|
||||
export interface StopAction extends BaseAction {
|
||||
stop: string;
|
||||
response_variable?: string;
|
||||
error?: boolean;
|
||||
}
|
||||
|
||||
|
@@ -31,10 +31,6 @@ import {
|
||||
VariablesAction,
|
||||
WaitForTriggerAction,
|
||||
} from "./script";
|
||||
import { formatListWithAnds } from "../common/string/format-list";
|
||||
|
||||
const actionTranslationBaseKey =
|
||||
"ui.panel.config.automation.editor.actions.type";
|
||||
|
||||
export const describeAction = <T extends ActionType>(
|
||||
hass: HomeAssistant,
|
||||
@@ -42,32 +38,6 @@ export const describeAction = <T extends ActionType>(
|
||||
action: ActionTypes[T],
|
||||
actionType?: T,
|
||||
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 => {
|
||||
if (action.alias && !ignoreAlias) {
|
||||
return action.alias;
|
||||
@@ -79,8 +49,25 @@ const tryDescribeAction = <T extends ActionType>(
|
||||
if (actionType === "service") {
|
||||
const config = action as ActionTypes["service"];
|
||||
|
||||
const targets: string[] = [];
|
||||
let base: string | undefined;
|
||||
|
||||
if (
|
||||
config.service_template ||
|
||||
(config.service && isTemplate(config.service))
|
||||
) {
|
||||
base = "Call a service based on a template";
|
||||
} else if (config.service) {
|
||||
const [domain, serviceName] = config.service.split(".", 2);
|
||||
const service = hass.services[domain][serviceName];
|
||||
base = service
|
||||
? `${domainToName(hass.localize, domain)}: ${service.name}`
|
||||
: `Call service: ${config.service}`;
|
||||
} else {
|
||||
return "Call a service";
|
||||
}
|
||||
if (config.target) {
|
||||
const targets: string[] = [];
|
||||
|
||||
for (const [key, label] of Object.entries({
|
||||
area_id: "areas",
|
||||
device_id: "devices",
|
||||
@@ -95,12 +82,7 @@ const tryDescribeAction = <T extends ActionType>(
|
||||
|
||||
for (const targetThing of keyConf) {
|
||||
if (isTemplate(targetThing)) {
|
||||
targets.push(
|
||||
hass.localize(
|
||||
`${actionTranslationBaseKey}.service.description.target_template`,
|
||||
{ name: label }
|
||||
)
|
||||
);
|
||||
targets.push(`templated ${label}`);
|
||||
break;
|
||||
} else if (key === "entity_id") {
|
||||
if (targetThing.includes(".")) {
|
||||
@@ -117,11 +99,7 @@ const tryDescribeAction = <T extends ActionType>(
|
||||
computeEntityRegistryName(hass, entityReg) || targetThing
|
||||
);
|
||||
} else {
|
||||
targets.push(
|
||||
hass.localize(
|
||||
`${actionTranslationBaseKey}.service.description.target_unknown_entity`
|
||||
)
|
||||
);
|
||||
targets.push("unknown entity");
|
||||
}
|
||||
}
|
||||
} else if (key === "device_id") {
|
||||
@@ -129,105 +107,46 @@ const tryDescribeAction = <T extends ActionType>(
|
||||
if (device) {
|
||||
targets.push(computeDeviceName(device, hass));
|
||||
} else {
|
||||
targets.push(
|
||||
hass.localize(
|
||||
`${actionTranslationBaseKey}.service.description.target_unknown_device`
|
||||
)
|
||||
);
|
||||
targets.push("unknown device");
|
||||
}
|
||||
} else if (key === "area_id") {
|
||||
const area = hass.areas[targetThing];
|
||||
if (area?.name) {
|
||||
targets.push(area.name);
|
||||
} else {
|
||||
targets.push(
|
||||
hass.localize(
|
||||
`${actionTranslationBaseKey}.service.description.target_unknown_area`
|
||||
)
|
||||
);
|
||||
targets.push("unknown area");
|
||||
}
|
||||
} else {
|
||||
targets.push(targetThing);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targets.length > 0) {
|
||||
base += ` ${targets.join(", ")}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
config.service_template ||
|
||||
(config.service && isTemplate(config.service))
|
||||
) {
|
||||
return hass.localize(
|
||||
`${actionTranslationBaseKey}.service.description.service_based_on_template`,
|
||||
{ targets: formatListWithAnds(hass.locale, targets) }
|
||||
);
|
||||
}
|
||||
|
||||
if (config.service) {
|
||||
const [domain, serviceName] = config.service.split(".", 2);
|
||||
const service = hass.services[domain][serviceName];
|
||||
return hass.localize(
|
||||
`${actionTranslationBaseKey}.service.description.service_based_on_name`,
|
||||
{
|
||||
name: service
|
||||
? `${domainToName(hass.localize, domain)}: ${service.name}`
|
||||
: config.service,
|
||||
targets: formatListWithAnds(hass.locale, targets),
|
||||
}
|
||||
);
|
||||
}
|
||||
return hass.localize(
|
||||
`${actionTranslationBaseKey}.service.description.service`
|
||||
);
|
||||
return base;
|
||||
}
|
||||
|
||||
if (actionType === "delay") {
|
||||
const config = action as DelayAction;
|
||||
|
||||
let duration: string;
|
||||
|
||||
if (typeof config.delay === "number") {
|
||||
duration = hass.localize(
|
||||
`${actionTranslationBaseKey}.delay.description.duration_string`,
|
||||
{
|
||||
string: secondsToDuration(config.delay)!,
|
||||
}
|
||||
);
|
||||
duration = `for ${secondsToDuration(config.delay)!}`;
|
||||
} else if (typeof config.delay === "string") {
|
||||
duration = isTemplate(config.delay)
|
||||
? hass.localize(
|
||||
`${actionTranslationBaseKey}.delay.description.duration_template`
|
||||
)
|
||||
: hass.localize(
|
||||
`${actionTranslationBaseKey}.delay.description.duration_string`,
|
||||
{
|
||||
string:
|
||||
config.delay ||
|
||||
hass.localize(
|
||||
`${actionTranslationBaseKey}.delay.description.duration_unknown`
|
||||
),
|
||||
}
|
||||
);
|
||||
? "based on a template"
|
||||
: `for ${config.delay || "a duration"}`;
|
||||
} else if (config.delay) {
|
||||
duration = hass.localize(
|
||||
`${actionTranslationBaseKey}.delay.description.duration_string`,
|
||||
{
|
||||
string: formatDuration(config.delay),
|
||||
}
|
||||
);
|
||||
duration = `for ${formatDuration(config.delay)}`;
|
||||
} else {
|
||||
duration = hass.localize(
|
||||
`${actionTranslationBaseKey}.delay.description.duration_string`,
|
||||
{
|
||||
string: hass.localize(
|
||||
`${actionTranslationBaseKey}.delay.description.duration_unknown`
|
||||
),
|
||||
}
|
||||
);
|
||||
duration = "for a duration";
|
||||
}
|
||||
|
||||
return hass.localize(`${actionTranslationBaseKey}.delay.description.full`, {
|
||||
duration: duration,
|
||||
});
|
||||
return `Delay ${duration}`;
|
||||
}
|
||||
|
||||
if (actionType === "activate_scene") {
|
||||
@@ -239,139 +158,77 @@ const tryDescribeAction = <T extends ActionType>(
|
||||
entityId = config.target?.entity_id || config.entity_id;
|
||||
}
|
||||
if (!entityId) {
|
||||
return hass.localize(
|
||||
`${actionTranslationBaseKey}.activate_scene.description.activate_scene`
|
||||
);
|
||||
return "Activate a scene";
|
||||
}
|
||||
const sceneStateObj = entityId ? hass.states[entityId] : undefined;
|
||||
return hass.localize(
|
||||
`${actionTranslationBaseKey}.activate_scene.description.activate_scene_with_name`,
|
||||
{ name: sceneStateObj ? computeStateName(sceneStateObj) : entityId }
|
||||
);
|
||||
return `Activate scene ${
|
||||
sceneStateObj ? computeStateName(sceneStateObj) : entityId
|
||||
}`;
|
||||
}
|
||||
|
||||
if (actionType === "play_media") {
|
||||
const config = action as PlayMediaAction;
|
||||
const entityId = config.target?.entity_id || config.entity_id;
|
||||
const mediaStateObj = entityId ? hass.states[entityId] : undefined;
|
||||
return hass.localize(
|
||||
`${actionTranslationBaseKey}.play_media.description.full`,
|
||||
{
|
||||
hasMedia: config.metadata.title || config.data.media_content_id,
|
||||
media: config.metadata.title || config.data.media_content_id,
|
||||
hasMediaPlayer: mediaStateObj ? true : entityId !== undefined,
|
||||
mediaPlayer: mediaStateObj ? computeStateName(mediaStateObj) : entityId,
|
||||
}
|
||||
);
|
||||
return `Play ${
|
||||
config.metadata.title || config.data.media_content_id || "media"
|
||||
} on ${
|
||||
mediaStateObj
|
||||
? computeStateName(mediaStateObj)
|
||||
: entityId || "a media player"
|
||||
}`;
|
||||
}
|
||||
|
||||
if (actionType === "wait_for_trigger") {
|
||||
const config = action as WaitForTriggerAction;
|
||||
const triggers = ensureArray(config.wait_for_trigger);
|
||||
if (!triggers || triggers.length === 0) {
|
||||
return hass.localize(
|
||||
`${actionTranslationBaseKey}.wait_for_trigger.description.wait_for_a_trigger`
|
||||
);
|
||||
return "Wait for a trigger";
|
||||
}
|
||||
const triggerNames = triggers.map((trigger) =>
|
||||
describeTrigger(trigger, hass, entityRegistry)
|
||||
);
|
||||
return hass.localize(
|
||||
`${actionTranslationBaseKey}.wait_for_trigger.description.wait_for_triggers_with_name`,
|
||||
{ triggers: formatListWithAnds(hass.locale, triggerNames) }
|
||||
);
|
||||
return `Wait for ${triggers
|
||||
.map((trigger) => describeTrigger(trigger, hass, entityRegistry))
|
||||
.join(", ")}`;
|
||||
}
|
||||
|
||||
if (actionType === "variables") {
|
||||
const config = action as VariablesAction;
|
||||
return hass.localize(
|
||||
`${actionTranslationBaseKey}.variables.description.full`,
|
||||
{
|
||||
names: formatListWithAnds(hass.locale, Object.keys(config.variables)),
|
||||
}
|
||||
);
|
||||
return `Define variables ${Object.keys(config.variables).join(", ")}`;
|
||||
}
|
||||
|
||||
if (actionType === "fire_event") {
|
||||
const config = action as EventAction;
|
||||
if (isTemplate(config.event)) {
|
||||
return hass.localize(
|
||||
`${actionTranslationBaseKey}.event.description.full`,
|
||||
{
|
||||
name: hass.localize(
|
||||
`${actionTranslationBaseKey}.event.description.template`
|
||||
),
|
||||
}
|
||||
);
|
||||
return "Fire event based on a template";
|
||||
}
|
||||
return hass.localize(`${actionTranslationBaseKey}.event.description.full`, {
|
||||
name: config.event,
|
||||
});
|
||||
return `Fire event ${config.event}`;
|
||||
}
|
||||
|
||||
if (actionType === "wait_template") {
|
||||
return hass.localize(
|
||||
`${actionTranslationBaseKey}.wait_template.description.full`
|
||||
);
|
||||
return "Wait for a template to render true";
|
||||
}
|
||||
|
||||
if (actionType === "check_condition") {
|
||||
return describeCondition(action as Condition, hass, entityRegistry);
|
||||
}
|
||||
|
||||
if (actionType === "stop") {
|
||||
const config = action as StopAction;
|
||||
return hass.localize(`${actionTranslationBaseKey}.stop.description.full`, {
|
||||
hasReason: config.stop !== undefined,
|
||||
reason: config.stop,
|
||||
});
|
||||
return `Stop${config.stop ? ` because: ${config.stop}` : ""}`;
|
||||
}
|
||||
|
||||
if (actionType === "if") {
|
||||
const config = action as IfAction;
|
||||
|
||||
let ifConditions: string[] = [];
|
||||
if (Array.isArray(config.if)) {
|
||||
const conditions = ensureArray(config.if);
|
||||
conditions.forEach((condition) => {
|
||||
ifConditions.push(describeCondition(condition, hass, entityRegistry));
|
||||
});
|
||||
} else {
|
||||
ifConditions = [config.if];
|
||||
}
|
||||
|
||||
let elseActions: string[] = [];
|
||||
if (config.else) {
|
||||
if (Array.isArray(config.else)) {
|
||||
const actions = ensureArray(config.else);
|
||||
actions.forEach((currentAction) => {
|
||||
elseActions.push(
|
||||
describeAction(hass, entityRegistry, currentAction, undefined)
|
||||
);
|
||||
});
|
||||
} else {
|
||||
elseActions = [
|
||||
describeAction(hass, entityRegistry, config.else, undefined),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
let thenActions: string[] = [];
|
||||
if (Array.isArray(config.then)) {
|
||||
const actions = ensureArray(config.then);
|
||||
actions.forEach((currentAction) => {
|
||||
thenActions.push(
|
||||
describeAction(hass, entityRegistry, currentAction, undefined)
|
||||
);
|
||||
});
|
||||
} else {
|
||||
thenActions = [
|
||||
describeAction(hass, entityRegistry, config.then, undefined),
|
||||
];
|
||||
}
|
||||
|
||||
return hass.localize(`${actionTranslationBaseKey}.if.description.full`, {
|
||||
hasElse: config.else !== undefined,
|
||||
action: formatListWithAnds(hass.locale, thenActions),
|
||||
conditions: formatListWithAnds(hass.locale, ifConditions),
|
||||
elseAction: formatListWithAnds(hass.locale, elseActions),
|
||||
});
|
||||
return `Perform an action if: ${
|
||||
!config.if
|
||||
? ""
|
||||
: typeof config.if === "string"
|
||||
? config.if
|
||||
: ensureArray(config.if).length > 1
|
||||
? `${ensureArray(config.if).length} conditions`
|
||||
: ensureArray(config.if).length
|
||||
? describeCondition(ensureArray(config.if)[0], hass, entityRegistry)
|
||||
: ""
|
||||
}${config.else ? " (or else!)" : ""}`;
|
||||
}
|
||||
|
||||
if (actionType === "choose") {
|
||||
@@ -379,64 +236,42 @@ const tryDescribeAction = <T extends ActionType>(
|
||||
if (config.choose) {
|
||||
const numActions =
|
||||
ensureArray(config.choose).length + (config.default ? 1 : 0);
|
||||
return hass.localize(
|
||||
`${actionTranslationBaseKey}.choose.description.full`,
|
||||
{ number: numActions }
|
||||
);
|
||||
return `Choose between ${numActions} action${
|
||||
numActions === 1 ? "" : "s"
|
||||
}`;
|
||||
}
|
||||
return hass.localize(
|
||||
`${actionTranslationBaseKey}.choose.description.no_action`
|
||||
);
|
||||
return "Choose an action";
|
||||
}
|
||||
|
||||
if (actionType === "repeat") {
|
||||
const config = action as RepeatAction;
|
||||
|
||||
let chosenAction = "";
|
||||
let base = "Repeat an action";
|
||||
if ("count" in config.repeat) {
|
||||
const count = config.repeat.count;
|
||||
chosenAction = hass.localize(
|
||||
`${actionTranslationBaseKey}.repeat.description.count`,
|
||||
{ count: count }
|
||||
);
|
||||
base += ` ${count} time${Number(count) === 1 ? "" : "s"}`;
|
||||
} else if ("while" in config.repeat) {
|
||||
const conditions = ensureArray(config.repeat.while).map((condition) =>
|
||||
describeCondition(condition, hass, entityRegistry)
|
||||
);
|
||||
chosenAction = hass.localize(
|
||||
`${actionTranslationBaseKey}.repeat.description.while`,
|
||||
{ conditions: formatListWithAnds(hass.locale, conditions) }
|
||||
);
|
||||
base += ` while ${ensureArray(config.repeat.while)
|
||||
.map((condition) => describeCondition(condition, hass, entityRegistry))
|
||||
.join(", ")} is true`;
|
||||
} else if ("until" in config.repeat) {
|
||||
const conditions = ensureArray(config.repeat.until).map((condition) =>
|
||||
describeCondition(condition, hass, entityRegistry)
|
||||
);
|
||||
chosenAction = hass.localize(
|
||||
`${actionTranslationBaseKey}.repeat.description.until`,
|
||||
{ conditions: formatListWithAnds(hass.locale, conditions) }
|
||||
);
|
||||
base += ` until ${ensureArray(config.repeat.until)
|
||||
.map((condition) => describeCondition(condition, hass, entityRegistry))
|
||||
.join(", ")} is true`;
|
||||
} else if ("for_each" in config.repeat) {
|
||||
const items = ensureArray(config.repeat.for_each).map((item) =>
|
||||
JSON.stringify(item)
|
||||
);
|
||||
chosenAction = hass.localize(
|
||||
`${actionTranslationBaseKey}.repeat.description.for_each`,
|
||||
{ items: formatListWithAnds(hass.locale, items) }
|
||||
);
|
||||
base += ` for every item: ${ensureArray(config.repeat.for_each)
|
||||
.map((item) => JSON.stringify(item))
|
||||
.join(", ")}`;
|
||||
}
|
||||
return hass.localize(
|
||||
`${actionTranslationBaseKey}.repeat.description.full`,
|
||||
{ chosenAction: chosenAction }
|
||||
);
|
||||
return base;
|
||||
}
|
||||
|
||||
if (actionType === "check_condition") {
|
||||
return hass.localize(
|
||||
`${actionTranslationBaseKey}.check_condition.description.full`,
|
||||
{
|
||||
condition: describeCondition(action as Condition, hass, entityRegistry),
|
||||
}
|
||||
);
|
||||
return `Test ${describeCondition(
|
||||
action as Condition,
|
||||
hass,
|
||||
entityRegistry
|
||||
)}`;
|
||||
}
|
||||
|
||||
if (actionType === "device_action") {
|
||||
@@ -452,7 +287,7 @@ const tryDescribeAction = <T extends ActionType>(
|
||||
if (localized) {
|
||||
return localized;
|
||||
}
|
||||
const stateObj = hass.states[config.entity_id];
|
||||
const stateObj = hass.states[config.entity_id as string];
|
||||
return `${config.type || "Perform action with"} ${
|
||||
stateObj ? computeStateName(stateObj) : config.entity_id
|
||||
}`;
|
||||
@@ -461,10 +296,7 @@ const tryDescribeAction = <T extends ActionType>(
|
||||
if (actionType === "parallel") {
|
||||
const config = action as ParallelAction;
|
||||
const numActions = ensureArray(config.parallel).length;
|
||||
return hass.localize(
|
||||
`${actionTranslationBaseKey}.parallel.description.full`,
|
||||
{ number: numActions }
|
||||
);
|
||||
return `Run ${numActions} action${numActions === 1 ? "" : "s"} in parallel`;
|
||||
}
|
||||
|
||||
return actionType;
|
||||
|
@@ -1,24 +1,14 @@
|
||||
import { Context, HomeAssistant } from "../types";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { Action } from "./script";
|
||||
|
||||
export const callExecuteScript = (
|
||||
hass: HomeAssistant,
|
||||
sequence: Action | Action[]
|
||||
): Promise<{ context: Context; response: Record<string, any> }> =>
|
||||
) =>
|
||||
hass.callWS({
|
||||
type: "execute_script",
|
||||
sequence,
|
||||
});
|
||||
|
||||
export const serviceCallWillDisconnect = (
|
||||
domain: string,
|
||||
service: string,
|
||||
serviceData?: Record<string, any>
|
||||
) =>
|
||||
(domain === "homeassistant" && ["restart", "stop"].includes(service)) ||
|
||||
(domain === "update" &&
|
||||
service === "install" &&
|
||||
[
|
||||
"update.home_assistant_core_update",
|
||||
"update.home_assistant_operating_system_update",
|
||||
].includes(serviceData?.entity_id));
|
||||
export const serviceCallWillDisconnect = (domain: string, service: string) =>
|
||||
domain === "homeassistant" && ["restart", "stop"].includes(service);
|
||||
|
@@ -22,8 +22,6 @@ interface MountOptions {
|
||||
default_backup_mount?: string | null;
|
||||
}
|
||||
|
||||
export type CIFSVersion = "auto" | "1.0" | "2.0";
|
||||
|
||||
interface SupervisorMountBase {
|
||||
name: string;
|
||||
usage: SupervisorMountUsage;
|
||||
@@ -44,7 +42,6 @@ export interface SupervisorNFSMount extends SupervisorMountResponse {
|
||||
export interface SupervisorCIFSMount extends SupervisorMountResponse {
|
||||
type: SupervisorMountType.CIFS;
|
||||
share: string;
|
||||
version?: CIFSVersion;
|
||||
}
|
||||
|
||||
export type SupervisorMount = SupervisorNFSMount | SupervisorCIFSMount;
|
||||
@@ -54,7 +51,6 @@ export type SupervisorNFSMountRequestParams = SupervisorNFSMount;
|
||||
export interface SupervisorCIFSMountRequestParams extends SupervisorCIFSMount {
|
||||
username?: string;
|
||||
password?: string;
|
||||
version?: CIFSVersion;
|
||||
}
|
||||
|
||||
export type SupervisorMountRequestParams =
|
||||
|
@@ -129,9 +129,5 @@ export const getSupervisorEventCollection = (
|
||||
`_supervisor${key}Event`,
|
||||
(conn2) => supervisorApiWsRequest(conn2, { endpoint }),
|
||||
(connection, store) =>
|
||||
subscribeSupervisorEventUpdates(connection, store, key),
|
||||
{ unsubGrace: false }
|
||||
subscribeSupervisorEventUpdates(connection, store, key)
|
||||
);
|
||||
|
||||
export const cleanupSupervisorCollection = (conn: Connection, key: string) =>
|
||||
delete conn[`_supervisor${key}Event`];
|
||||
|
@@ -9,7 +9,6 @@ import {
|
||||
mdiMapMarker,
|
||||
mdiMapMarkerRadius,
|
||||
mdiMessageAlert,
|
||||
mdiMicrophoneMessage,
|
||||
mdiNfcVariant,
|
||||
mdiNumeric,
|
||||
mdiStateMachine,
|
||||
@@ -28,7 +27,6 @@ export const TRIGGER_TYPES = {
|
||||
mqtt: mdiSwapHorizontal,
|
||||
numeric_state: mdiNumeric,
|
||||
sun: mdiWeatherSunny,
|
||||
conversation: mdiMicrophoneMessage,
|
||||
tag: mdiNfcVariant,
|
||||
template: mdiCodeBraces,
|
||||
time: mdiClockOutline,
|
||||
|
@@ -81,8 +81,6 @@ class DialogBox extends LitElement {
|
||||
.type=${this._params.inputType
|
||||
? this._params.inputType
|
||||
: "text"}
|
||||
.min=${this._params.inputMin}
|
||||
.max=${this._params.inputMax}
|
||||
></ha-textfield>
|
||||
`
|
||||
: ""}
|
||||
|
@@ -26,8 +26,6 @@ export interface PromptDialogParams extends BaseDialogBoxParams {
|
||||
placeholder?: string;
|
||||
confirm?: (out?: string) => void;
|
||||
cancel?: () => void;
|
||||
inputMin?: number | string;
|
||||
inputMax?: number | string;
|
||||
}
|
||||
|
||||
export interface DialogBoxParams
|
||||
|
@@ -7,7 +7,6 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { domainIcon } from "../../../../common/entity/domain_icon";
|
||||
import { stateColorCss } from "../../../../common/entity/state_color";
|
||||
@@ -137,6 +136,8 @@ export class HaMoreInfoLockToggle extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-control-switch
|
||||
.pathOn=${onIcon}
|
||||
.pathOff=${offIcon}
|
||||
vertical
|
||||
reversed
|
||||
.checked=${this._isOn}
|
||||
@@ -148,33 +149,12 @@ export class HaMoreInfoLockToggle extends LitElement {
|
||||
})}
|
||||
.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>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
ha-control-switch {
|
||||
height: 45vh;
|
||||
max-height: 320px;
|
||||
@@ -184,9 +164,6 @@ export class HaMoreInfoLockToggle extends LitElement {
|
||||
--control-switch-padding: 6px;
|
||||
--mdc-icon-size: 24px;
|
||||
}
|
||||
.pulse {
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@@ -4,7 +4,7 @@ import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-date-input";
|
||||
import "../../../components/ha-time-input";
|
||||
import { setDateValue } from "../../../data/date";
|
||||
import { isUnavailableState, UNAVAILABLE } from "../../../data/entity";
|
||||
import { isUnavailableState } from "../../../data/entity";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
@customElement("more-info-date")
|
||||
@@ -14,17 +14,15 @@ class MoreInfoDate extends LitElement {
|
||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||
|
||||
protected render() {
|
||||
if (!this.stateObj || this.stateObj.state === UNAVAILABLE) {
|
||||
if (!this.stateObj || isUnavailableState(this.stateObj.state)) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-date-input
|
||||
.locale=${this.hass.locale}
|
||||
.value=${isUnavailableState(this.stateObj.state)
|
||||
? undefined
|
||||
: this.stateObj.state}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
.value=${this.stateObj.state}
|
||||
.disabled=${isUnavailableState(this.stateObj.state)}
|
||||
@value-changed=${this._dateChanged}
|
||||
>
|
||||
</ha-date-input>
|
||||
@@ -32,9 +30,7 @@ class MoreInfoDate extends LitElement {
|
||||
}
|
||||
|
||||
private _dateChanged(ev: CustomEvent<{ value: string }>): void {
|
||||
if (ev.detail.value) {
|
||||
setDateValue(this.hass!, this.stateObj!.entity_id, ev.detail.value);
|
||||
}
|
||||
setDateValue(this.hass!, this.stateObj!.entity_id, ev.detail.value);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@@ -5,7 +5,7 @@ import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-date-input";
|
||||
import "../../../components/ha-time-input";
|
||||
import { setDateTimeValue } from "../../../data/datetime";
|
||||
import { isUnavailableState, UNAVAILABLE } from "../../../data/entity";
|
||||
import { isUnavailableState } from "../../../data/entity";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
@customElement("more-info-datetime")
|
||||
@@ -15,27 +15,25 @@ class MoreInfoDatetime extends LitElement {
|
||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||
|
||||
protected render() {
|
||||
if (!this.stateObj || this.stateObj.state === UNAVAILABLE) {
|
||||
if (!this.stateObj || isUnavailableState(this.stateObj.state)) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const dateObj = isUnavailableState(this.stateObj.state)
|
||||
? undefined
|
||||
: new Date(this.stateObj.state);
|
||||
const time = dateObj ? format(dateObj, "HH:mm:ss") : undefined;
|
||||
const date = dateObj ? format(dateObj, "yyyy-MM-dd") : undefined;
|
||||
const dateObj = new Date(this.stateObj.state);
|
||||
const time = format(dateObj, "HH:mm:ss");
|
||||
const date = format(dateObj, "yyyy-MM-dd");
|
||||
|
||||
return html`<ha-date-input
|
||||
.locale=${this.hass.locale}
|
||||
.value=${date}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
.disabled=${isUnavailableState(this.stateObj.state)}
|
||||
@value-changed=${this._dateChanged}
|
||||
>
|
||||
</ha-date-input>
|
||||
<ha-time-input
|
||||
.value=${time}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
.disabled=${isUnavailableState(this.stateObj.state)}
|
||||
@value-changed=${this._timeChanged}
|
||||
@click=${this._stopEventPropagation}
|
||||
></ha-time-input>`;
|
||||
@@ -46,23 +44,19 @@ class MoreInfoDatetime extends LitElement {
|
||||
}
|
||||
|
||||
private _timeChanged(ev: CustomEvent<{ value: string }>): void {
|
||||
if (ev.detail.value) {
|
||||
const dateObj = new Date(this.stateObj!.state);
|
||||
const newTime = ev.detail.value.split(":").map(Number);
|
||||
dateObj.setHours(newTime[0], newTime[1], newTime[2]);
|
||||
const dateObj = new Date(this.stateObj!.state);
|
||||
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 {
|
||||
if (ev.detail.value) {
|
||||
const dateObj = new Date(this.stateObj!.state);
|
||||
const newDate = ev.detail.value.split("-").map(Number);
|
||||
dateObj.setFullYear(newDate[0], newDate[1] - 1, newDate[2]);
|
||||
const dateObj = new Date(this.stateObj!.state);
|
||||
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 {
|
||||
|
@@ -46,8 +46,7 @@ class MoreInfoGroup extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const baseStateObj =
|
||||
states.find((s) => s.state === this.stateObj!.state) || states[0];
|
||||
const baseStateObj = states.find((s) => s.state === "on") || states[0];
|
||||
|
||||
const groupDomain = computeGroupDomain(this.stateObj);
|
||||
|
||||
@@ -57,8 +56,6 @@ class MoreInfoGroup extends LitElement {
|
||||
this._groupDomainStateObj = {
|
||||
...baseStateObj,
|
||||
entity_id: this.stateObj.entity_id,
|
||||
last_updated: this.stateObj.last_updated,
|
||||
last_changed: this.stateObj.last_changed,
|
||||
attributes: {
|
||||
...baseStateObj.attributes,
|
||||
friendly_name: this.stateObj.attributes.friendly_name,
|
||||
|
@@ -10,10 +10,7 @@ import {
|
||||
import { property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import {
|
||||
computeAttributeNameDisplay,
|
||||
computeAttributeValueDisplay,
|
||||
} from "../../../common/entity/compute_attribute_display";
|
||||
import { computeAttributeValueDisplay } from "../../../common/entity/compute_attribute_display";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
@@ -25,7 +22,6 @@ import {
|
||||
HUMIDIFIER_SUPPORT_MODES,
|
||||
} from "../../../data/humidifier";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
||||
|
||||
class MoreInfoHumidifier extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -53,14 +49,7 @@ class MoreInfoHumidifier extends LitElement {
|
||||
})}
|
||||
>
|
||||
<div class="container-humidity">
|
||||
<div>
|
||||
${computeAttributeNameDisplay(
|
||||
hass.localize,
|
||||
stateObj,
|
||||
hass.entities,
|
||||
"humidity"
|
||||
)}
|
||||
</div>
|
||||
<div>${hass.localize("ui.card.humidifier.humidity")}</div>
|
||||
<div class="single-row">
|
||||
<div class="target-humidity">${stateObj.attributes.humidity} %</div>
|
||||
<ha-slider
|
||||
@@ -76,35 +65,6 @@ class MoreInfoHumidifier extends LitElement {
|
||||
</ha-slider>
|
||||
</div>
|
||||
</div>
|
||||
<ha-select
|
||||
.label=${hass.localize("ui.card.humidifier.state")}
|
||||
.value=${stateObj.state}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
@selected=${this._handleStateChanged}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
<mwc-list-item value="off">
|
||||
${computeStateDisplay(
|
||||
hass.localize,
|
||||
stateObj,
|
||||
hass.locale,
|
||||
this.hass.config,
|
||||
hass.entities,
|
||||
"off"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item value="on">
|
||||
${computeStateDisplay(
|
||||
hass.localize,
|
||||
stateObj,
|
||||
hass.locale,
|
||||
this.hass.config,
|
||||
hass.entities,
|
||||
"on"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
</ha-select>
|
||||
|
||||
${supportModes
|
||||
? html`
|
||||
@@ -163,16 +123,6 @@ class MoreInfoHumidifier extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private _handleStateChanged(ev) {
|
||||
const newVal = ev.target.value || null;
|
||||
this._callServiceHelper(
|
||||
this.stateObj!.state,
|
||||
newVal,
|
||||
newVal === "on" ? "turn_on" : "turn_off",
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
private _handleModeChanged(ev) {
|
||||
const newVal = ev.target.value || null;
|
||||
this._callServiceHelper(
|
||||
@@ -229,11 +179,6 @@ class MoreInfoHumidifier extends LitElement {
|
||||
|
||||
ha-select {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
ha-slider {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.container-humidity .single-row {
|
||||
|
@@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-date-input";
|
||||
import "../../../components/ha-time-input";
|
||||
import { isUnavailableState, UNAVAILABLE } from "../../../data/entity";
|
||||
import { isUnavailableState } from "../../../data/entity";
|
||||
import { setTimeValue } from "../../../data/time";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
@@ -14,17 +14,15 @@ class MoreInfoTime extends LitElement {
|
||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||
|
||||
protected render() {
|
||||
if (!this.stateObj || this.stateObj.state === UNAVAILABLE) {
|
||||
if (!this.stateObj || isUnavailableState(this.stateObj.state)) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-time-input
|
||||
.value=${isUnavailableState(this.stateObj.state)
|
||||
? undefined
|
||||
: this.stateObj.state}
|
||||
.value=${this.stateObj.state}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
.disabled=${isUnavailableState(this.stateObj.state)}
|
||||
@value-changed=${this._timeChanged}
|
||||
@click=${this._stopEventPropagation}
|
||||
></ha-time-input>
|
||||
@@ -36,9 +34,7 @@ class MoreInfoTime extends LitElement {
|
||||
}
|
||||
|
||||
private _timeChanged(ev: CustomEvent<{ value: string }>): void {
|
||||
if (ev.detail.value) {
|
||||
setTimeValue(this.hass!, this.stateObj!.entity_id, ev.detail.value);
|
||||
}
|
||||
setTimeValue(this.hass!, this.stateObj!.entity_id, ev.detail.value);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@@ -105,34 +105,36 @@ class MoreInfoVacuum extends LitElement {
|
||||
return html`
|
||||
${stateObj.state !== UNAVAILABLE
|
||||
? html` <div class="flex-horizontal">
|
||||
<div>
|
||||
<span class="status-subtitle"
|
||||
>${this.hass!.localize(
|
||||
"ui.dialogs.more_info_control.vacuum.status"
|
||||
)}:
|
||||
</span>
|
||||
<span>
|
||||
<strong>
|
||||
${supportsFeature(stateObj, VacuumEntityFeature.STATUS) &&
|
||||
stateObj.attributes.status
|
||||
? computeAttributeValueDisplay(
|
||||
this.hass.localize,
|
||||
stateObj,
|
||||
this.hass.locale,
|
||||
this.hass.config,
|
||||
this.hass.entities,
|
||||
"status"
|
||||
)
|
||||
: computeStateDisplay(
|
||||
this.hass.localize,
|
||||
stateObj,
|
||||
this.hass.locale,
|
||||
this.hass.config,
|
||||
this.hass.entities
|
||||
)}
|
||||
</strong>
|
||||
</span>
|
||||
</div>
|
||||
${supportsFeature(stateObj, VacuumEntityFeature.STATUS)
|
||||
? html`
|
||||
<div>
|
||||
<span class="status-subtitle"
|
||||
>${this.hass!.localize(
|
||||
"ui.dialogs.more_info_control.vacuum.status"
|
||||
)}:
|
||||
</span>
|
||||
<span>
|
||||
<strong>
|
||||
${computeAttributeValueDisplay(
|
||||
this.hass.localize,
|
||||
stateObj,
|
||||
this.hass.locale,
|
||||
this.hass.config,
|
||||
this.hass.entities,
|
||||
"status"
|
||||
) ||
|
||||
computeStateDisplay(
|
||||
this.hass.localize,
|
||||
stateObj,
|
||||
this.hass.locale,
|
||||
this.hass.config,
|
||||
this.hass.entities
|
||||
)}
|
||||
</strong>
|
||||
</span>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${supportsFeature(stateObj, VacuumEntityFeature.BATTERY) &&
|
||||
stateObj.attributes.battery_level
|
||||
? html`
|
||||
|
@@ -501,7 +501,7 @@ export class QuickBar extends LitElement {
|
||||
|
||||
private async _generateCommandItems(): Promise<CommandItem[]> {
|
||||
return [
|
||||
...(await this._generateReloadCommands()),
|
||||
...this._generateReloadCommands(),
|
||||
...this._generateServerControlCommands(),
|
||||
...(await this._generateNavigationCommands()),
|
||||
].sort((a, b) =>
|
||||
@@ -513,22 +513,17 @@ export class QuickBar extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private async _generateReloadCommands(): Promise<CommandItem[]> {
|
||||
private _generateReloadCommands(): CommandItem[] {
|
||||
// Get all domains that have a direct "reload" service
|
||||
const reloadableDomains = componentsWithService(this.hass, "reload");
|
||||
|
||||
const localize = await this.hass.loadBackendTranslation(
|
||||
"title",
|
||||
reloadableDomains
|
||||
);
|
||||
|
||||
const commands = reloadableDomains.map((domain) => ({
|
||||
primaryText:
|
||||
this.hass.localize(`ui.dialogs.quick-bar.commands.reload.${domain}`) ||
|
||||
this.hass.localize(
|
||||
"ui.dialogs.quick-bar.commands.reload.reload",
|
||||
"domain",
|
||||
domainToName(localize, domain)
|
||||
domainToName(this.hass.localize, domain)
|
||||
),
|
||||
action: () => this.hass.callService(domain, "reload"),
|
||||
iconPath: mdiReload,
|
||||
|
@@ -35,6 +35,7 @@ import {
|
||||
listAssistPipelines,
|
||||
runAssistPipeline,
|
||||
} from "../../data/assist_pipeline";
|
||||
import { AgentInfo, getAgentInfo } from "../../data/conversation";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { AudioRecorder } from "../../util/audio-recorder";
|
||||
@@ -65,6 +66,8 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
|
||||
@state() private _pipeline?: AssistPipeline;
|
||||
|
||||
@state() private _agentInfo?: AgentInfo;
|
||||
|
||||
@state() private _showSendButton = false;
|
||||
|
||||
@state() private _pipelines?: AssistPipeline[];
|
||||
@@ -112,6 +115,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
this._opened = false;
|
||||
this._pipeline = undefined;
|
||||
this._pipelines = undefined;
|
||||
this._agentInfo = undefined;
|
||||
this._conversation = undefined;
|
||||
this._conversationId = null;
|
||||
this._audioRecorder?.close();
|
||||
@@ -261,6 +265,17 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
`}
|
||||
</span>
|
||||
</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>
|
||||
</ha-dialog>
|
||||
`;
|
||||
@@ -283,7 +298,12 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
if (e.code === "not_found") {
|
||||
this._pipelineId = undefined;
|
||||
}
|
||||
return;
|
||||
}
|
||||
this._agentInfo = await getAgentInfo(
|
||||
this.hass,
|
||||
this._pipeline.conversation_engine
|
||||
);
|
||||
}
|
||||
|
||||
private async _loadPipelines() {
|
||||
@@ -708,6 +728,12 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
flex: 1 0;
|
||||
padding: 4px;
|
||||
}
|
||||
.attribution {
|
||||
display: block;
|
||||
color: var(--secondary-text-color);
|
||||
padding-top: 4px;
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
.messages {
|
||||
display: block;
|
||||
height: 400px;
|
||||
|
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../components/ha-circular-progress";
|
||||
import "../components/ha-icon-button-arrow-prev";
|
||||
@@ -24,8 +17,6 @@ class HassLoadingScreen extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property() public message?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this.noToolbar
|
||||
@@ -47,9 +38,6 @@ class HassLoadingScreen extends LitElement {
|
||||
</div>`}
|
||||
<div class="content">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
${this.message
|
||||
? html`<div id="loading-text">${this.message}</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -92,14 +80,9 @@ class HassLoadingScreen extends LitElement {
|
||||
.content {
|
||||
height: calc(100% - var(--header-height));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
#loading-text {
|
||||
max-width: 350px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -19,7 +19,6 @@ import "../components/ha-menu-button";
|
||||
import "../components/ha-svg-icon";
|
||||
import "../components/ha-tab";
|
||||
import { HomeAssistant, Route } from "../types";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
|
||||
export interface PageNavigation {
|
||||
path: string;
|
||||
@@ -187,7 +186,7 @@ class HassTabsSubpage extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="content ha-scrollbar ${classMap({ tabs: showTabs })}"
|
||||
class="content ${classMap({ tabs: showTabs })}"
|
||||
@scroll=${this._saveScrollPos}
|
||||
>
|
||||
<slot></slot>
|
||||
@@ -212,146 +211,143 @@ class HassTabsSubpage extends LitElement {
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background-color: var(--primary-background-color);
|
||||
}
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background-color: var(--primary-background-color);
|
||||
}
|
||||
|
||||
:host([narrow]) {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
}
|
||||
:host([narrow]) {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
ha-menu-button {
|
||||
margin-right: 24px;
|
||||
}
|
||||
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 {
|
||||
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%;
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
.toolbar a {
|
||||
color: var(--sidebar-text-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
.bottom-bar a {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
#tabbar {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#tabbar {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#tabbar > a {
|
||||
overflow: hidden;
|
||||
max-width: 45%;
|
||||
}
|
||||
#tabbar > a {
|
||||
overflow: hidden;
|
||||
max-width: 45%;
|
||||
}
|
||||
|
||||
#tabbar.bottom-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
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);
|
||||
}
|
||||
#tabbar.bottom-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
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);
|
||||
}
|
||||
|
||||
#tabbar:not(.bottom-bar) {
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
#tabbar:not(.bottom-bar) {
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:host(:not([narrow])) #toolbar-icon {
|
||||
min-width: 40px;
|
||||
}
|
||||
:host(:not([narrow])) #toolbar-icon {
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
ha-menu-button,
|
||||
ha-icon-button-arrow-prev,
|
||||
::slotted([slot="toolbar-icon"]) {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
pointer-events: auto;
|
||||
color: var(--sidebar-icon-color);
|
||||
}
|
||||
ha-menu-button,
|
||||
ha-icon-button-arrow-prev,
|
||||
::slotted([slot="toolbar-icon"]) {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
pointer-events: auto;
|
||||
color: var(--sidebar-icon-color);
|
||||
}
|
||||
|
||||
.main-title {
|
||||
flex: 1;
|
||||
max-height: var(--header-height);
|
||||
line-height: 20px;
|
||||
color: var(--sidebar-text-color);
|
||||
margin: var(--main-title-margin, 0 0 0 24px);
|
||||
}
|
||||
.main-title {
|
||||
flex: 1;
|
||||
max-height: var(--header-height);
|
||||
line-height: 20px;
|
||||
color: var(--sidebar-text-color);
|
||||
margin: var(--main-title-margin, 0 0 0 24px);
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
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;
|
||||
}
|
||||
.content {
|
||||
position: relative;
|
||||
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 {
|
||||
height: calc(100% - 2 * var(--header-height));
|
||||
height: calc(
|
||||
100% - 2 * var(--header-height) - env(safe-area-inset-bottom)
|
||||
);
|
||||
}
|
||||
: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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
#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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -34,7 +34,6 @@ import "../lovelace/components/hui-generic-entity-row";
|
||||
import "./ha-recurrence-rule-editor";
|
||||
import { showConfirmEventDialog } from "./show-confirm-event-dialog-box";
|
||||
import { CalendarEventEditDialogParams } from "./show-dialog-calendar-event-editor";
|
||||
import { TimeZone } from "../../data/translation";
|
||||
|
||||
const CALENDAR_DOMAINS = ["calendar"];
|
||||
|
||||
@@ -82,9 +81,8 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
supportsFeature(stateObj, CalendarEntityFeature.CREATE_EVENT)
|
||||
)?.entity_id;
|
||||
this._timeZone =
|
||||
this.hass.locale.time_zone === TimeZone.local
|
||||
? Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
: this.hass.config.time_zone;
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone ||
|
||||
this.hass.config.time_zone;
|
||||
if (params.entry) {
|
||||
const entry = params.entry!;
|
||||
this._allDay = isDate(entry.dtstart);
|
||||
@@ -502,7 +500,7 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
return;
|
||||
}
|
||||
const eventData = this._calculateData();
|
||||
if (entry.rrule && eventData.rrule && range === RecurrenceRange.THISEVENT) {
|
||||
if (eventData.rrule && range === RecurrenceRange.THISEVENT) {
|
||||
// Updates to a single instance of a recurring event by definition
|
||||
// cannot change the recurrence rule and doing so would be invalid.
|
||||
// It is difficult to detect if the user changed the recurrence rule
|
||||
|
@@ -46,7 +46,6 @@ import type {
|
||||
} from "../../types";
|
||||
import { showCalendarEventDetailDialog } from "./show-dialog-calendar-event-detail";
|
||||
import { showCalendarEventEditDialog } from "./show-dialog-calendar-event-editor";
|
||||
import { TimeZone } from "../../data/translation";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@@ -241,26 +240,9 @@ export class HAFullCalendar extends LitElement {
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this._loadCalendar();
|
||||
}
|
||||
|
||||
private async _loadCalendar() {
|
||||
const luxonPlugin =
|
||||
this.hass.locale.time_zone === TimeZone.local
|
||||
? undefined
|
||||
: (await import("@fullcalendar/luxon3")).default;
|
||||
|
||||
const config: CalendarOptions = {
|
||||
...defaultFullCalendarConfig,
|
||||
plugins:
|
||||
this.hass.locale.time_zone === TimeZone.local
|
||||
? defaultFullCalendarConfig.plugins
|
||||
: [...defaultFullCalendarConfig.plugins!, luxonPlugin!],
|
||||
locale: this.hass.language,
|
||||
timeZone:
|
||||
this.hass.locale.time_zone === TimeZone.local
|
||||
? "local"
|
||||
: this.hass.config.time_zone,
|
||||
firstDay: firstWeekdayIndex(this.hass.locale),
|
||||
initialView: this.initialView,
|
||||
eventDisplay: this.eventDisplay,
|
||||
|
@@ -3,9 +3,9 @@ import "@material/mwc-list/mwc-list-item";
|
||||
import {
|
||||
mdiAlertCircleCheck,
|
||||
mdiCheck,
|
||||
mdiContentDuplicate,
|
||||
mdiContentCopy,
|
||||
mdiContentCut,
|
||||
mdiContentDuplicate,
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
mdiPlay,
|
||||
@@ -14,19 +14,17 @@ import {
|
||||
mdiSort,
|
||||
mdiStopCircleOutline,
|
||||
} from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
PropertyValues,
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { consume } from "@lit-labs/context";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
||||
@@ -38,9 +36,12 @@ import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
||||
import { ACTION_TYPES, YAML_ONLY_ACTION_TYPES } from "../../../../data/action";
|
||||
import { AutomationClipboard } from "../../../../data/automation";
|
||||
import { validateConfig } from "../../../../data/config";
|
||||
import { EntityRegistryEntry } from "../../../../data/entity_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../../../data/entity_registry";
|
||||
import { Clipboard } from "../../../../data/automation";
|
||||
import {
|
||||
Action,
|
||||
NonConditionAction,
|
||||
@@ -70,7 +71,6 @@ import "./types/ha-automation-action-service";
|
||||
import "./types/ha-automation-action-stop";
|
||||
import "./types/ha-automation-action-wait_for_trigger";
|
||||
import "./types/ha-automation-action-wait_template";
|
||||
import { fullEntitiesContext } from "../../../../data/context";
|
||||
|
||||
export const getType = (action: Action | undefined) => {
|
||||
if (!action) {
|
||||
@@ -127,17 +127,9 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
state: false,
|
||||
subscribe: true,
|
||||
storage: "sessionStorage",
|
||||
})
|
||||
public _clipboard?: AutomationClipboard;
|
||||
@property() public clipboard?: Clipboard;
|
||||
|
||||
@state()
|
||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||
_entityReg!: EntityRegistryEntry[];
|
||||
@state() private _entityReg: EntityRegistryEntry[] = [];
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@@ -147,6 +139,14 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
|
||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entityReg = entities;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues) {
|
||||
if (!changedProperties.has("action")) {
|
||||
return;
|
||||
@@ -396,6 +396,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
narrow: this.narrow,
|
||||
reOrderMode: this.reOrderMode,
|
||||
disabled: this.disabled,
|
||||
clipboard: this.clipboard,
|
||||
})}
|
||||
</div>
|
||||
`}
|
||||
@@ -430,10 +431,10 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
fireEvent(this, "duplicate");
|
||||
break;
|
||||
case 4:
|
||||
this._setClipboard();
|
||||
fireEvent(this, "set-clipboard", { action: this.action });
|
||||
break;
|
||||
case 5:
|
||||
this._setClipboard();
|
||||
fireEvent(this, "set-clipboard", { action: this.action });
|
||||
fireEvent(this, "value-changed", { value: null });
|
||||
break;
|
||||
case 6:
|
||||
@@ -453,13 +454,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _setClipboard() {
|
||||
this._clipboard = {
|
||||
...this._clipboard,
|
||||
action: deepClone(this.action),
|
||||
};
|
||||
}
|
||||
|
||||
private _onDisable() {
|
||||
const enabled = !(this.action.enabled ?? true);
|
||||
const value = { ...this.action, enabled };
|
||||
|
@@ -29,7 +29,7 @@ import type { HaSelect } from "../../../../components/ha-select";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { ACTION_TYPES } from "../../../../data/action";
|
||||
import { Action } from "../../../../data/script";
|
||||
import { AutomationClipboard } from "../../../../data/automation";
|
||||
import { Clipboard } from "../../../../data/automation";
|
||||
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||
import {
|
||||
loadSortable,
|
||||
@@ -52,7 +52,6 @@ import "./types/ha-automation-action-service";
|
||||
import "./types/ha-automation-action-stop";
|
||||
import "./types/ha-automation-action-wait_for_trigger";
|
||||
import "./types/ha-automation-action-wait_template";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
|
||||
const PASTE_VALUE = "__paste__";
|
||||
|
||||
@@ -70,13 +69,7 @@ export default class HaAutomationAction extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
state: true,
|
||||
subscribe: true,
|
||||
storage: "sessionStorage",
|
||||
})
|
||||
public _clipboard?: AutomationClipboard;
|
||||
@property() public clipboard?: Clipboard;
|
||||
|
||||
private _focusLastActionOnChange = false;
|
||||
|
||||
@@ -120,6 +113,7 @@ export default class HaAutomationAction extends LitElement {
|
||||
@duplicate=${this._duplicateAction}
|
||||
@value-changed=${this._actionChanged}
|
||||
@re-order=${this._enterReOrderMode}
|
||||
.clipboard=${this.clipboard}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
${this.reOrderMode
|
||||
@@ -168,14 +162,14 @@ export default class HaAutomationAction extends LitElement {
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</ha-button>
|
||||
${this._clipboard?.action
|
||||
${this.clipboard?.action
|
||||
? html` <mwc-list-item .value=${PASTE_VALUE} graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.paste"
|
||||
)}
|
||||
(${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.actions.type.${getType(
|
||||
this._clipboard.action
|
||||
this.clipboard.action
|
||||
)}.label`
|
||||
)})
|
||||
<ha-svg-icon slot="graphic" .path=${mdiContentPaste}></ha-svg-icon
|
||||
@@ -266,7 +260,7 @@ export default class HaAutomationAction extends LitElement {
|
||||
|
||||
let actions: Action[];
|
||||
if (action === PASTE_VALUE) {
|
||||
actions = this.actions.concat(deepClone(this._clipboard!.action));
|
||||
actions = this.actions.concat(deepClone(this.clipboard!.action));
|
||||
} else {
|
||||
const elClass = customElements.get(
|
||||
`ha-automation-action-${action}`
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { mdiDelete, mdiPlus } from "@mdi/js";
|
||||
import { CSSResultGroup, LitElement, css, html } from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ensureArray } from "../../../../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-button";
|
||||
import { ensureArray } from "../../../../../common/array/ensure-array";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import { Condition } from "../../../../../data/automation";
|
||||
import "../../../../../components/ha-button";
|
||||
import { Condition, Clipboard } from "../../../../../data/automation";
|
||||
import { Action, ChooseAction } from "../../../../../data/script";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
@@ -23,6 +23,8 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
|
||||
@state() private _showDefault = false;
|
||||
|
||||
@property() public clipboard?: Clipboard;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { choose: [{ conditions: [], sequence: [] }] };
|
||||
}
|
||||
@@ -63,6 +65,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
.hass=${this.hass}
|
||||
.idx=${idx}
|
||||
@value-changed=${this._conditionChanged}
|
||||
.clipboard=${this.clipboard}
|
||||
></ha-automation-condition>
|
||||
<h3>
|
||||
${this.hass.localize(
|
||||
@@ -77,6 +80,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
.hass=${this.hass}
|
||||
.idx=${idx}
|
||||
@value-changed=${this._actionChanged}
|
||||
.clipboard=${this.clipboard}
|
||||
></ha-automation-action>
|
||||
</div>
|
||||
</ha-card>`
|
||||
@@ -105,6 +109,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._defaultChanged}
|
||||
.hass=${this.hass}
|
||||
.clipboard=${this.clipboard}
|
||||
></ha-automation-action>
|
||||
`
|
||||
: html`<div class="link-button-row">
|
||||
|
@@ -6,7 +6,7 @@ import { stringCompare } from "../../../../../common/string/compare";
|
||||
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||
import "../../../../../components/ha-select";
|
||||
import type { HaSelect } from "../../../../../components/ha-select";
|
||||
import type { Condition } from "../../../../../data/automation";
|
||||
import type { Condition, Clipboard } from "../../../../../data/automation";
|
||||
import { CONDITION_TYPES } from "../../../../../data/condition";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "../../condition/ha-automation-condition-editor";
|
||||
@@ -20,6 +20,8 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
||||
|
||||
@property() public action!: Condition;
|
||||
|
||||
@property() public clipboard?: Clipboard;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { condition: "state" };
|
||||
}
|
||||
@@ -49,6 +51,7 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
||||
.disabled=${this.disabled}
|
||||
.hass=${this.hass}
|
||||
@value-changed=${this._conditionChanged}
|
||||
.clipboard=${this.clipboard}
|
||||
></ha-automation-condition-editor>
|
||||
`;
|
||||
}
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-textfield";
|
||||
import { Action, IfAction } from "../../../../../data/script";
|
||||
import type { Clipboard } from "../../../../../data/automation";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type { Condition } from "../../../../lovelace/common/validate-condition";
|
||||
import "../ha-automation-action";
|
||||
import "../../../../../components/ha-textfield";
|
||||
import type { ActionElement } from "../ha-automation-action-row";
|
||||
|
||||
@customElement("ha-automation-action-if")
|
||||
@@ -19,6 +20,8 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
@property() public clipboard?: Clipboard;
|
||||
|
||||
@state() private _showElse = false;
|
||||
|
||||
public static get defaultConfig() {
|
||||
@@ -43,6 +46,7 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._ifChanged}
|
||||
.clipboard=${this.clipboard}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-condition>
|
||||
|
||||
@@ -57,6 +61,7 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._thenChanged}
|
||||
.clipboard=${this.clipboard}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>
|
||||
${this._showElse || action.else
|
||||
@@ -72,6 +77,7 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._elseChanged}
|
||||
.clipboard=${this.clipboard}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>
|
||||
`
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-textfield";
|
||||
import { Action, ParallelAction } from "../../../../../data/script";
|
||||
import type { Clipboard } from "../../../../../data/automation";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../ha-automation-action";
|
||||
import "../../../../../components/ha-textfield";
|
||||
import type { ActionElement } from "../ha-automation-action-row";
|
||||
|
||||
@customElement("ha-automation-action-parallel")
|
||||
@@ -18,6 +19,8 @@ export class HaParallelAction extends LitElement implements ActionElement {
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
@property() public clipboard?: Clipboard;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return {
|
||||
parallel: [],
|
||||
@@ -34,6 +37,7 @@ export class HaParallelAction extends LitElement implements ActionElement {
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._actionsChanged}
|
||||
.clipboard=${this.clipboard}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>
|
||||
`;
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-textfield";
|
||||
import {
|
||||
Action,
|
||||
CountRepeat,
|
||||
@@ -9,10 +8,12 @@ import {
|
||||
UntilRepeat,
|
||||
WhileRepeat,
|
||||
} from "../../../../../data/script";
|
||||
import type { Clipboard } from "../../../../../data/automation";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type { Condition } from "../../../../lovelace/common/validate-condition";
|
||||
import "../ha-automation-action";
|
||||
import "../../../../../components/ha-textfield";
|
||||
import type { ActionElement } from "../ha-automation-action-row";
|
||||
|
||||
const OPTIONS = ["count", "while", "until"] as const;
|
||||
@@ -29,6 +30,8 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
@property() public clipboard?: Clipboard;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { repeat: { count: 2, sequence: [] } };
|
||||
}
|
||||
@@ -82,6 +85,7 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._conditionChanged}
|
||||
.clipboard=${this.clipboard}
|
||||
></ha-automation-condition>`
|
||||
: type === "until"
|
||||
? html` <h3>
|
||||
@@ -95,6 +99,7 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._conditionChanged}
|
||||
.clipboard=${this.clipboard}
|
||||
></ha-automation-condition>`
|
||||
: ""}
|
||||
</div>
|
||||
@@ -109,6 +114,7 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._actionChanged}
|
||||
.clipboard=${this.clipboard}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>
|
||||
`;
|
||||
|
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
PropertyValues,
|
||||
} from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { assert } from "superstruct";
|
||||
@@ -28,9 +21,7 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@state() private _action?: ServiceAction;
|
||||
|
||||
@state() private _responseChecked = false;
|
||||
@state() private _action!: ServiceAction;
|
||||
|
||||
private _fields = memoizeOne(
|
||||
(
|
||||
@@ -107,12 +98,6 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._action) {
|
||||
return nothing;
|
||||
}
|
||||
const [domain, service] = this._action.service
|
||||
? this._action.service.split(".", 2)
|
||||
: [undefined, undefined];
|
||||
return html`
|
||||
<ha-service-control
|
||||
.narrow=${this.narrow}
|
||||
@@ -122,43 +107,6 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
||||
.showAdvanced=${this.hass.userData?.showAdvanced}
|
||||
@value-changed=${this._actionChanged}
|
||||
></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.hass.services[domain][service].response!.optional &&
|
||||
!this._action.response_variable &&
|
||||
!this._responseChecked)}
|
||||
@change=${this._responseVariableChanged}
|
||||
></ha-textfield>
|
||||
</ha-settings-row>`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -166,39 +114,6 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
||||
if (ev.detail.value === this._action) {
|
||||
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 {
|
||||
@@ -207,25 +122,6 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
||||
display: block;
|
||||
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() {
|
||||
const { error, stop, response_variable } = this.action;
|
||||
const { error, stop } = this.action;
|
||||
|
||||
return html`
|
||||
<ha-textfield
|
||||
@@ -30,14 +30,6 @@ export class HaStopAction extends LitElement implements ActionElement {
|
||||
.disabled=${this.disabled}
|
||||
@change=${this._stopChanged}
|
||||
></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
|
||||
.disabled=${this.disabled}
|
||||
.label=${this.hass.localize(
|
||||
@@ -53,21 +45,14 @@ export class HaStopAction extends LitElement implements ActionElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _stopChanged(ev: Event) {
|
||||
private _stopChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this.action, stop: (ev.target as any).value },
|
||||
});
|
||||
}
|
||||
|
||||
private _responseChanged(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this.action, response_variable: (ev.target as any).value },
|
||||
});
|
||||
}
|
||||
|
||||
private _errorChanged(ev: Event) {
|
||||
private _errorChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this.action, error: (ev.target as any).checked },
|
||||
|
@@ -1,16 +1,17 @@
|
||||
import "../../../../../components/ha-textfield";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
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 { TimeChangedEvent } from "../../../../../components/ha-base-time-input";
|
||||
import "../../../../../components/ha-duration-input";
|
||||
import "../../../../../components/ha-formfield";
|
||||
import "../../../../../components/ha-textfield";
|
||||
import { WaitForTriggerAction } from "../../../../../data/script";
|
||||
import type { Clipboard } from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "../../trigger/ha-automation-trigger";
|
||||
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")
|
||||
export class HaWaitForTriggerAction
|
||||
@@ -25,6 +26,8 @@ export class HaWaitForTriggerAction
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
@property() public clipboard?: Clipboard;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { wait_for_trigger: [] };
|
||||
}
|
||||
@@ -62,6 +65,7 @@ export class HaWaitForTriggerAction
|
||||
.name=${"wait_for_trigger"}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
@value-changed=${this._valueChanged}
|
||||
.clipboard=${this.clipboard}
|
||||
></ha-automation-trigger>
|
||||
`;
|
||||
}
|
||||
|
@@ -138,12 +138,11 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
this.config.use_blueprint.input[key]) ??
|
||||
value?.default}
|
||||
.disabled=${this.disabled}
|
||||
.required=${value?.default === undefined}
|
||||
@value-changed=${this._inputChanged}
|
||||
></ha-selector>`
|
||||
: html`<ha-textfield
|
||||
.key=${key}
|
||||
.required=${value?.default === undefined}
|
||||
required
|
||||
.value=${(this.config.use_blueprint.input &&
|
||||
this.config.use_blueprint.input[key]) ??
|
||||
value?.default}
|
||||
|
@@ -4,7 +4,7 @@ import memoizeOne from "memoize-one";
|
||||
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-yaml-editor";
|
||||
import type { Condition } from "../../../../data/automation";
|
||||
import type { Condition, Clipboard } from "../../../../data/automation";
|
||||
import { expandConditionWithShorthand } from "../../../../data/automation";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
@@ -32,6 +32,8 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
@property() public clipboard?: Clipboard;
|
||||
|
||||
private _processedCondition = memoizeOne((condition) =>
|
||||
expandConditionWithShorthand(condition)
|
||||
);
|
||||
@@ -70,6 +72,7 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
condition: condition,
|
||||
reOrderMode: this.reOrderMode,
|
||||
disabled: this.disabled,
|
||||
clipboard: this.clipboard,
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
|
@@ -3,9 +3,9 @@ import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import {
|
||||
mdiCheck,
|
||||
mdiContentDuplicate,
|
||||
mdiContentCopy,
|
||||
mdiContentCut,
|
||||
mdiContentDuplicate,
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
mdiFlask,
|
||||
@@ -14,11 +14,9 @@ import {
|
||||
mdiSort,
|
||||
mdiStopCircleOutline,
|
||||
} from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
@@ -26,8 +24,8 @@ import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import type { AutomationClipboard } from "../../../../data/automation";
|
||||
import { Condition, testCondition } from "../../../../data/automation";
|
||||
import type { Clipboard } from "../../../../data/automation";
|
||||
import { describeCondition } from "../../../../data/automation_i18n";
|
||||
import { CONDITION_TYPES } from "../../../../data/condition";
|
||||
import { validateConfig } from "../../../../data/config";
|
||||
@@ -85,13 +83,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
state: false,
|
||||
subscribe: true,
|
||||
storage: "sessionStorage",
|
||||
})
|
||||
public _clipboard?: AutomationClipboard;
|
||||
@property() public clipboard?: Clipboard;
|
||||
|
||||
@state() private _yamlMode = false;
|
||||
|
||||
@@ -298,6 +290,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.condition=${this.condition}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
.clipboard=${this.clipboard}
|
||||
></ha-automation-condition-editor>
|
||||
</div>
|
||||
</ha-expansion-panel>
|
||||
@@ -350,10 +343,10 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
fireEvent(this, "duplicate");
|
||||
break;
|
||||
case 4:
|
||||
this._setClipboard();
|
||||
fireEvent(this, "set-clipboard", { condition: this.condition });
|
||||
break;
|
||||
case 5:
|
||||
this._setClipboard();
|
||||
fireEvent(this, "set-clipboard", { condition: this.condition });
|
||||
fireEvent(this, "value-changed", { value: null });
|
||||
break;
|
||||
case 6:
|
||||
@@ -373,13 +366,6 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _setClipboard() {
|
||||
this._clipboard = {
|
||||
...this._clipboard,
|
||||
condition: deepClone(this.condition),
|
||||
};
|
||||
}
|
||||
|
||||
private _onDisable() {
|
||||
const enabled = !(this.condition.enabled ?? true);
|
||||
const value = { ...this.condition, enabled };
|
||||
|
@@ -3,9 +3,9 @@ import type { ActionDetail } from "@material/mwc-list";
|
||||
import {
|
||||
mdiArrowDown,
|
||||
mdiArrowUp,
|
||||
mdiContentPaste,
|
||||
mdiDrag,
|
||||
mdiPlus,
|
||||
mdiContentPaste,
|
||||
} from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import {
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
PropertyValues,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
@@ -24,10 +24,7 @@ import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import type {
|
||||
AutomationClipboard,
|
||||
Condition,
|
||||
} from "../../../../data/automation";
|
||||
import type { Condition, Clipboard } from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "./ha-automation-condition-row";
|
||||
import type HaAutomationConditionRow from "./ha-automation-condition-row";
|
||||
@@ -52,7 +49,6 @@ import "./types/ha-automation-condition-template";
|
||||
import "./types/ha-automation-condition-time";
|
||||
import "./types/ha-automation-condition-trigger";
|
||||
import "./types/ha-automation-condition-zone";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
|
||||
const PASTE_VALUE = "__paste__";
|
||||
|
||||
@@ -68,13 +64,7 @@ export default class HaAutomationCondition extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
state: true,
|
||||
subscribe: true,
|
||||
storage: "sessionStorage",
|
||||
})
|
||||
public _clipboard?: AutomationClipboard;
|
||||
@property() public clipboard?: Clipboard;
|
||||
|
||||
private _focusLastConditionOnChange = false;
|
||||
|
||||
@@ -167,6 +157,7 @@ export default class HaAutomationCondition extends LitElement {
|
||||
@move-condition=${this._move}
|
||||
@value-changed=${this._conditionChanged}
|
||||
@re-order=${this._enterReOrderMode}
|
||||
.clipboard=${this.clipboard}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
${this.reOrderMode
|
||||
@@ -215,13 +206,13 @@ export default class HaAutomationCondition extends LitElement {
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</ha-button>
|
||||
${this._clipboard?.condition
|
||||
${this.clipboard?.condition
|
||||
? html` <mwc-list-item .value=${PASTE_VALUE} graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.paste"
|
||||
)}
|
||||
(${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
|
||||
></mwc-list-item>`
|
||||
@@ -290,9 +281,7 @@ export default class HaAutomationCondition extends LitElement {
|
||||
|
||||
let conditions: Condition[];
|
||||
if (value === PASTE_VALUE) {
|
||||
conditions = this.conditions.concat(
|
||||
deepClone(this._clipboard!.condition)
|
||||
);
|
||||
conditions = this.conditions.concat(deepClone(this.clipboard!.condition));
|
||||
} else {
|
||||
const condition = value as Condition["condition"];
|
||||
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import type { LogicalCondition } from "../../../../../data/automation";
|
||||
import type {
|
||||
LogicalCondition,
|
||||
Clipboard,
|
||||
} from "../../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../ha-automation-condition";
|
||||
import type { ConditionElement } from "../ha-automation-condition-row";
|
||||
@@ -16,6 +19,8 @@ export class HaLogicalCondition extends LitElement implements ConditionElement {
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
@property() public clipboard?: Clipboard;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return {
|
||||
conditions: [],
|
||||
@@ -30,6 +35,7 @@ export class HaLogicalCondition extends LitElement implements ConditionElement {
|
||||
@value-changed=${this._valueChanged}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
.clipboard=${this.clipboard}
|
||||
.reOrderMode=${this.reOrderMode}
|
||||
></ha-automation-condition>
|
||||
`;
|
||||
|
@@ -11,19 +11,17 @@ import {
|
||||
mdiPlay,
|
||||
mdiPlayCircleOutline,
|
||||
mdiRenameBox,
|
||||
mdiRobotConfused,
|
||||
mdiStopCircleOutline,
|
||||
mdiTransitConnection,
|
||||
} from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@@ -48,7 +46,10 @@ import {
|
||||
saveAutomationConfig,
|
||||
showAutomationEditor,
|
||||
triggerAutomationActions,
|
||||
Trigger,
|
||||
Condition,
|
||||
} from "../../../data/automation";
|
||||
import { Action } from "../../../data/script";
|
||||
import { fetchEntityRegistry } from "../../../data/entity_registry";
|
||||
import {
|
||||
showAlertDialog,
|
||||
@@ -64,8 +65,6 @@ import { showAutomationModeDialog } from "./automation-mode-dialog/show-dialog-a
|
||||
import { showAutomationRenameDialog } from "./automation-rename-dialog/show-dialog-automation-rename";
|
||||
import "./blueprint-automation-editor";
|
||||
import "./manual-automation-editor";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import { validateConfig } from "../../../data/config";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@@ -80,6 +79,11 @@ declare global {
|
||||
"ui-mode-not-available": Error;
|
||||
duplicate: undefined;
|
||||
"re-order": undefined;
|
||||
"set-clipboard": {
|
||||
trigger?: Trigger;
|
||||
condition?: Condition;
|
||||
action?: Action;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,8 +114,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
|
||||
@state() private _readOnly = false;
|
||||
|
||||
@state() private _validationErrors?: (string | TemplateResult)[];
|
||||
|
||||
@query("ha-yaml-editor", true) private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
private _configSubscriptions: Record<
|
||||
@@ -297,22 +299,9 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
})}"
|
||||
@subscribe-automation-config=${this._subscribeAutomationConfig}
|
||||
>
|
||||
${this._errors || stateObj?.state === UNAVAILABLE
|
||||
? html`<ha-alert
|
||||
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}
|
||||
${this._errors
|
||||
? html`<ha-alert alert-type="error">
|
||||
${this._errors}
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
${this._mode === "gui"
|
||||
@@ -446,7 +435,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
if (changedProps.has("entityId") && this.entityId) {
|
||||
getAutomationStateConfig(this.hass, this.entityId).then((c) => {
|
||||
this._config = this._normalizeConfig(c.config);
|
||||
this._checkValidation();
|
||||
});
|
||||
this._entityId = this.entityId;
|
||||
this._dirty = false;
|
||||
@@ -475,30 +463,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
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 {
|
||||
// Normalize data: ensure trigger, action and condition are lists
|
||||
// Happens when people copy paste their automations into the config
|
||||
@@ -520,7 +484,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
this._dirty = false;
|
||||
this._readOnly = false;
|
||||
this._config = this._normalizeConfig(config);
|
||||
this._checkValidation();
|
||||
} catch (err: any) {
|
||||
const entityRegistry = await fetchEntityRegistry(this.hass.connection);
|
||||
const entity = entityRegistry.find(
|
||||
@@ -731,7 +694,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
await this._promptAutomationAlias();
|
||||
}
|
||||
|
||||
this._validationErrors = undefined;
|
||||
try {
|
||||
await saveAutomationConfig(this.hass, id, this._config!);
|
||||
} catch (errors: any) {
|
||||
|
@@ -15,7 +15,6 @@ import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { differenceInDays } from "date-fns/esm";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
|
||||
import { relativeTime } from "../../../common/datetime/relative_time";
|
||||
@@ -53,7 +52,6 @@ import { configSections } from "../ha-panel-config";
|
||||
import { showNewAutomationDialog } from "./show-dialog-new-automation";
|
||||
import { findRelated } from "../../../data/search";
|
||||
import { fetchBlueprints } from "../../../data/blueprint";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
|
||||
@customElement("ha-automation-picker")
|
||||
class HaAutomationPicker extends LitElement {
|
||||
@@ -108,15 +106,7 @@ class HaAutomationPicker extends LitElement {
|
||||
),
|
||||
type: "icon",
|
||||
template: (_, automation) =>
|
||||
html`<ha-state-icon
|
||||
.state=${automation}
|
||||
style=${styleMap({
|
||||
color:
|
||||
automation.state === UNAVAILABLE
|
||||
? "var(--error-color)"
|
||||
: "unset",
|
||||
})}
|
||||
></ha-state-icon>`,
|
||||
html`<ha-state-icon .state=${automation}></ha-state-icon>`,
|
||||
},
|
||||
name: {
|
||||
title: this.hass.localize(
|
||||
|
@@ -3,6 +3,7 @@ import { mdiHelpCircle } from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
Condition,
|
||||
ManualAutomationConfig,
|
||||
Trigger,
|
||||
Clipboard,
|
||||
} from "../../../data/automation";
|
||||
import { Action } from "../../../data/script";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
@@ -18,6 +20,7 @@ import { documentationUrl } from "../../../util/documentation-url";
|
||||
import "./action/ha-automation-action";
|
||||
import "./condition/ha-automation-condition";
|
||||
import "./trigger/ha-automation-trigger";
|
||||
import { storage } from "../../../common/decorators/storage";
|
||||
|
||||
@customElement("manual-automation-editor")
|
||||
export class HaManualAutomationEditor extends LitElement {
|
||||
@@ -33,6 +36,14 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
state: true,
|
||||
subscribe: false,
|
||||
storage: "sessionStorage",
|
||||
})
|
||||
private _clipboard: Clipboard = {};
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
${this.disabled
|
||||
@@ -91,6 +102,8 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
@value-changed=${this._triggerChanged}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
@set-clipboard=${this._setClipboard}
|
||||
.clipboard=${this._clipboard}
|
||||
></ha-automation-trigger>
|
||||
|
||||
<div class="header">
|
||||
@@ -120,6 +133,8 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
@value-changed=${this._conditionChanged}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
@set-clipboard=${this._setClipboard}
|
||||
.clipboard=${this._clipboard}
|
||||
></ha-automation-condition>
|
||||
|
||||
<div class="header">
|
||||
@@ -152,6 +167,8 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
@set-clipboard=${this._setClipboard}
|
||||
.clipboard=${this._clipboard}
|
||||
></ha-automation-action>
|
||||
`;
|
||||
}
|
||||
@@ -163,6 +180,11 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _setClipboard(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
this._clipboard = { ...this._clipboard, ...deepClone(ev.detail) };
|
||||
}
|
||||
|
||||
private _conditionChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
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 {
|
||||
mdiCheck,
|
||||
mdiContentDuplicate,
|
||||
mdiContentCopy,
|
||||
mdiContentCut,
|
||||
mdiContentDuplicate,
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
mdiIdentifier,
|
||||
@@ -15,10 +15,9 @@ import {
|
||||
mdiStopCircleOutline,
|
||||
} from "@mdi/js";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
||||
@@ -31,8 +30,7 @@ import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-textfield";
|
||||
import { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
||||
import type { AutomationClipboard } from "../../../../data/automation";
|
||||
import { Trigger, subscribeTrigger } from "../../../../data/automation";
|
||||
import { subscribeTrigger, Trigger } from "../../../../data/automation";
|
||||
import { describeTrigger } from "../../../../data/automation_i18n";
|
||||
import { validateConfig } from "../../../../data/config";
|
||||
import { fullEntitiesContext } from "../../../../data/context";
|
||||
@@ -53,7 +51,6 @@ import "./types/ha-automation-trigger-homeassistant";
|
||||
import "./types/ha-automation-trigger-mqtt";
|
||||
import "./types/ha-automation-trigger-numeric_state";
|
||||
import "./types/ha-automation-trigger-persistent_notification";
|
||||
import "./types/ha-automation-trigger-conversation";
|
||||
import "./types/ha-automation-trigger-state";
|
||||
import "./types/ha-automation-trigger-sun";
|
||||
import "./types/ha-automation-trigger-tag";
|
||||
@@ -113,14 +110,6 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
|
||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
state: false,
|
||||
subscribe: true,
|
||||
storage: "sessionStorage",
|
||||
})
|
||||
public _clipboard?: AutomationClipboard;
|
||||
|
||||
@state()
|
||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||
_entityReg!: EntityRegistryEntry[];
|
||||
@@ -480,10 +469,10 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
fireEvent(this, "duplicate");
|
||||
break;
|
||||
case 4:
|
||||
this._setClipboard();
|
||||
fireEvent(this, "set-clipboard", { trigger: this.trigger });
|
||||
break;
|
||||
case 5:
|
||||
this._setClipboard();
|
||||
fireEvent(this, "set-clipboard", { trigger: this.trigger });
|
||||
fireEvent(this, "value-changed", { value: null });
|
||||
break;
|
||||
case 6:
|
||||
@@ -503,13 +492,6 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _setClipboard() {
|
||||
this._clipboard = {
|
||||
...this._clipboard,
|
||||
trigger: this.trigger,
|
||||
};
|
||||
}
|
||||
|
||||
private _onDelete() {
|
||||
showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
|
@@ -27,7 +27,7 @@ import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-button";
|
||||
import type { HaSelect } from "../../../../components/ha-select";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { Trigger, AutomationClipboard } from "../../../../data/automation";
|
||||
import { Trigger, Clipboard } from "../../../../data/automation";
|
||||
import { TRIGGER_TYPES } from "../../../../data/trigger";
|
||||
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||
import { SortableInstance } from "../../../../resources/sortable";
|
||||
@@ -43,7 +43,6 @@ import "./types/ha-automation-trigger-homeassistant";
|
||||
import "./types/ha-automation-trigger-mqtt";
|
||||
import "./types/ha-automation-trigger-numeric_state";
|
||||
import "./types/ha-automation-trigger-persistent_notification";
|
||||
import "./types/ha-automation-trigger-conversation";
|
||||
import "./types/ha-automation-trigger-state";
|
||||
import "./types/ha-automation-trigger-sun";
|
||||
import "./types/ha-automation-trigger-tag";
|
||||
@@ -52,7 +51,6 @@ import "./types/ha-automation-trigger-time";
|
||||
import "./types/ha-automation-trigger-time_pattern";
|
||||
import "./types/ha-automation-trigger-webhook";
|
||||
import "./types/ha-automation-trigger-zone";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
|
||||
const PASTE_VALUE = "__paste__";
|
||||
|
||||
@@ -68,13 +66,7 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public reOrderMode = false;
|
||||
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
state: true,
|
||||
subscribe: true,
|
||||
storage: "sessionStorage",
|
||||
})
|
||||
public _clipboard?: AutomationClipboard;
|
||||
@property() public clipboard?: Clipboard;
|
||||
|
||||
private _focusLastTriggerOnChange = false;
|
||||
|
||||
@@ -163,13 +155,13 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</ha-button>
|
||||
${this._clipboard?.trigger
|
||||
${this.clipboard?.trigger
|
||||
? html` <mwc-list-item .value=${PASTE_VALUE} graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.paste"
|
||||
)}
|
||||
(${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
|
||||
slot="graphic"
|
||||
@@ -267,7 +259,7 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
|
||||
let triggers: Trigger[];
|
||||
if (value === PASTE_VALUE) {
|
||||
triggers = this.triggers.concat(deepClone(this._clipboard!.trigger));
|
||||
triggers = this.triggers.concat(deepClone(this.clipboard!.trigger));
|
||||
} else {
|
||||
const platform = value as Trigger["platform"];
|
||||
|
||||
|
@@ -1,191 +0,0 @@
|
||||
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";
|
||||
|
||||
const PATTERN = "^[^.。,,?¿?؟!!;;::]+$";
|
||||
|
||||
@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}
|
||||
.validationMessage=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.conversation.no_punctuation"
|
||||
)}
|
||||
autoValidate
|
||||
validateOnInitialRender
|
||||
pattern=${PATTERN}
|
||||
@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"
|
||||
)}
|
||||
.validationMessage=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.conversation.no_punctuation"
|
||||
)}
|
||||
autoValidate
|
||||
pattern=${PATTERN}
|
||||
@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 = [
|
||||
...(Array.isArray(this.trigger.command)
|
||||
? this.trigger.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;
|
||||
}
|
||||
}
|
@@ -128,7 +128,6 @@ class HaConfigBackup extends LitElement {
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage-data-table
|
||||
hasFab
|
||||
.tabs=${[
|
||||
{
|
||||
translationKey: "ui.panel.config.backup.caption",
|
||||
|
@@ -96,7 +96,6 @@ class HaBlueprintOverview extends LitElement {
|
||||
type,
|
||||
error: true,
|
||||
path,
|
||||
fullpath: `${type}/${path}`,
|
||||
});
|
||||
} else {
|
||||
result.push({
|
||||
@@ -104,7 +103,6 @@ class HaBlueprintOverview extends LitElement {
|
||||
type,
|
||||
error: false,
|
||||
path,
|
||||
fullpath: `${type}/${path}`,
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -156,10 +154,6 @@ class HaBlueprintOverview extends LitElement {
|
||||
direction: "asc",
|
||||
width: "25%",
|
||||
},
|
||||
fullpath: {
|
||||
title: "fullpath",
|
||||
hidden: true,
|
||||
},
|
||||
actions: {
|
||||
title: "",
|
||||
width: this.narrow ? undefined : "10%",
|
||||
@@ -239,7 +233,7 @@ class HaBlueprintOverview extends LitElement {
|
||||
.tabs=${configSections.automations}
|
||||
.columns=${this._columns(this.narrow, this.hass.language)}
|
||||
.data=${this._processedBlueprints(this.blueprints)}
|
||||
id="fullpath"
|
||||
id="path"
|
||||
.noDataText=${this.hass.localize(
|
||||
"ui.panel.config.blueprint.overview.no_blueprints"
|
||||
)}
|
||||
@@ -324,7 +318,7 @@ class HaBlueprintOverview extends LitElement {
|
||||
|
||||
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
|
||||
const blueprint = this._processedBlueprints(this.blueprints).find(
|
||||
(b) => b.fullpath === ev.detail.id
|
||||
(b) => b.path === ev.detail.id
|
||||
);
|
||||
if (blueprint.error) {
|
||||
showAlertDialog(this, {
|
||||
|
@@ -1,9 +1,11 @@
|
||||
import { consume } from "@lit-labs/context";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-chip";
|
||||
import "../../../../components/ha-chip-set";
|
||||
import { showAutomationEditor } from "../../../../data/automation";
|
||||
import { fullEntitiesContext } from "../../../../data/context";
|
||||
import {
|
||||
DeviceAction,
|
||||
DeviceAutomation,
|
||||
@@ -30,10 +32,12 @@ export abstract class HaDeviceAutomationCard<
|
||||
|
||||
@property({ attribute: false }) public automations: T[] = [];
|
||||
|
||||
@property({ attribute: false }) entityReg?: EntityRegistryEntry[];
|
||||
|
||||
@state() public _showSecondary = false;
|
||||
|
||||
@state()
|
||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||
_entityReg!: EntityRegistryEntry[];
|
||||
|
||||
abstract headerKey: Parameters<typeof this.hass.localize>[0];
|
||||
|
||||
abstract type: "action" | "condition" | "trigger";
|
||||
@@ -63,7 +67,7 @@ export abstract class HaDeviceAutomationCard<
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (this.automations.length === 0 || !this.entityReg) {
|
||||
if (this.automations.length === 0) {
|
||||
return nothing;
|
||||
}
|
||||
const automations = this._showSecondary
|
||||
@@ -85,7 +89,7 @@ export abstract class HaDeviceAutomationCard<
|
||||
>
|
||||
${this._localizeDeviceAutomation(
|
||||
this.hass,
|
||||
this.entityReg!,
|
||||
this._entityReg,
|
||||
automation
|
||||
)}
|
||||
</ha-chip>
|
||||
|
@@ -109,7 +109,6 @@ export class DialogDeviceAutomation extends LitElement {
|
||||
<ha-device-triggers-card
|
||||
.hass=${this.hass}
|
||||
.automations=${this._triggers}
|
||||
.entityReg=${this._params.entityReg}
|
||||
></ha-device-triggers-card>
|
||||
`
|
||||
: ""}
|
||||
@@ -118,7 +117,6 @@ export class DialogDeviceAutomation extends LitElement {
|
||||
<ha-device-conditions-card
|
||||
.hass=${this.hass}
|
||||
.automations=${this._conditions}
|
||||
.entityReg=${this._params.entityReg}
|
||||
></ha-device-conditions-card>
|
||||
`
|
||||
: ""}
|
||||
@@ -128,7 +126,6 @@ export class DialogDeviceAutomation extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.automations=${this._actions}
|
||||
.script=${this._params.script}
|
||||
.entityReg=${this._params.entityReg}
|
||||
></ha-device-actions-card>
|
||||
`
|
||||
: ""}
|
||||
|
@@ -1,10 +1,8 @@
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { DeviceRegistryEntry } from "../../../../data/device_registry";
|
||||
import { EntityRegistryEntry } from "../../../../data/entity_registry";
|
||||
|
||||
export interface DeviceAutomationDialogParams {
|
||||
device: DeviceRegistryEntry;
|
||||
entityReg: EntityRegistryEntry[];
|
||||
script?: boolean;
|
||||
}
|
||||
|
||||
|
@@ -67,9 +67,7 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
<ha-textfield
|
||||
.value=${this._nameByUser}
|
||||
@input=${this._nameChanged}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.device-registry-detail.name"
|
||||
)}
|
||||
.label=${this.hass.localize("ui.panel.config.devices.name")}
|
||||
.placeholder=${device.name || ""}
|
||||
.disabled=${this._submitting}
|
||||
dialogInitialFocus
|
||||
@@ -89,10 +87,10 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
<div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.device-registry-detail.enabled_label",
|
||||
"ui.panel.config.devices.enabled_label",
|
||||
"type",
|
||||
this.hass.localize(
|
||||
`ui.dialogs.device-registry-detail.type.${
|
||||
`ui.panel.config.devices.type.${
|
||||
device.entry_type || "device"
|
||||
}`
|
||||
)
|
||||
@@ -101,10 +99,10 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
<div class="secondary">
|
||||
${this._disabledBy && this._disabledBy !== "user"
|
||||
? this.hass.localize(
|
||||
"ui.dialogs.device-registry-detail.enabled_cause",
|
||||
"ui.panel.config.devices.enabled_cause",
|
||||
"type",
|
||||
this.hass.localize(
|
||||
`ui.dialogs.device-registry-detail.type.${
|
||||
`ui.panel.config.devices.type.${
|
||||
device.entry_type || "device"
|
||||
}`
|
||||
),
|
||||
@@ -115,7 +113,7 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
)
|
||||
: ""}
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.device-registry-detail.enabled_description"
|
||||
"ui.panel.config.devices.enabled_description"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -134,7 +132,7 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
@click=${this._updateEntry}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass.localize("ui.dialogs.device-registry-detail.update")}
|
||||
${this.hass.localize("ui.panel.config.devices.update")}
|
||||
</mwc-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
@@ -165,7 +163,7 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
} catch (err: any) {
|
||||
this._error =
|
||||
err.message ||
|
||||
this.hass.localize("ui.dialogs.device-registry-detail.unknown_error");
|
||||
this.hass.localize("ui.panel.config.devices.unknown_error");
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
|
@@ -20,7 +20,6 @@ import {
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { consume } from "@lit-labs/context";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { SENSOR_ENTITIES } from "../../../common/const";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
@@ -42,7 +41,6 @@ import {
|
||||
ConfigEntry,
|
||||
disableConfigEntry,
|
||||
DisableConfigEntryResult,
|
||||
sortConfigEntries,
|
||||
} from "../../../data/config_entries";
|
||||
import {
|
||||
computeDeviceName,
|
||||
@@ -62,7 +60,7 @@ import {
|
||||
findBatteryEntity,
|
||||
updateEntityRegistryEntry,
|
||||
} from "../../../data/entity_registry";
|
||||
import { IntegrationManifest, domainToName } from "../../../data/integration";
|
||||
import { domainToName } from "../../../data/integration";
|
||||
import { SceneEntities, showSceneEditor } from "../../../data/scene";
|
||||
import { findRelated, RelatedResult } from "../../../data/search";
|
||||
import {
|
||||
@@ -85,7 +83,6 @@ import {
|
||||
loadDeviceRegistryDetailDialog,
|
||||
showDeviceRegistryDetailDialog,
|
||||
} from "./device-registry-detail/show-dialog-device-registry-detail";
|
||||
import { fullEntitiesContext } from "../../../data/context";
|
||||
|
||||
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
|
||||
stateName?: string | null;
|
||||
@@ -118,8 +115,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public areas!: AreaRegistryEntry[];
|
||||
|
||||
@property({ attribute: false }) public manifests!: IntegrationManifest[];
|
||||
|
||||
@property() public deviceId!: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
|
||||
@@ -139,10 +134,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
|
||||
@state() private _deviceAlerts?: DeviceAlert[];
|
||||
|
||||
@state()
|
||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||
_entityReg!: EntityRegistryEntry[];
|
||||
|
||||
private _logbookTime = { recent: 86400 };
|
||||
|
||||
private _device = memoizeOne(
|
||||
@@ -154,25 +145,8 @@ export class HaConfigDevicePage extends LitElement {
|
||||
);
|
||||
|
||||
private _integrations = memoizeOne(
|
||||
(
|
||||
device: DeviceRegistryEntry,
|
||||
entries: ConfigEntry[],
|
||||
manifests: IntegrationManifest[]
|
||||
): ConfigEntry[] => {
|
||||
const entryLookup: { [entryId: string]: ConfigEntry } = {};
|
||||
for (const entry of entries) {
|
||||
entryLookup[entry.entry_id] = entry;
|
||||
}
|
||||
const manifestLookup: { [domain: string]: IntegrationManifest } = {};
|
||||
for (const manifest of manifests) {
|
||||
manifestLookup[manifest.domain] = manifest;
|
||||
}
|
||||
const deviceEntries = device.config_entries
|
||||
.filter((entId) => entId in entryLookup)
|
||||
.map((entry) => entryLookup[entry]);
|
||||
|
||||
return sortConfigEntries(deviceEntries, manifestLookup);
|
||||
}
|
||||
(device: DeviceRegistryEntry, entries: ConfigEntry[]): ConfigEntry[] =>
|
||||
entries.filter((entry) => device.config_entries.includes(entry.entry_id))
|
||||
);
|
||||
|
||||
private _entities = memoizeOne(
|
||||
@@ -311,11 +285,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
}
|
||||
|
||||
const deviceName = computeDeviceName(device, this.hass);
|
||||
const integrations = this._integrations(
|
||||
device,
|
||||
this.entries,
|
||||
this.manifests
|
||||
);
|
||||
const integrations = this._integrations(device, this.entries);
|
||||
const entities = this._entities(this.deviceId, this.entities);
|
||||
const entitiesByCategory = this._entitiesByCategory(entities);
|
||||
const batteryEntity = this._batteryEntity(entities);
|
||||
@@ -428,13 +398,12 @@ export class HaConfigDevicePage extends LitElement {
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.devices.automation.create",
|
||||
{
|
||||
type: this.hass.localize(
|
||||
`ui.panel.config.devices.type.${
|
||||
device.entry_type || "device"
|
||||
}`
|
||||
),
|
||||
}
|
||||
"type",
|
||||
this.hass.localize(
|
||||
`ui.panel.config.devices.type.${
|
||||
device.entry_type || "device"
|
||||
}`
|
||||
)
|
||||
)}
|
||||
.path=${mdiPlusCircle}
|
||||
></ha-icon-button>
|
||||
@@ -944,7 +913,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
}
|
||||
|
||||
let links = await Promise.all(
|
||||
this._integrations(device, this.entries, this.manifests).map(
|
||||
this._integrations(device, this.entries).map(
|
||||
async (entry): Promise<boolean | { link: string; domain: string }> => {
|
||||
if (entry.state !== "loaded") {
|
||||
return false;
|
||||
@@ -1007,55 +976,50 @@ export class HaConfigDevicePage extends LitElement {
|
||||
}
|
||||
|
||||
const buttons: DeviceAction[] = [];
|
||||
this._integrations(device, this.entries, this.manifests).forEach(
|
||||
(entry) => {
|
||||
if (entry.state !== "loaded" || !entry.supports_remove_device) {
|
||||
return;
|
||||
}
|
||||
buttons.push({
|
||||
action: async () => {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
text:
|
||||
this._integrations(device, this.entries, this.manifests)
|
||||
.length > 1
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.devices.confirm_delete_integration`,
|
||||
{
|
||||
integration: domainToName(
|
||||
this.hass.localize,
|
||||
entry.domain
|
||||
),
|
||||
}
|
||||
)
|
||||
: this.hass.localize(
|
||||
`ui.panel.config.devices.confirm_delete`
|
||||
),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await removeConfigEntryFromDevice(
|
||||
this.hass!,
|
||||
this.deviceId,
|
||||
entry.entry_id
|
||||
);
|
||||
},
|
||||
classes: "warning",
|
||||
icon: mdiDelete,
|
||||
label:
|
||||
this._integrations(device, this.entries, this.manifests).length > 1
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.devices.delete_device_integration`,
|
||||
{
|
||||
integration: domainToName(this.hass.localize, entry.domain),
|
||||
}
|
||||
)
|
||||
: this.hass.localize(`ui.panel.config.devices.delete_device`),
|
||||
});
|
||||
this._integrations(device, this.entries).forEach((entry) => {
|
||||
if (entry.state !== "loaded" || !entry.supports_remove_device) {
|
||||
return;
|
||||
}
|
||||
);
|
||||
buttons.push({
|
||||
action: async () => {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
text:
|
||||
this._integrations(device, this.entries).length > 1
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.devices.confirm_delete_integration`,
|
||||
{
|
||||
integration: domainToName(
|
||||
this.hass.localize,
|
||||
entry.domain
|
||||
),
|
||||
}
|
||||
)
|
||||
: this.hass.localize(`ui.panel.config.devices.confirm_delete`),
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await removeConfigEntryFromDevice(
|
||||
this.hass!,
|
||||
this.deviceId,
|
||||
entry.entry_id
|
||||
);
|
||||
},
|
||||
classes: "warning",
|
||||
icon: mdiDelete,
|
||||
label:
|
||||
this._integrations(device, this.entries).length > 1
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.devices.delete_device_integration`,
|
||||
{
|
||||
integration: domainToName(this.hass.localize, entry.domain),
|
||||
}
|
||||
)
|
||||
: this.hass.localize(`ui.panel.config.devices.delete_device`),
|
||||
});
|
||||
});
|
||||
|
||||
if (buttons.length > 0) {
|
||||
this._deleteButtons = buttons;
|
||||
@@ -1090,11 +1054,9 @@ export class HaConfigDevicePage extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
const domains = this._integrations(
|
||||
device,
|
||||
this.entries,
|
||||
this.manifests
|
||||
).map((int) => int.domain);
|
||||
const domains = this._integrations(device, this.entries).map(
|
||||
(int) => int.domain
|
||||
);
|
||||
|
||||
if (domains.includes("mqtt")) {
|
||||
const mqtt = await import(
|
||||
@@ -1134,11 +1096,9 @@ export class HaConfigDevicePage extends LitElement {
|
||||
|
||||
const deviceAlerts: DeviceAlert[] = [];
|
||||
|
||||
const domains = this._integrations(
|
||||
device,
|
||||
this.entries,
|
||||
this.manifests
|
||||
).map((int) => int.domain);
|
||||
const domains = this._integrations(device, this.entries).map(
|
||||
(int) => int.domain
|
||||
);
|
||||
|
||||
if (domains.includes("zwave_js")) {
|
||||
const zwave = await import(
|
||||
@@ -1187,7 +1147,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
private _showScriptDialog() {
|
||||
showDeviceAutomationDialog(this, {
|
||||
device: this._device(this.deviceId, this.devices)!,
|
||||
entityReg: this._entityReg,
|
||||
script: true,
|
||||
});
|
||||
}
|
||||
@@ -1195,7 +1154,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
private _showAutomationDialog() {
|
||||
showDeviceAutomationDialog(this, {
|
||||
device: this._device(this.deviceId, this.devices)!,
|
||||
entityReg: this._entityReg,
|
||||
script: false,
|
||||
});
|
||||
}
|
||||
|
@@ -25,7 +25,7 @@ import "../../../components/ha-check-list-item";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-icon-button";
|
||||
import { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import { ConfigEntry, sortConfigEntries } from "../../../data/config_entries";
|
||||
import { ConfigEntry } from "../../../data/config_entries";
|
||||
import {
|
||||
computeDeviceName,
|
||||
DeviceEntityLookup,
|
||||
@@ -36,7 +36,7 @@ import {
|
||||
findBatteryChargingEntity,
|
||||
findBatteryEntity,
|
||||
} from "../../../data/entity_registry";
|
||||
import { IntegrationManifest, domainToName } from "../../../data/integration";
|
||||
import { domainToName } from "../../../data/integration";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
@@ -68,8 +68,6 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
|
||||
@property() public areas!: AreaRegistryEntry[];
|
||||
|
||||
@property() public manifests!: IntegrationManifest[];
|
||||
|
||||
@property() public route!: Route;
|
||||
|
||||
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||
@@ -151,7 +149,6 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
entries: ConfigEntry[],
|
||||
entities: EntityRegistryEntry[],
|
||||
areas: AreaRegistryEntry[],
|
||||
manifests: IntegrationManifest[],
|
||||
filters: URLSearchParams,
|
||||
showDisabled: boolean,
|
||||
localize: LocalizeFunc
|
||||
@@ -189,11 +186,6 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
areaLookup[area.area_id] = area;
|
||||
}
|
||||
|
||||
const manifestLookup: { [domain: string]: IntegrationManifest } = {};
|
||||
for (const manifest of manifests) {
|
||||
manifestLookup[manifest.domain] = manifest;
|
||||
}
|
||||
|
||||
let filterConfigEntry: ConfigEntry | undefined;
|
||||
|
||||
const filteredDomains = new Set<string>();
|
||||
@@ -225,51 +217,47 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
outputDevices = outputDevices.filter((device) => !device.disabled_by);
|
||||
}
|
||||
|
||||
outputDevices = outputDevices.map((device) => {
|
||||
const deviceEntries = sortConfigEntries(
|
||||
device.config_entries
|
||||
.filter((entId) => entId in entryLookup)
|
||||
.map((entId) => entryLookup[entId]),
|
||||
manifestLookup
|
||||
);
|
||||
return {
|
||||
...device,
|
||||
name: computeDeviceName(
|
||||
device,
|
||||
this.hass,
|
||||
deviceEntityLookup[device.id]
|
||||
),
|
||||
model:
|
||||
device.model ||
|
||||
`<${localize("ui.panel.config.devices.data_table.unknown")}>`,
|
||||
manufacturer:
|
||||
device.manufacturer ||
|
||||
`<${localize("ui.panel.config.devices.data_table.unknown")}>`,
|
||||
area:
|
||||
device.area_id && areaLookup[device.area_id]
|
||||
? areaLookup[device.area_id].name
|
||||
: "—",
|
||||
integration: deviceEntries.length
|
||||
? deviceEntries
|
||||
.map(
|
||||
(entry) =>
|
||||
localize(`component.${entry.domain}.title`) || entry.domain
|
||||
)
|
||||
.join(", ")
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.devices.data_table.no_integration"
|
||||
),
|
||||
domains: deviceEntries.map((entry) => entry.domain),
|
||||
battery_entity: [
|
||||
this._batteryEntity(device.id, deviceEntityLookup),
|
||||
this._batteryChargingEntity(device.id, deviceEntityLookup),
|
||||
],
|
||||
battery_level:
|
||||
this.hass.states[
|
||||
this._batteryEntity(device.id, deviceEntityLookup) || ""
|
||||
]?.state,
|
||||
};
|
||||
});
|
||||
outputDevices = outputDevices.map((device) => ({
|
||||
...device,
|
||||
name: computeDeviceName(
|
||||
device,
|
||||
this.hass,
|
||||
deviceEntityLookup[device.id]
|
||||
),
|
||||
model:
|
||||
device.model ||
|
||||
`<${localize("ui.panel.config.devices.data_table.unknown")}>`,
|
||||
manufacturer:
|
||||
device.manufacturer ||
|
||||
`<${localize("ui.panel.config.devices.data_table.unknown")}>`,
|
||||
area:
|
||||
device.area_id && areaLookup[device.area_id]
|
||||
? areaLookup[device.area_id].name
|
||||
: "—",
|
||||
integration: device.config_entries.length
|
||||
? device.config_entries
|
||||
.filter((entId) => entId in entryLookup)
|
||||
.map(
|
||||
(entId) =>
|
||||
localize(`component.${entryLookup[entId].domain}.title`) ||
|
||||
entryLookup[entId].domain
|
||||
)
|
||||
.join(", ")
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.devices.data_table.no_integration"
|
||||
),
|
||||
domains: device.config_entries
|
||||
.filter((entId) => entId in entryLookup)
|
||||
.map((entId) => entryLookup[entId].domain),
|
||||
battery_entity: [
|
||||
this._batteryEntity(device.id, deviceEntityLookup),
|
||||
this._batteryChargingEntity(device.id, deviceEntityLookup),
|
||||
],
|
||||
battery_level:
|
||||
this.hass.states[
|
||||
this._batteryEntity(device.id, deviceEntityLookup) || ""
|
||||
]?.state,
|
||||
}));
|
||||
|
||||
this._numHiddenDevices = startLength - outputDevices.length;
|
||||
return {
|
||||
@@ -441,7 +429,6 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
this.entries,
|
||||
this.entities,
|
||||
this.areas,
|
||||
this.manifests,
|
||||
this._searchParms,
|
||||
this._showDisabled,
|
||||
this.hass.localize
|
||||
@@ -578,7 +565,6 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
this.entries,
|
||||
this.entities,
|
||||
this.areas,
|
||||
this.manifests,
|
||||
this._searchParms,
|
||||
this._showDisabled,
|
||||
this.hass.localize
|
||||
|
@@ -14,10 +14,6 @@ import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../../data/entity_registry";
|
||||
import {
|
||||
IntegrationManifest,
|
||||
fetchIntegrationManifests,
|
||||
} from "../../../data/integration";
|
||||
import {
|
||||
HassRouterPage,
|
||||
RouterOptions,
|
||||
@@ -51,8 +47,6 @@ class HaConfigDevices extends HassRouterPage {
|
||||
|
||||
@state() private _configEntries: ConfigEntry[] = [];
|
||||
|
||||
@state() private _manifests: IntegrationManifest[] = [];
|
||||
|
||||
@state()
|
||||
private _entityRegistryEntries: EntityRegistryEntry[] = [];
|
||||
|
||||
@@ -105,7 +99,6 @@ class HaConfigDevices extends HassRouterPage {
|
||||
|
||||
pageEl.entities = this._entityRegistryEntries;
|
||||
pageEl.entries = this._configEntries;
|
||||
pageEl.manifests = this._manifests;
|
||||
pageEl.devices = this._deviceRegistryEntries;
|
||||
pageEl.areas = this._areas;
|
||||
pageEl.narrow = this.narrow;
|
||||
@@ -118,10 +111,6 @@ class HaConfigDevices extends HassRouterPage {
|
||||
getConfigEntries(this.hass).then((configEntries) => {
|
||||
this._configEntries = configEntries;
|
||||
});
|
||||
fetchIntegrationManifests(this.hass).then((manifests) => {
|
||||
this._manifests = manifests;
|
||||
});
|
||||
|
||||
if (this._unsubs) {
|
||||
return;
|
||||
}
|
||||
|
@@ -11,12 +11,10 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { mdiContentCopy } from "@mdi/js";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeObjectId } from "../../../common/entity/compute_object_id";
|
||||
import { domainIcon } from "../../../common/entity/domain_icon";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { formatNumber } from "../../../common/number/format_number";
|
||||
@@ -81,8 +79,6 @@ import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
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 = {
|
||||
cover: [
|
||||
@@ -329,6 +325,8 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
|
||||
const domain = computeDomain(this.entry.entity_id);
|
||||
|
||||
const invalidDomainUpdate = computeDomain(this._entityId.trim()) !== domain;
|
||||
|
||||
const invalidDefaultCode =
|
||||
domain === "lock" &&
|
||||
this._isInvalidDefaultCode(
|
||||
@@ -677,23 +675,15 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
<ha-textfield
|
||||
class="entityId"
|
||||
.value=${computeObjectId(this._entityId)}
|
||||
.prefix=${domain + "."}
|
||||
error-message="Domain needs to stay the same"
|
||||
.value=${this._entityId}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.entity_id"
|
||||
)}
|
||||
.invalid=${invalidDomainUpdate}
|
||||
.disabled=${this.disabled}
|
||||
required
|
||||
@input=${this._entityIdChanged}
|
||||
iconTrailing
|
||||
>
|
||||
<ha-icon-button
|
||||
@click=${this._copyEntityId}
|
||||
slot="trailingIcon"
|
||||
.path=${mdiContentCopy}
|
||||
></ha-icon-button>
|
||||
</ha-textfield>
|
||||
></ha-textfield>
|
||||
${!this.entry.device_id
|
||||
? html`<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
@@ -798,11 +788,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._handleVoiceAssistantsClicked}
|
||||
>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.voice_assistants"
|
||||
)}</span
|
||||
>
|
||||
<span>Voice assistants</span>
|
||||
<span slot="secondary">
|
||||
${this.entry.aliases.length
|
||||
? [...this.entry.aliases]
|
||||
@@ -1175,16 +1161,9 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
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 {
|
||||
fireEvent(this, "change");
|
||||
this._entityId = `${computeDomain(this._origEntityId)}.${ev.target.value}`;
|
||||
this._entityId = ev.target.value;
|
||||
}
|
||||
|
||||
private _deviceClassChanged(ev): void {
|
||||
@@ -1324,10 +1303,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
}
|
||||
|
||||
private _handleVoiceAssistantsClicked() {
|
||||
showVoiceAssistantsView(
|
||||
this,
|
||||
this.hass.localize("ui.dialogs.entity_registry.editor.voice_assistants")
|
||||
);
|
||||
showVoiceAssistantsView(this, "Voice assistants");
|
||||
}
|
||||
|
||||
private async _showOptionsFlow() {
|
||||
@@ -1367,20 +1343,6 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
:host {
|
||||
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 {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user