mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-27 03:37:31 +00:00
Compare commits
77 Commits
shortcut-n
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9001cd3e65 | ||
|
|
ca8923d8f4 | ||
|
|
e7e4407a09 | ||
|
|
3f0c9538bd | ||
|
|
5c3ccbfdad | ||
|
|
9710142c47 | ||
|
|
57640d17cd | ||
|
|
b5d93e7515 | ||
|
|
ca9b29d82a | ||
|
|
51efa4f61f | ||
|
|
a6c71719d1 | ||
|
|
ccc48d158a | ||
|
|
b11e787f09 | ||
|
|
cdb6562de8 | ||
|
|
d8e8c9aa02 | ||
|
|
be392be1e6 | ||
|
|
10dc432445 | ||
|
|
19187f887d | ||
|
|
dc76a42aaa | ||
|
|
1f2b8047a6 | ||
|
|
e8c9ed0528 | ||
|
|
c7ae78c02f | ||
|
|
dc8f1211e6 | ||
|
|
5c25a63ea5 | ||
|
|
c1787ab994 | ||
|
|
6fea535fdc | ||
|
|
e8cee84380 | ||
|
|
b4613edeb7 | ||
|
|
a8b6e5aa3d | ||
|
|
e842193cd6 | ||
|
|
bb0813333d | ||
|
|
ab4c6f80f4 | ||
|
|
89796e425a | ||
|
|
9c42c8bbc4 | ||
|
|
616237caee | ||
|
|
2d36a0d37f | ||
|
|
1ec432a20f | ||
|
|
afd91b2261 | ||
|
|
cdfb7f914f | ||
|
|
33b0897522 | ||
|
|
5f0cf1b522 | ||
|
|
afb2ad95a4 | ||
|
|
27beab3133 | ||
|
|
435c82489b | ||
|
|
3ba6bf272e | ||
|
|
eec99b2fa3 | ||
|
|
d23e45e410 | ||
|
|
3c82d12609 | ||
|
|
15d67997e7 | ||
|
|
a6dfcb3100 | ||
|
|
26c2369228 | ||
|
|
2eed446492 | ||
|
|
7ebdeab6b2 | ||
|
|
0c35278f51 | ||
|
|
561122f03d | ||
|
|
95311be034 | ||
|
|
1eda44a102 | ||
|
|
d76781eb91 | ||
|
|
82d44e051f | ||
|
|
fdc9f5a3b7 | ||
|
|
ee6c82aba9 | ||
|
|
11d3f5c2ba | ||
|
|
feb68ce373 | ||
|
|
7f9a9de157 | ||
|
|
8e1b6a3d3b | ||
|
|
6e6e5a53e2 | ||
|
|
0408734ec5 | ||
|
|
317519fc08 | ||
|
|
843d79eab4 | ||
|
|
165a757f06 | ||
|
|
ea8b730142 | ||
|
|
e88c97d625 | ||
|
|
7560988b76 | ||
|
|
eecd8077b6 | ||
|
|
cbab5c3f7b | ||
|
|
a5d27c8bb8 | ||
|
|
a6a340b5db |
@@ -18,7 +18,6 @@ import { HaEventTrigger } from "../../../../src/panels/config/automation/trigger
|
||||
import { HaGeolocationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location";
|
||||
import { HaHassTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-homeassistant";
|
||||
import { HaTriggerList } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-list";
|
||||
import { HaMQTTTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt";
|
||||
import { HaNumericStateTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state";
|
||||
import { HaPersistentNotificationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-persistent_notification";
|
||||
import { HaStateTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-state";
|
||||
@@ -38,11 +37,6 @@ const SCHEMAS: { name: string; triggers: Trigger[] }[] = [
|
||||
triggers: [{ ...HaStateTrigger.defaultConfig }],
|
||||
},
|
||||
|
||||
{
|
||||
name: "MQTT",
|
||||
triggers: [{ ...HaMQTTTrigger.defaultConfig }],
|
||||
},
|
||||
|
||||
{
|
||||
name: "GeoLocation",
|
||||
triggers: [{ ...HaGeolocationTrigger.defaultConfig }],
|
||||
|
||||
@@ -194,7 +194,7 @@
|
||||
"eslint-plugin-wc": "3.0.2",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.3.2",
|
||||
"glob": "12.0.0",
|
||||
"glob": "13.0.0",
|
||||
"gulp": "5.0.1",
|
||||
"gulp-brotli": "3.0.0",
|
||||
"gulp-json-transform": "0.5.0",
|
||||
|
||||
@@ -114,12 +114,6 @@ export interface StateTrigger extends BaseTrigger {
|
||||
for?: string | number | ForDict;
|
||||
}
|
||||
|
||||
export interface MqttTrigger extends BaseTrigger {
|
||||
trigger: "mqtt";
|
||||
topic: string;
|
||||
payload?: string;
|
||||
}
|
||||
|
||||
export interface GeoLocationTrigger extends BaseTrigger {
|
||||
trigger: "geo_location";
|
||||
source: string;
|
||||
@@ -127,6 +121,12 @@ export interface GeoLocationTrigger extends BaseTrigger {
|
||||
event: "enter" | "leave";
|
||||
}
|
||||
|
||||
export interface MqttTrigger extends BaseTrigger {
|
||||
trigger: "mqtt";
|
||||
topic: string;
|
||||
payload?: string;
|
||||
}
|
||||
|
||||
export interface HassTrigger extends BaseTrigger {
|
||||
trigger: "homeassistant";
|
||||
event: "start" | "shutdown";
|
||||
|
||||
14
src/data/esphome.ts
Normal file
14
src/data/esphome.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export interface ESPHomeEncryptionKey {
|
||||
encryption_key: string;
|
||||
}
|
||||
|
||||
export const fetchESPHomeEncryptionKey = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string
|
||||
): Promise<ESPHomeEncryptionKey> =>
|
||||
hass.callWS({
|
||||
type: "esphome/get_encryption_key",
|
||||
entry_id,
|
||||
});
|
||||
@@ -40,7 +40,6 @@ export const TRIGGER_COLLECTIONS: AutomationElementGroupCollection[] = [
|
||||
event: {},
|
||||
geo_location: {},
|
||||
homeassistant: {},
|
||||
mqtt: {},
|
||||
conversation: {},
|
||||
tag: {},
|
||||
template: {},
|
||||
|
||||
@@ -63,11 +63,6 @@ const _SHORTCUTS: Section[] = [
|
||||
descriptionTranslationKey:
|
||||
"ui.dialogs.shortcuts.searching.search_in_table",
|
||||
},
|
||||
{
|
||||
shortcut: ["N"],
|
||||
descriptionTranslationKey:
|
||||
"ui.dialogs.shortcuts.searching.new_in_table",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -190,21 +190,6 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
|
||||
};
|
||||
}
|
||||
|
||||
protected supportedSingleKeyShortcuts(): SupportedShortcuts {
|
||||
if (this.hasFab) {
|
||||
return {
|
||||
n: () => {
|
||||
const fab = this.querySelector<HTMLElement>('[slot="fab"]');
|
||||
if (fab) {
|
||||
fab.click();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
private _showPaneController = new ResizeController(this, {
|
||||
callback: (entries) => entries[0]?.contentRect.width > 750,
|
||||
});
|
||||
|
||||
@@ -114,7 +114,6 @@ import {
|
||||
} from "../../../data/trigger";
|
||||
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
|
||||
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { isMac } from "../../../util/is_mac";
|
||||
import { showToast } from "../../../util/toast";
|
||||
@@ -168,7 +167,7 @@ const DYNAMIC_KEYWORDS = ["dynamicGroups", "helpers", "other"];
|
||||
|
||||
@customElement("add-automation-element-dialog")
|
||||
class DialogAddAutomationElement
|
||||
extends KeyboardShortcutMixin(SubscribeMixin(LitElement))
|
||||
extends KeyboardShortcutMixin(LitElement)
|
||||
implements HassDialog
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -233,6 +232,8 @@ class DialogAddAutomationElement
|
||||
|
||||
private _unsub?: Promise<UnsubscribeFunc>;
|
||||
|
||||
private _unsubscribeLabFeatures?: UnsubscribeFunc;
|
||||
|
||||
private _configEntryLookup: Record<string, ConfigEntry> = {};
|
||||
|
||||
// #endregion variables
|
||||
@@ -246,11 +247,47 @@ class DialogAddAutomationElement
|
||||
) {
|
||||
this._calculateUsedDomains();
|
||||
}
|
||||
|
||||
if (changedProps.has("_newTriggersAndConditions")) {
|
||||
this._subscribeDescriptions();
|
||||
}
|
||||
}
|
||||
|
||||
public hassSubscribe() {
|
||||
return [
|
||||
subscribeLabFeatures(this.hass!.connection, (features) => {
|
||||
private _subscribeDescriptions() {
|
||||
this._unsubscribe();
|
||||
if (this._params?.type === "trigger") {
|
||||
this._triggerDescriptions = {};
|
||||
this._unsub = subscribeTriggers(this.hass, (triggers) => {
|
||||
this._triggerDescriptions = {
|
||||
...this._triggerDescriptions,
|
||||
...triggers,
|
||||
};
|
||||
});
|
||||
} else if (this._params?.type === "condition") {
|
||||
this._conditionDescriptions = {};
|
||||
this._unsub = subscribeConditions(this.hass, (conditions) => {
|
||||
this._conditionDescriptions = {
|
||||
...this._conditionDescriptions,
|
||||
...conditions,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public showDialog(params): void {
|
||||
this._params = params;
|
||||
|
||||
this.addKeyboardShortcuts();
|
||||
|
||||
this._loadConfigEntries();
|
||||
|
||||
this._unsubscribe();
|
||||
this._fetchManifests();
|
||||
this._calculateUsedDomains();
|
||||
|
||||
this._unsubscribeLabFeatures = subscribeLabFeatures(
|
||||
this.hass.connection,
|
||||
(features) => {
|
||||
this._newTriggersAndConditions =
|
||||
features.find(
|
||||
(feature) =>
|
||||
@@ -261,25 +298,8 @@ class DialogAddAutomationElement
|
||||
this._newTriggersAndConditions && this._params?.type !== "condition"
|
||||
? "targets"
|
||||
: "groups";
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public showDialog(params): void {
|
||||
this._params = params;
|
||||
|
||||
this._tab =
|
||||
this._newTriggersAndConditions && this._params?.type !== "condition"
|
||||
? "targets"
|
||||
: "groups";
|
||||
|
||||
this.addKeyboardShortcuts();
|
||||
|
||||
this._loadConfigEntries();
|
||||
|
||||
this._unsubscribe();
|
||||
this._fetchManifests();
|
||||
this._calculateUsedDomains();
|
||||
}
|
||||
);
|
||||
|
||||
if (this._params?.type === "action") {
|
||||
this.hass.loadBackendTranslation("services");
|
||||
@@ -287,21 +307,11 @@ class DialogAddAutomationElement
|
||||
} else if (this._params?.type === "trigger") {
|
||||
this.hass.loadBackendTranslation("triggers");
|
||||
getTriggerIcons(this.hass);
|
||||
this._unsub = subscribeTriggers(this.hass, (triggers) => {
|
||||
this._triggerDescriptions = {
|
||||
...this._triggerDescriptions,
|
||||
...triggers,
|
||||
};
|
||||
});
|
||||
this._subscribeDescriptions();
|
||||
} else if (this._params?.type === "condition") {
|
||||
this.hass.loadBackendTranslation("conditions");
|
||||
getConditionIcons(this.hass);
|
||||
this._unsub = subscribeConditions(this.hass, (conditions) => {
|
||||
this._conditionDescriptions = {
|
||||
...this._conditionDescriptions,
|
||||
...conditions,
|
||||
};
|
||||
});
|
||||
this._subscribeDescriptions();
|
||||
}
|
||||
|
||||
window.addEventListener("resize", this._updateNarrow);
|
||||
@@ -379,6 +389,10 @@ class DialogAddAutomationElement
|
||||
this._unsub.then((unsub) => unsub());
|
||||
this._unsub = undefined;
|
||||
}
|
||||
if (this._unsubscribeLabFeatures) {
|
||||
this._unsubscribeLabFeatures();
|
||||
this._unsubscribeLabFeatures = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// #endregion lifecycle
|
||||
@@ -426,10 +440,7 @@ class DialogAddAutomationElement
|
||||
},
|
||||
];
|
||||
|
||||
if (
|
||||
this._newTriggersAndConditions &&
|
||||
automationElementType !== "condition"
|
||||
) {
|
||||
if (this._newTriggersAndConditions) {
|
||||
tabButtons.unshift({
|
||||
label: this.hass.localize(`ui.panel.config.automation.editor.targets`),
|
||||
value: "targets",
|
||||
@@ -519,8 +530,7 @@ class DialogAddAutomationElement
|
||||
this._manifests
|
||||
)}
|
||||
.convertToItem=${this._convertToItem}
|
||||
.newTriggersAndConditions=${this._newTriggersAndConditions &&
|
||||
automationElementType !== "condition"}
|
||||
.newTriggersAndConditions=${this._newTriggersAndConditions}
|
||||
@search-element-picked=${this._searchItemSelected}
|
||||
>
|
||||
</ha-automation-add-search>`
|
||||
@@ -599,7 +609,7 @@ class DialogAddAutomationElement
|
||||
: nothing}
|
||||
${collections.map(
|
||||
(collection, index) => html`
|
||||
${collection.titleKey
|
||||
${collection.titleKey && collection.groups.length
|
||||
? html`<ha-section-title>
|
||||
${this.hass.localize(collection.titleKey)}
|
||||
</ha-section-title>`
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import type { HassServiceTarget } from "home-assistant-js-websocket";
|
||||
import type {
|
||||
HassServiceTarget,
|
||||
UnsubscribeFunc,
|
||||
} from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, queryAll, state } from "lit/decorators";
|
||||
@@ -25,6 +28,7 @@ import {
|
||||
CONDITION_BUILDING_BLOCKS,
|
||||
subscribeConditions,
|
||||
} from "../../../../data/condition";
|
||||
import { subscribeLabFeatures } from "../../../../data/labs";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
@@ -74,19 +78,52 @@ export default class HaAutomationCondition extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _conditionKeys = new WeakMap<Condition, string>();
|
||||
|
||||
private _unsub?: Promise<UnsubscribeFunc>;
|
||||
|
||||
// @ts-ignore
|
||||
@state() private _newTriggersAndConditions = false;
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._unsubscribe();
|
||||
}
|
||||
|
||||
protected hassSubscribe() {
|
||||
return [
|
||||
subscribeConditions(this.hass, (conditions) =>
|
||||
this._addConditions(conditions)
|
||||
),
|
||||
subscribeLabFeatures(this.hass!.connection, (features) => {
|
||||
this._newTriggersAndConditions =
|
||||
features.find(
|
||||
(feature) =>
|
||||
feature.domain === "automation" &&
|
||||
feature.preview_feature === "new_triggers_conditions"
|
||||
)?.enabled ?? false;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
private _addConditions(conditions: ConditionDescriptions) {
|
||||
this._conditionDescriptions = {
|
||||
...this._conditionDescriptions,
|
||||
...conditions,
|
||||
};
|
||||
private _subscribeDescriptions() {
|
||||
this._unsubscribe();
|
||||
this._conditionDescriptions = {};
|
||||
this._unsub = subscribeConditions(this.hass, (descriptions) => {
|
||||
this._conditionDescriptions = {
|
||||
...this._conditionDescriptions,
|
||||
...descriptions,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private _unsubscribe() {
|
||||
if (this._unsub) {
|
||||
this._unsub.then((unsub) => unsub());
|
||||
this._unsub = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues): void {
|
||||
super.willUpdate(changedProperties);
|
||||
if (changedProperties.has("_newTriggersAndConditions")) {
|
||||
this._subscribeDescriptions();
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
|
||||
@@ -69,6 +69,45 @@ export class HaPlatformCondition extends LitElement {
|
||||
} else {
|
||||
this._manifest = undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
oldValue?.condition !== this.condition?.condition &&
|
||||
this.condition &&
|
||||
this.description?.fields
|
||||
) {
|
||||
let updatedDefaultValue = false;
|
||||
const updatedOptions = {};
|
||||
const loadDefaults = !("options" in this.condition);
|
||||
// Set mandatory bools without a default value to false
|
||||
Object.entries(this.description.fields).forEach(([key, field]) => {
|
||||
if (
|
||||
field.selector &&
|
||||
field.required &&
|
||||
field.default === undefined &&
|
||||
"boolean" in field.selector &&
|
||||
updatedOptions[key] === undefined
|
||||
) {
|
||||
updatedDefaultValue = true;
|
||||
updatedOptions[key] = false;
|
||||
} else if (
|
||||
loadDefaults &&
|
||||
field.selector &&
|
||||
field.default !== undefined &&
|
||||
updatedOptions[key] === undefined
|
||||
) {
|
||||
updatedDefaultValue = true;
|
||||
updatedOptions[key] = field.default;
|
||||
}
|
||||
});
|
||||
if (updatedDefaultValue) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.condition,
|
||||
options: updatedOptions,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
|
||||
@@ -72,7 +72,6 @@ import "./types/ha-automation-trigger-event";
|
||||
import "./types/ha-automation-trigger-geo_location";
|
||||
import "./types/ha-automation-trigger-homeassistant";
|
||||
import "./types/ha-automation-trigger-list";
|
||||
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-platform";
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import type { HassServiceTarget } from "home-assistant-js-websocket";
|
||||
import type {
|
||||
HassServiceTarget,
|
||||
UnsubscribeFunc,
|
||||
} from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@@ -21,6 +24,7 @@ import {
|
||||
type Trigger,
|
||||
type TriggerList,
|
||||
} from "../../../../data/automation";
|
||||
import { subscribeLabFeatures } from "../../../../data/labs";
|
||||
import type { TriggerDescriptions } from "../../../../data/trigger";
|
||||
import { isTriggerList, subscribeTriggers } from "../../../../data/trigger";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
@@ -67,16 +71,54 @@ export default class HaAutomationTrigger extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _triggerKeys = new WeakMap<Trigger, string>();
|
||||
|
||||
private _unsub?: Promise<UnsubscribeFunc>;
|
||||
|
||||
@state() private _triggerDescriptions: TriggerDescriptions = {};
|
||||
|
||||
// @ts-ignore
|
||||
@state() private _newTriggersAndConditions = false;
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._unsubscribe();
|
||||
}
|
||||
|
||||
protected hassSubscribe() {
|
||||
return [
|
||||
subscribeTriggers(this.hass, (triggers) => this._addTriggers(triggers)),
|
||||
subscribeLabFeatures(this.hass!.connection, (features) => {
|
||||
this._newTriggersAndConditions =
|
||||
features.find(
|
||||
(feature) =>
|
||||
feature.domain === "automation" &&
|
||||
feature.preview_feature === "new_triggers_conditions"
|
||||
)?.enabled ?? false;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
private _addTriggers(triggers: TriggerDescriptions) {
|
||||
this._triggerDescriptions = { ...this._triggerDescriptions, ...triggers };
|
||||
private _subscribeDescriptions() {
|
||||
this._unsubscribe();
|
||||
this._triggerDescriptions = {};
|
||||
this._unsub = subscribeTriggers(this.hass, (descriptions) => {
|
||||
this._triggerDescriptions = {
|
||||
...this._triggerDescriptions,
|
||||
...descriptions,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private _unsubscribe() {
|
||||
if (this._unsub) {
|
||||
this._unsub.then((unsub) => unsub());
|
||||
this._unsub = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues): void {
|
||||
super.willUpdate(changedProperties);
|
||||
if (changedProperties.has("_newTriggersAndConditions")) {
|
||||
this._subscribeDescriptions();
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../../components/ha-form/types";
|
||||
import type { MqttTrigger } from "../../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type { TriggerElement } from "../ha-automation-trigger-row";
|
||||
|
||||
const SCHEMA = [
|
||||
{ name: "topic", required: true, selector: { text: {} } },
|
||||
{ name: "payload", selector: { text: {} } },
|
||||
] as const;
|
||||
|
||||
@customElement("ha-automation-trigger-mqtt")
|
||||
export class HaMQTTTrigger extends LitElement implements TriggerElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public trigger!: MqttTrigger;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
public static get defaultConfig(): MqttTrigger {
|
||||
return { trigger: "mqtt", topic: "" };
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-form
|
||||
.schema=${SCHEMA}
|
||||
.data=${this.trigger}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const newTrigger = ev.detail.value;
|
||||
fireEvent(this, "value-changed", { value: newTrigger });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<typeof SCHEMA>
|
||||
): string =>
|
||||
this.hass.localize(
|
||||
`ui.panel.config.automation.editor.triggers.type.mqtt.${schema.name}`
|
||||
);
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-trigger-mqtt": HaMQTTTrigger;
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,16 @@ const showOptionalToggle = (field: TriggerDescription["fields"][string]) =>
|
||||
!field.required &&
|
||||
!("boolean" in field.selector && field.default);
|
||||
|
||||
const DEFAULT_KEYS: (keyof PlatformTrigger)[] = [
|
||||
"trigger",
|
||||
"target",
|
||||
"alias",
|
||||
"id",
|
||||
"variables",
|
||||
"enabled",
|
||||
"options",
|
||||
] as const;
|
||||
|
||||
@customElement("ha-automation-trigger-platform")
|
||||
export class HaPlatformTrigger extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -52,6 +62,31 @@ export class HaPlatformTrigger extends LitElement {
|
||||
if (!changedProperties.has("trigger")) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newValue: PlatformTrigger | undefined;
|
||||
|
||||
for (const key in this.trigger) {
|
||||
// Migrate old options to `options`
|
||||
if (DEFAULT_KEYS.includes(key as keyof PlatformTrigger)) {
|
||||
continue;
|
||||
}
|
||||
if (newValue === undefined) {
|
||||
newValue = {
|
||||
...this.trigger,
|
||||
options: { [key]: this.trigger[key] },
|
||||
};
|
||||
} else {
|
||||
newValue.options![key] = this.trigger[key];
|
||||
}
|
||||
delete newValue[key];
|
||||
}
|
||||
if (newValue !== undefined) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
});
|
||||
this.trigger = newValue;
|
||||
}
|
||||
|
||||
const oldValue = changedProperties.get("trigger") as
|
||||
| undefined
|
||||
| this["trigger"];
|
||||
@@ -69,6 +104,46 @@ export class HaPlatformTrigger extends LitElement {
|
||||
} else {
|
||||
this._manifest = undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
oldValue?.trigger !== this.trigger?.trigger &&
|
||||
this.trigger &&
|
||||
this.description?.fields
|
||||
) {
|
||||
let updatedDefaultValue = false;
|
||||
const updatedOptions = {};
|
||||
const loadDefaults = !("options" in this.trigger);
|
||||
// Set mandatory bools without a default value to false
|
||||
Object.entries(this.description.fields).forEach(([key, field]) => {
|
||||
if (
|
||||
field.selector &&
|
||||
field.required &&
|
||||
field.default === undefined &&
|
||||
"boolean" in field.selector &&
|
||||
updatedOptions[key] === undefined
|
||||
) {
|
||||
updatedDefaultValue = true;
|
||||
updatedOptions[key] = false;
|
||||
} else if (
|
||||
loadDefaults &&
|
||||
field.selector &&
|
||||
field.default !== undefined &&
|
||||
updatedOptions[key] === undefined
|
||||
) {
|
||||
updatedDefaultValue = true;
|
||||
updatedOptions[key] = field.default;
|
||||
}
|
||||
});
|
||||
|
||||
if (updatedDefaultValue) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.trigger,
|
||||
options: updatedOptions,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { mdiKey } from "@mdi/js";
|
||||
import { getConfigEntries } from "../../../../../../data/config_entries";
|
||||
import type { DeviceRegistryEntry } from "../../../../../../data/device_registry";
|
||||
import { fetchESPHomeEncryptionKey } from "../../../../../../data/esphome";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { showESPHomeEncryptionKeyDialog } from "../../../../integrations/integration-panels/esphome/show-dialog-esphome-encryption-key";
|
||||
import type { DeviceAction } from "../../../ha-config-device-page";
|
||||
|
||||
export const getESPHomeDeviceActions = async (
|
||||
el: HTMLElement,
|
||||
hass: HomeAssistant,
|
||||
device: DeviceRegistryEntry
|
||||
): Promise<DeviceAction[]> => {
|
||||
const actions: DeviceAction[] = [];
|
||||
|
||||
const configEntries = await getConfigEntries(hass, {
|
||||
domain: "esphome",
|
||||
});
|
||||
|
||||
const configEntry = configEntries.find((entry) =>
|
||||
device.config_entries.includes(entry.entry_id)
|
||||
);
|
||||
|
||||
if (!configEntry) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const entryId = configEntry.entry_id;
|
||||
|
||||
try {
|
||||
const encryptionKey = await fetchESPHomeEncryptionKey(hass, entryId);
|
||||
|
||||
if (encryptionKey.encryption_key) {
|
||||
actions.push({
|
||||
label: hass.localize(
|
||||
"ui.panel.config.devices.esphome.show_encryption_key"
|
||||
),
|
||||
icon: mdiKey,
|
||||
action: () =>
|
||||
showESPHomeEncryptionKeyDialog(el, {
|
||||
entry_id: entryId,
|
||||
encryption_key: encryptionKey.encryption_key,
|
||||
}),
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to fetch ESPHome encryption key:", err);
|
||||
}
|
||||
|
||||
return actions;
|
||||
};
|
||||
@@ -1162,6 +1162,17 @@ export class HaConfigDevicePage extends LitElement {
|
||||
);
|
||||
deviceActions.push(...actions);
|
||||
}
|
||||
if (domains.includes("esphome")) {
|
||||
const esphome = await import(
|
||||
"./device-detail/integration-elements/esphome/device-actions"
|
||||
);
|
||||
const actions = await esphome.getESPHomeDeviceActions(
|
||||
this,
|
||||
this.hass,
|
||||
device
|
||||
);
|
||||
deviceActions.push(...actions);
|
||||
}
|
||||
if (domains.includes("matter")) {
|
||||
const matter = await import(
|
||||
"./device-detail/integration-elements/matter/device-actions"
|
||||
|
||||
@@ -968,12 +968,6 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
|
||||
};
|
||||
}
|
||||
|
||||
protected supportedSingleKeyShortcuts(): SupportedShortcuts {
|
||||
return {
|
||||
n: () => this._createFlow(),
|
||||
};
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import { mdiClose, mdiContentCopy } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { copyToClipboard } from "../../../../../common/util/copy-clipboard";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-dialog-footer";
|
||||
import "../../../../../components/ha-dialog-header";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-wa-dialog";
|
||||
import { haStyleDialog } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { showToast } from "../../../../../util/toast";
|
||||
import type { ESPHomeEncryptionKeyDialogParams } from "./show-dialog-esphome-encryption-key";
|
||||
|
||||
@customElement("dialog-esphome-encryption-key")
|
||||
class DialogESPHomeEncryptionKey extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: ESPHomeEncryptionKeyDialogParams;
|
||||
|
||||
public async showDialog(
|
||||
params: ESPHomeEncryptionKeyDialogParams
|
||||
): Promise<void> {
|
||||
this._params = params;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-wa-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
header-title=${this.hass.localize(
|
||||
"ui.panel.config.devices.esphome.encryption_key_title"
|
||||
)}
|
||||
>
|
||||
<ha-dialog-header slot="heading">
|
||||
<ha-icon-button
|
||||
slot="navigationIcon"
|
||||
dialogAction="cancel"
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
<span slot="title">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.esphome.encryption_key_title"
|
||||
)}
|
||||
</span>
|
||||
</ha-dialog-header>
|
||||
|
||||
<div class="content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.esphome.encryption_key_description"
|
||||
)}
|
||||
</p>
|
||||
<div class="key-row">
|
||||
<div class="key-container">
|
||||
<code>${this._params.encryption_key}</code>
|
||||
</div>
|
||||
<ha-icon-button
|
||||
@click=${this._copyToClipboard}
|
||||
.label=${this.hass.localize("ui.common.copy")}
|
||||
.path=${mdiContentCopy}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button slot="primaryAction" data-dialog="close">
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-wa-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _copyToClipboard(): Promise<void> {
|
||||
if (!this._params?.encryption_key) {
|
||||
return;
|
||||
}
|
||||
|
||||
await copyToClipboard(this._params.encryption_key);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--ha-space-6);
|
||||
}
|
||||
|
||||
.key-row {
|
||||
display: flex;
|
||||
gap: var(--ha-space-2);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.key-container {
|
||||
flex: 1;
|
||||
border-radius: var(--ha-space-2);
|
||||
border: 1px solid var(--divider-color);
|
||||
background-color: var(
|
||||
--code-editor-background-color,
|
||||
var(--secondary-background-color)
|
||||
);
|
||||
padding: var(--ha-space-3);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: var(--secondary-text-color);
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-esphome-encryption-key": DialogESPHomeEncryptionKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
|
||||
export interface ESPHomeEncryptionKeyDialogParams {
|
||||
entry_id: string;
|
||||
encryption_key: string;
|
||||
}
|
||||
|
||||
export const loadESPHomeEncryptionKeyDialog = () =>
|
||||
import("./dialog-esphome-encryption-key");
|
||||
|
||||
export const showESPHomeEncryptionKeyDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: ESPHomeEncryptionKeyDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-esphome-encryption-key",
|
||||
dialogImport: loadESPHomeEncryptionKeyDialog,
|
||||
dialogParams,
|
||||
});
|
||||
};
|
||||
@@ -55,7 +55,6 @@ import {
|
||||
subscribeEntityRegistry,
|
||||
updateEntityRegistryEntry,
|
||||
} from "../../../../../../data/entity_registry";
|
||||
import { SubscribeMixin } from "../../../../../../mixins/subscribe-mixin";
|
||||
import "./zwave-js-add-node-added-insecure";
|
||||
import "./zwave-js-add-node-code-input";
|
||||
import "./zwave-js-add-node-configure-device";
|
||||
@@ -69,7 +68,7 @@ import "./zwave-js-add-node-select-security-strategy";
|
||||
const INCLUSION_TIMEOUT_MINUTES = 5;
|
||||
|
||||
@customElement("dialog-zwave_js-add-node")
|
||||
class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
||||
class DialogZWaveJSAddNode extends LitElement {
|
||||
// #region variables
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -103,6 +102,8 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _securityClasses: SecurityClass[] = [];
|
||||
|
||||
@state() private _entities: EntityRegistryEntry[] = [];
|
||||
|
||||
@state() private _codeInput = "";
|
||||
|
||||
@query("ha-dialog") private _dialog?: HaDialog;
|
||||
@@ -113,22 +114,14 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _onStop?: () => void;
|
||||
|
||||
private _subscribed?: Promise<UnsubscribeFunc | undefined>;
|
||||
private _subscribedAddZwaveNode?: Promise<UnsubscribeFunc | undefined>;
|
||||
|
||||
private _newDeviceSubscription?: Promise<UnsubscribeFunc | undefined>;
|
||||
|
||||
@state() private _entities: EntityRegistryEntry[] = [];
|
||||
private _subscribedEntityRegistry?: UnsubscribeFunc;
|
||||
|
||||
// #endregion
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection, (entities) => {
|
||||
this._entities = entities;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._entryId) {
|
||||
return nothing;
|
||||
@@ -439,11 +432,6 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
||||
></zwave-js-add-node-loading>`;
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
window.addEventListener("beforeunload", this._onBeforeUnload);
|
||||
}
|
||||
|
||||
private _onBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||
if (this._step && this._shouldPreventClose(this._step)) {
|
||||
event.preventDefault();
|
||||
@@ -468,6 +456,14 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
public async showDialog(params: ZWaveJSAddNodeDialogParams): Promise<void> {
|
||||
window.addEventListener("beforeunload", this._onBeforeUnload);
|
||||
this._subscribedEntityRegistry = subscribeEntityRegistry(
|
||||
this.hass.connection,
|
||||
(entities) => {
|
||||
this._entities = entities;
|
||||
}
|
||||
);
|
||||
|
||||
if (this._step) {
|
||||
// already started
|
||||
return;
|
||||
@@ -562,7 +558,7 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
||||
this._step = "select_method";
|
||||
break;
|
||||
case "search_devices":
|
||||
this._unsubscribe();
|
||||
this._unsubscribeAddZwaveNode();
|
||||
if (
|
||||
this._supportsSmartStart &&
|
||||
this.hass.auth.external?.config.hasBarCodeScanner
|
||||
@@ -604,7 +600,7 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _searchDevicesShowSecurityOptions() {
|
||||
this._unsubscribe();
|
||||
this._unsubscribeAddZwaveNode();
|
||||
this._step = "choose_security_strategy";
|
||||
}
|
||||
|
||||
@@ -626,7 +622,7 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
||||
this._lowSecurity = false;
|
||||
|
||||
const s2Device = qrProvisioningInformation || dsk;
|
||||
this._subscribed = subscribeAddZwaveNode(
|
||||
this._subscribedAddZwaveNode = subscribeAddZwaveNode(
|
||||
this.hass,
|
||||
this._entryId!,
|
||||
(message) => {
|
||||
@@ -635,7 +631,7 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
||||
this._step = s2Device ? "search_s2_device" : "search_devices";
|
||||
break;
|
||||
case "inclusion failed":
|
||||
this._unsubscribe();
|
||||
this._unsubscribeAddZwaveNode();
|
||||
this._step = "failed";
|
||||
break;
|
||||
case "inclusion stopped":
|
||||
@@ -677,7 +673,7 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
||||
this._lowSecurityReason = message.node.low_security_reason;
|
||||
break;
|
||||
case "interview completed":
|
||||
this._unsubscribe();
|
||||
this._unsubscribeAddZwaveNode();
|
||||
this._step = "configure_device";
|
||||
break;
|
||||
}
|
||||
@@ -694,7 +690,7 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
||||
});
|
||||
this._addNodeTimeoutHandle = window.setTimeout(
|
||||
() => {
|
||||
this._unsubscribe();
|
||||
this._unsubscribeAddZwaveNode();
|
||||
this._error = this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.timeout_error",
|
||||
{ minutes: INCLUSION_TIMEOUT_MINUTES }
|
||||
@@ -1023,10 +1019,10 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private _unsubscribe(): void {
|
||||
if (this._subscribed) {
|
||||
this._subscribed.then((unsub) => unsub && unsub());
|
||||
this._subscribed = undefined;
|
||||
private _unsubscribeAddZwaveNode(): void {
|
||||
if (this._subscribedAddZwaveNode) {
|
||||
this._subscribedAddZwaveNode.then((unsub) => unsub && unsub());
|
||||
this._subscribedAddZwaveNode = undefined;
|
||||
|
||||
if (this._entryId) {
|
||||
stopZwaveInclusion(this.hass, this._entryId);
|
||||
@@ -1060,8 +1056,17 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
||||
window.removeEventListener("beforeunload", this._onBeforeUnload);
|
||||
}
|
||||
|
||||
private _unsubscribeDialog() {
|
||||
if (this._subscribedEntityRegistry) {
|
||||
this._subscribedEntityRegistry();
|
||||
this._subscribedEntityRegistry = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _dialogClosed() {
|
||||
this._unsubscribe();
|
||||
window.removeEventListener("beforeunload", this._onBeforeUnload);
|
||||
this._unsubscribeAddZwaveNode();
|
||||
this._unsubscribeDialog();
|
||||
this._open = false;
|
||||
this._entryId = undefined;
|
||||
this._step = undefined;
|
||||
@@ -1100,7 +1105,8 @@ class DialogZWaveJSAddNode extends SubscribeMixin(LitElement) {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener("beforeunload", this._onBeforeUnload);
|
||||
|
||||
this._unsubscribe();
|
||||
this._unsubscribeAddZwaveNode();
|
||||
this._unsubscribeDialog();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
||||
@@ -47,12 +47,9 @@ import { configSections } from "../ha-panel-config";
|
||||
import { showHomeZoneDetailDialog } from "./show-dialog-home-zone-detail";
|
||||
import { showZoneDetailDialog } from "./show-dialog-zone-detail";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
||||
|
||||
@customElement("ha-config-zone")
|
||||
export class HaConfigZone extends KeyboardShortcutMixin(
|
||||
SubscribeMixin(LitElement)
|
||||
) {
|
||||
export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: "is-wide", type: Boolean }) public isWide = false;
|
||||
@@ -540,12 +537,6 @@ export class HaConfigZone extends KeyboardShortcutMixin(
|
||||
});
|
||||
}
|
||||
|
||||
protected supportedSingleKeyShortcuts(): SupportedShortcuts {
|
||||
return {
|
||||
n: () => this._createZone(),
|
||||
};
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
hass-loading-screen {
|
||||
--app-header-background-color: var(--sidebar-background-color);
|
||||
|
||||
@@ -6,7 +6,6 @@ import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
|
||||
|
||||
const sourceHasCost = (source: Record<string, any>): boolean =>
|
||||
@@ -52,10 +51,6 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
source.type === "grid" &&
|
||||
(source.flow_from?.length || source.flow_to?.length)
|
||||
) as GridSourceTypeEnergyPreference;
|
||||
const hasReturn = hasGrid && hasGrid.flow_to.length > 0;
|
||||
const hasSolar = prefs.energy_sources.some(
|
||||
(source) => source.type === "solar"
|
||||
);
|
||||
const hasGas = prefs.energy_sources.some((source) => source.type === "gas");
|
||||
const hasBattery = prefs.energy_sources.some(
|
||||
(source) => source.type === "battery"
|
||||
@@ -107,6 +102,7 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
overviewSection.cards!.push({
|
||||
type: "energy-sources-table",
|
||||
collection_key: collectionKey,
|
||||
show_only_totals: true,
|
||||
});
|
||||
}
|
||||
view.sections!.push(overviewSection);
|
||||
@@ -142,43 +138,10 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
modes: ["bar"],
|
||||
});
|
||||
} else if (hasGrid) {
|
||||
const gauges: LovelaceCardConfig[] = [];
|
||||
// Only include if we have a grid source & return.
|
||||
if (hasReturn) {
|
||||
gauges.push({
|
||||
type: "energy-grid-neutrality-gauge",
|
||||
view_layout: { position: "sidebar" },
|
||||
collection_key: collectionKey,
|
||||
});
|
||||
}
|
||||
|
||||
gauges.push({
|
||||
type: "energy-carbon-consumed-gauge",
|
||||
view_layout: { position: "sidebar" },
|
||||
collection_key: collectionKey,
|
||||
});
|
||||
|
||||
// Only include if we have a solar source.
|
||||
if (hasSolar) {
|
||||
if (hasReturn) {
|
||||
gauges.push({
|
||||
type: "energy-solar-consumed-gauge",
|
||||
view_layout: { position: "sidebar" },
|
||||
collection_key: collectionKey,
|
||||
});
|
||||
}
|
||||
gauges.push({
|
||||
type: "energy-self-sufficiency-gauge",
|
||||
view_layout: { position: "sidebar" },
|
||||
collection_key: collectionKey,
|
||||
});
|
||||
}
|
||||
|
||||
electricitySection.cards!.push({
|
||||
type: "grid",
|
||||
columns: 2,
|
||||
square: false,
|
||||
cards: gauges,
|
||||
title: hass.localize("ui.panel.energy.cards.energy_usage_graph_title"),
|
||||
type: "energy-usage-graph",
|
||||
collection_key: collectionKey,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -166,10 +166,24 @@ export class HuiEnergySourcesTableCard
|
||||
cost: number | null,
|
||||
compareCost: number | null,
|
||||
showCosts: boolean,
|
||||
compare: boolean
|
||||
compare: boolean,
|
||||
bulletColor?: { border: string; background: string },
|
||||
isFinalTotal?: boolean
|
||||
) {
|
||||
return html` <tr class="mdc-data-table__row total">
|
||||
<td class="mdc-data-table__cell"></td>
|
||||
return html` <tr
|
||||
class="mdc-data-table__row ${bulletColor && !isFinalTotal ? "" : "total"}"
|
||||
>
|
||||
<td class="mdc-data-table__cell cell-bullet">
|
||||
${bulletColor
|
||||
? html`<div
|
||||
class="bullet"
|
||||
style=${styleMap({
|
||||
borderColor: bulletColor.border,
|
||||
backgroundColor: bulletColor.background,
|
||||
})}
|
||||
></div>`
|
||||
: nothing}
|
||||
</td>
|
||||
<th class="mdc-data-table__cell" scope="row">${label}</th>
|
||||
${compare
|
||||
? html`<td class="mdc-data-table__cell mdc-data-table__cell--numeric">
|
||||
@@ -339,6 +353,8 @@ export class HuiEnergySourcesTableCard
|
||||
};
|
||||
};
|
||||
|
||||
const showOnlyTotals = this._config.show_only_totals;
|
||||
|
||||
const _renderSimpleCategory = (type: "solar" | "gas" | "water") =>
|
||||
html` ${types[type]?.map((source, idx) => {
|
||||
const cost_stat =
|
||||
@@ -361,6 +377,10 @@ export class HuiEnergySourcesTableCard
|
||||
totalCostsCompare[type] += costCompare;
|
||||
}
|
||||
|
||||
if (showOnlyTotals) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return this._renderRow(
|
||||
computedStyles,
|
||||
type,
|
||||
@@ -386,7 +406,27 @@ export class HuiEnergySourcesTableCard
|
||||
hasCosts[type] ? totalCosts[type] : null,
|
||||
hasCosts[type] ? totalCostsCompare[type] : null,
|
||||
showCosts,
|
||||
compare
|
||||
compare,
|
||||
showOnlyTotals
|
||||
? {
|
||||
border: getEnergyColor(
|
||||
computedStyles,
|
||||
this.hass.themes.darkMode,
|
||||
false,
|
||||
false,
|
||||
colorPropertyMap[type],
|
||||
0
|
||||
),
|
||||
background: getEnergyColor(
|
||||
computedStyles,
|
||||
this.hass.themes.darkMode,
|
||||
true,
|
||||
false,
|
||||
colorPropertyMap[type],
|
||||
0
|
||||
),
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
: ""}`;
|
||||
|
||||
@@ -474,6 +514,10 @@ export class HuiEnergySourcesTableCard
|
||||
totalBattery += energyFrom - energyTo;
|
||||
totalBatteryCompare += energyFromCompare - energyToCompare;
|
||||
|
||||
if (showOnlyTotals) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html` ${this._renderRow(
|
||||
computedStyles,
|
||||
"battery_out",
|
||||
@@ -511,7 +555,27 @@ export class HuiEnergySourcesTableCard
|
||||
null,
|
||||
null,
|
||||
showCosts,
|
||||
compare
|
||||
compare,
|
||||
showOnlyTotals
|
||||
? {
|
||||
border: getEnergyColor(
|
||||
computedStyles,
|
||||
this.hass.themes.darkMode,
|
||||
false,
|
||||
false,
|
||||
colorPropertyMap.battery_out,
|
||||
0
|
||||
),
|
||||
background: getEnergyColor(
|
||||
computedStyles,
|
||||
this.hass.themes.darkMode,
|
||||
true,
|
||||
false,
|
||||
colorPropertyMap.battery_out,
|
||||
0
|
||||
),
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
: ""}
|
||||
${types.grid?.map(
|
||||
@@ -543,6 +607,11 @@ export class HuiEnergySourcesTableCard
|
||||
totalGridCost += cost;
|
||||
totalGridCostCompare += costCompare;
|
||||
}
|
||||
|
||||
if (showOnlyTotals) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return this._renderRow(
|
||||
computedStyles,
|
||||
"grid_consumption",
|
||||
@@ -583,6 +652,11 @@ export class HuiEnergySourcesTableCard
|
||||
totalGridCost -= cost;
|
||||
totalGridCostCompare -= costCompare;
|
||||
}
|
||||
|
||||
if (showOnlyTotals) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return this._renderRow(
|
||||
computedStyles,
|
||||
"grid_return",
|
||||
@@ -611,7 +685,27 @@ export class HuiEnergySourcesTableCard
|
||||
hasGridCost ? totalGridCost : null,
|
||||
hasGridCost ? totalGridCostCompare : null,
|
||||
showCosts,
|
||||
compare
|
||||
compare,
|
||||
showOnlyTotals
|
||||
? {
|
||||
border: getEnergyColor(
|
||||
computedStyles,
|
||||
this.hass.themes.darkMode,
|
||||
false,
|
||||
false,
|
||||
colorPropertyMap.grid_consumption,
|
||||
0
|
||||
),
|
||||
background: getEnergyColor(
|
||||
computedStyles,
|
||||
this.hass.themes.darkMode,
|
||||
true,
|
||||
false,
|
||||
colorPropertyMap.grid_consumption,
|
||||
0
|
||||
),
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
: ""}
|
||||
${_renderSimpleCategory("gas")} ${_renderSimpleCategory("water")}
|
||||
@@ -629,7 +723,9 @@ export class HuiEnergySourcesTableCard
|
||||
totalGridCostCompare +
|
||||
totalCostsCompare.water,
|
||||
showCosts,
|
||||
compare
|
||||
compare,
|
||||
undefined,
|
||||
true
|
||||
)
|
||||
: ""}
|
||||
</tbody>
|
||||
@@ -672,8 +768,8 @@ export class HuiEnergySourcesTableCard
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
ha-card {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
.card-header {
|
||||
padding-bottom: 0;
|
||||
|
||||
@@ -194,6 +194,7 @@ export interface EnergySourcesTableCardConfig extends EnergyCardBaseConfig {
|
||||
type: "energy-sources-table";
|
||||
title?: string;
|
||||
types?: (keyof EnergySourceByType)[];
|
||||
show_only_totals?: boolean;
|
||||
}
|
||||
|
||||
export interface EnergySolarGaugeCardConfig extends EnergyCardBaseConfig {
|
||||
|
||||
@@ -2125,8 +2125,7 @@
|
||||
"search_command": "search command",
|
||||
"search_entities": "search entities",
|
||||
"search_devices": "search devices",
|
||||
"search_in_table": "to search in tables",
|
||||
"new_in_table": "to create a new item in tables"
|
||||
"search_in_table": "to search in tables"
|
||||
},
|
||||
"assist": {
|
||||
"title": "Assist",
|
||||
@@ -5466,6 +5465,11 @@
|
||||
"partial_failure": "Some devices failed to delete successfully. Check system logs for more information."
|
||||
}
|
||||
}
|
||||
},
|
||||
"esphome": {
|
||||
"show_encryption_key": "Show encryption key",
|
||||
"encryption_key_title": "ESPHome Encryption Key",
|
||||
"encryption_key_description": "This is the encryption key for your ESPHome device. Keep it in a safe place, as you may need it when transferring devices between Home Assistant instances."
|
||||
}
|
||||
},
|
||||
"entities": {
|
||||
@@ -7225,9 +7229,9 @@
|
||||
"solar_total": "Solar total",
|
||||
"water_total": "Water total",
|
||||
"source": "Source",
|
||||
"energy": "Energy",
|
||||
"energy": "Usage",
|
||||
"cost": "Cost",
|
||||
"previous_energy": "Previous energy",
|
||||
"previous_energy": "Previous usage",
|
||||
"previous_cost": "Previous cost",
|
||||
"battery_total": "Battery total",
|
||||
"total_costs": "Total costs"
|
||||
|
||||
26
yarn.lock
26
yarn.lock
@@ -8598,7 +8598,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"foreground-child@npm:^3.1.0, foreground-child@npm:^3.3.1":
|
||||
"foreground-child@npm:^3.1.0":
|
||||
version: 3.3.1
|
||||
resolution: "foreground-child@npm:3.3.1"
|
||||
dependencies:
|
||||
@@ -8869,19 +8869,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"glob@npm:12.0.0":
|
||||
version: 12.0.0
|
||||
resolution: "glob@npm:12.0.0"
|
||||
"glob@npm:13.0.0":
|
||||
version: 13.0.0
|
||||
resolution: "glob@npm:13.0.0"
|
||||
dependencies:
|
||||
foreground-child: "npm:^3.3.1"
|
||||
jackspeak: "npm:^4.1.1"
|
||||
minimatch: "npm:^10.1.1"
|
||||
minipass: "npm:^7.1.2"
|
||||
package-json-from-dist: "npm:^1.0.0"
|
||||
path-scurry: "npm:^2.0.0"
|
||||
bin:
|
||||
glob: dist/esm/bin.mjs
|
||||
checksum: 10/6e21b3f1f1fa635836d45e54bbe50704884cc3e310e0cc011cfb5429db65a030e12936d99b07e66236370efe45dc8c8b26fa5334dbf555d6f8709e0315c77c30
|
||||
checksum: 10/de390721d29ee1c9ea41e40ec2aa0de2cabafa68022e237dc4297665a5e4d650776f2573191984ea1640aba1bf0ea34eddef2d8cbfbfc2ad24b5fb0af41d8846
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9324,7 +9319,7 @@ __metadata:
|
||||
fancy-log: "npm:2.0.0"
|
||||
fs-extra: "npm:11.3.2"
|
||||
fuse.js: "npm:7.1.0"
|
||||
glob: "npm:12.0.0"
|
||||
glob: "npm:13.0.0"
|
||||
google-timezones-json: "npm:1.2.0"
|
||||
gulp: "npm:5.0.1"
|
||||
gulp-brotli: "npm:3.0.0"
|
||||
@@ -10346,15 +10341,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jackspeak@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "jackspeak@npm:4.1.1"
|
||||
dependencies:
|
||||
"@isaacs/cliui": "npm:^8.0.2"
|
||||
checksum: 10/ffceb270ec286841f48413bfb4a50b188662dfd599378ce142b6540f3f0a66821dc9dcb1e9ebc55c6c3b24dc2226c96e5819ba9bd7a241bd29031b61911718c7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jake@npm:^10.8.5":
|
||||
version: 10.9.4
|
||||
resolution: "jake@npm:10.9.4"
|
||||
|
||||
Reference in New Issue
Block a user