mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-19 15:26:36 +00:00
Add support for integration type (#12077)
This commit is contained in:
parent
bdde5268c6
commit
73f5580555
@ -1,4 +1,4 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { computeDomain } from "./compute_domain";
|
import { computeDomain } from "./compute_domain";
|
||||||
|
|
||||||
export const computeStateDomain = (stateObj: HassEntity) =>
|
export const computeStateDomain = (stateObj: HassEntity) =>
|
||||||
|
@ -28,7 +28,11 @@ export class HaAreaSelector extends LitElement {
|
|||||||
oldSelector !== this.selector &&
|
oldSelector !== this.selector &&
|
||||||
this.selector.area.device?.integration
|
this.selector.area.device?.integration
|
||||||
) {
|
) {
|
||||||
this._loadConfigEntries();
|
getConfigEntries(this.hass, {
|
||||||
|
domain: this.selector.area.device.integration,
|
||||||
|
}).then((entries) => {
|
||||||
|
this._configEntries = entries;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -85,12 +89,6 @@ export class HaAreaSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
private async _loadConfigEntries() {
|
|
||||||
this._configEntries = (await getConfigEntries(this.hass)).filter(
|
|
||||||
(entry) => entry.domain === this.selector.area.device?.integration
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -25,7 +25,11 @@ export class HaDeviceSelector extends LitElement {
|
|||||||
if (changedProperties.has("selector")) {
|
if (changedProperties.has("selector")) {
|
||||||
const oldSelector = changedProperties.get("selector");
|
const oldSelector = changedProperties.get("selector");
|
||||||
if (oldSelector !== this.selector && this.selector.device?.integration) {
|
if (oldSelector !== this.selector && this.selector.device?.integration) {
|
||||||
this._loadConfigEntries();
|
getConfigEntries(this.hass, {
|
||||||
|
domain: this.selector.device.integration,
|
||||||
|
}).then((entries) => {
|
||||||
|
this._configEntries = entries;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,12 +92,6 @@ export class HaDeviceSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
private async _loadConfigEntries() {
|
|
||||||
this._configEntries = (await getConfigEntries(this.hass)).filter(
|
|
||||||
(entry) => entry.domain === this.selector.device.integration
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -134,9 +134,8 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) {
|
|||||||
private async _loadConfigEntries() {
|
private async _loadConfigEntries() {
|
||||||
this._configEntries = (await getConfigEntries(this.hass)).filter(
|
this._configEntries = (await getConfigEntries(this.hass)).filter(
|
||||||
(entry) =>
|
(entry) =>
|
||||||
entry.domain ===
|
entry.domain === this.selector.target.device?.integration ||
|
||||||
(this.selector.target.device?.integration ||
|
entry.domain === this.selector.target.entity?.integration
|
||||||
this.selector.target.entity?.integration)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,8 +34,24 @@ export const ERROR_STATES: ConfigEntry["state"][] = [
|
|||||||
"setup_retry",
|
"setup_retry",
|
||||||
];
|
];
|
||||||
|
|
||||||
export const getConfigEntries = (hass: HomeAssistant) =>
|
export const getConfigEntries = (
|
||||||
hass.callApi<ConfigEntry[]>("GET", "config/config_entries/entry");
|
hass: HomeAssistant,
|
||||||
|
filters?: { type?: "helper" | "integration"; domain?: string }
|
||||||
|
): Promise<ConfigEntry[]> => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filters) {
|
||||||
|
if (filters.type) {
|
||||||
|
params.append("type", filters.type);
|
||||||
|
}
|
||||||
|
if (filters.domain) {
|
||||||
|
params.append("domain", filters.domain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hass.callApi<ConfigEntry[]>(
|
||||||
|
"GET",
|
||||||
|
`config/config_entries/entry?${params.toString()}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const updateConfigEntry = (
|
export const updateConfigEntry = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -65,8 +65,14 @@ export const ignoreConfigFlow = (
|
|||||||
export const deleteConfigFlow = (hass: HomeAssistant, flowId: string) =>
|
export const deleteConfigFlow = (hass: HomeAssistant, flowId: string) =>
|
||||||
hass.callApi("DELETE", `config/config_entries/flow/${flowId}`);
|
hass.callApi("DELETE", `config/config_entries/flow/${flowId}`);
|
||||||
|
|
||||||
export const getConfigFlowHandlers = (hass: HomeAssistant) =>
|
export const getConfigFlowHandlers = (
|
||||||
hass.callApi<string[]>("GET", "config/config_entries/flow_handlers");
|
hass: HomeAssistant,
|
||||||
|
type?: "helper" | "integration"
|
||||||
|
) =>
|
||||||
|
hass.callApi<string[]>(
|
||||||
|
"GET",
|
||||||
|
`config/config_entries/flow_handlers${type ? `?type=${type}` : ""}`
|
||||||
|
);
|
||||||
|
|
||||||
export const fetchConfigFlowInProgress = (
|
export const fetchConfigFlowInProgress = (
|
||||||
conn: Connection
|
conn: Connection
|
||||||
|
@ -247,14 +247,14 @@ const getEnergyData = async (
|
|||||||
end?: Date
|
end?: Date
|
||||||
): Promise<EnergyData> => {
|
): Promise<EnergyData> => {
|
||||||
const [configEntries, entityRegistryEntries, info] = await Promise.all([
|
const [configEntries, entityRegistryEntries, info] = await Promise.all([
|
||||||
getConfigEntries(hass),
|
getConfigEntries(hass, { domain: "co2signal" }),
|
||||||
subscribeOne(hass.connection, subscribeEntityRegistry),
|
subscribeOne(hass.connection, subscribeEntityRegistry),
|
||||||
getEnergyInfo(hass),
|
getEnergyInfo(hass),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const co2SignalConfigEntry = configEntries.find(
|
const co2SignalConfigEntry = configEntries.length
|
||||||
(entry) => entry.domain === "co2signal"
|
? configEntries[0]
|
||||||
);
|
: undefined;
|
||||||
|
|
||||||
let co2SignalEntity: string | undefined;
|
let co2SignalEntity: string | undefined;
|
||||||
|
|
||||||
|
71
src/data/helpers_crud.ts
Normal file
71
src/data/helpers_crud.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { fetchCounter, updateCounter, deleteCounter } from "./counter";
|
||||||
|
import {
|
||||||
|
fetchInputBoolean,
|
||||||
|
updateInputBoolean,
|
||||||
|
deleteInputBoolean,
|
||||||
|
} from "./input_boolean";
|
||||||
|
import {
|
||||||
|
fetchInputButton,
|
||||||
|
updateInputButton,
|
||||||
|
deleteInputButton,
|
||||||
|
} from "./input_button";
|
||||||
|
import {
|
||||||
|
fetchInputDateTime,
|
||||||
|
updateInputDateTime,
|
||||||
|
deleteInputDateTime,
|
||||||
|
} from "./input_datetime";
|
||||||
|
import {
|
||||||
|
fetchInputNumber,
|
||||||
|
updateInputNumber,
|
||||||
|
deleteInputNumber,
|
||||||
|
} from "./input_number";
|
||||||
|
import {
|
||||||
|
fetchInputSelect,
|
||||||
|
updateInputSelect,
|
||||||
|
deleteInputSelect,
|
||||||
|
} from "./input_select";
|
||||||
|
import { fetchInputText, updateInputText, deleteInputText } from "./input_text";
|
||||||
|
import { fetchTimer, updateTimer, deleteTimer } from "./timer";
|
||||||
|
|
||||||
|
export const HELPERS_CRUD = {
|
||||||
|
input_boolean: {
|
||||||
|
fetch: fetchInputBoolean,
|
||||||
|
update: updateInputBoolean,
|
||||||
|
delete: deleteInputBoolean,
|
||||||
|
},
|
||||||
|
input_button: {
|
||||||
|
fetch: fetchInputButton,
|
||||||
|
update: updateInputButton,
|
||||||
|
delete: deleteInputButton,
|
||||||
|
},
|
||||||
|
input_text: {
|
||||||
|
fetch: fetchInputText,
|
||||||
|
update: updateInputText,
|
||||||
|
delete: deleteInputText,
|
||||||
|
},
|
||||||
|
input_number: {
|
||||||
|
fetch: fetchInputNumber,
|
||||||
|
update: updateInputNumber,
|
||||||
|
delete: deleteInputNumber,
|
||||||
|
},
|
||||||
|
input_datetime: {
|
||||||
|
fetch: fetchInputDateTime,
|
||||||
|
update: updateInputDateTime,
|
||||||
|
delete: deleteInputDateTime,
|
||||||
|
},
|
||||||
|
input_select: {
|
||||||
|
fetch: fetchInputSelect,
|
||||||
|
update: updateInputSelect,
|
||||||
|
delete: deleteInputSelect,
|
||||||
|
},
|
||||||
|
counter: {
|
||||||
|
fetch: fetchCounter,
|
||||||
|
update: updateCounter,
|
||||||
|
delete: deleteCounter,
|
||||||
|
},
|
||||||
|
timer: {
|
||||||
|
fetch: fetchTimer,
|
||||||
|
update: updateTimer,
|
||||||
|
delete: deleteTimer,
|
||||||
|
},
|
||||||
|
};
|
@ -24,7 +24,7 @@ export const showConfigFlowDialog = (
|
|||||||
loadDevicesAndAreas: true,
|
loadDevicesAndAreas: true,
|
||||||
getFlowHandlers: async (hass) => {
|
getFlowHandlers: async (hass) => {
|
||||||
const [handlers] = await Promise.all([
|
const [handlers] = await Promise.all([
|
||||||
getConfigFlowHandlers(hass),
|
getConfigFlowHandlers(hass, "integration"),
|
||||||
hass.loadBackendTranslation("title", undefined, true),
|
hass.loadBackendTranslation("title", undefined, true),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -216,15 +216,16 @@ class StepFlowPickHandler extends LitElement {
|
|||||||
|
|
||||||
if (handler.is_add) {
|
if (handler.is_add) {
|
||||||
if (handler.slug === "zwave_js") {
|
if (handler.slug === "zwave_js") {
|
||||||
const entries = await getConfigEntries(this.hass);
|
const entries = await getConfigEntries(this.hass, {
|
||||||
const entry = entries.find((ent) => ent.domain === "zwave_js");
|
domain: "zwave_js",
|
||||||
|
});
|
||||||
|
|
||||||
if (!entry) {
|
if (!entries.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
showZWaveJSAddNodeDialog(this, {
|
showZWaveJSAddNodeDialog(this, {
|
||||||
entry_id: entry.entry_id,
|
entry_id: entries[0].entry_id,
|
||||||
});
|
});
|
||||||
} else if (handler.slug === "zha") {
|
} else if (handler.slug === "zha") {
|
||||||
navigate("/config/zha/add");
|
navigate("/config/zha/add");
|
||||||
|
@ -169,8 +169,8 @@ class OnboardingIntegrations extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _loadConfigEntries() {
|
private async _loadConfigEntries() {
|
||||||
const entries = await getConfigEntries(this.hass!);
|
const entries = await getConfigEntries(this.hass!, { type: "integration" });
|
||||||
// We filter out the config entry for the local weather and rpi_power.
|
// We filter out the config entries that are automatically created during onboarding.
|
||||||
// It is one that we create automatically and it will confuse the user
|
// It is one that we create automatically and it will confuse the user
|
||||||
// if it starts showing up during onboarding.
|
// if it starts showing up during onboarding.
|
||||||
this._entries = entries.filter(
|
this._entries = entries.filter(
|
||||||
|
@ -58,12 +58,11 @@ export class HaDeviceInfoZWaveJS extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const configEntries = await getConfigEntries(this.hass);
|
const configEntries = await getConfigEntries(this.hass, {
|
||||||
|
domain: "zwave_js",
|
||||||
|
});
|
||||||
let zwaveJsConfEntries = 0;
|
let zwaveJsConfEntries = 0;
|
||||||
for (const entry of configEntries) {
|
for (const entry of configEntries) {
|
||||||
if (entry.domain !== "zwave_js") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (zwaveJsConfEntries) {
|
if (zwaveJsConfEntries) {
|
||||||
this._multipleConfigEntries = true;
|
this._multipleConfigEntries = true;
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ export class EnergyGridSettings extends LitElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public validationResult?: EnergyPreferencesValidation;
|
public validationResult?: EnergyPreferencesValidation;
|
||||||
|
|
||||||
@state() private _configEntries?: ConfigEntry[];
|
@state() private _co2ConfigEntry?: ConfigEntry;
|
||||||
|
|
||||||
protected firstUpdated() {
|
protected firstUpdated() {
|
||||||
this._fetchCO2SignalConfigEntries();
|
this._fetchCO2SignalConfigEntries();
|
||||||
@ -195,8 +195,8 @@ export class EnergyGridSettings extends LitElement {
|
|||||||
"ui.panel.config.energy.grid.grid_carbon_footprint"
|
"ui.panel.config.energy.grid.grid_carbon_footprint"
|
||||||
)}
|
)}
|
||||||
</h3>
|
</h3>
|
||||||
${this._configEntries?.map(
|
${this._co2ConfigEntry
|
||||||
(entry) => html`<div class="row" .entry=${entry}>
|
? html`<div class="row" .entry=${this._co2ConfigEntry}>
|
||||||
<img
|
<img
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
src=${brandsUrl({
|
src=${brandsUrl({
|
||||||
@ -205,8 +205,10 @@ export class EnergyGridSettings extends LitElement {
|
|||||||
darkOptimized: this.hass.themes?.darkMode,
|
darkOptimized: this.hass.themes?.darkMode,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<span class="content">${entry.title}</span>
|
<span class="content">${this._co2ConfigEntry.title}</span>
|
||||||
<a href=${`/config/integrations#config_entry=${entry.entry_id}`}>
|
<a
|
||||||
|
href=${`/config/integrations#config_entry=${this._co2ConfigEntry.entry_id}`}
|
||||||
|
>
|
||||||
<ha-icon-button .path=${mdiPencil}></ha-icon-button>
|
<ha-icon-button .path=${mdiPencil}></ha-icon-button>
|
||||||
</a>
|
</a>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
@ -214,9 +216,7 @@ export class EnergyGridSettings extends LitElement {
|
|||||||
.path=${mdiDelete}
|
.path=${mdiDelete}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
</div>`
|
</div>`
|
||||||
)}
|
: html`
|
||||||
${this._configEntries?.length === 0
|
|
||||||
? html`
|
|
||||||
<div class="row border-bottom">
|
<div class="row border-bottom">
|
||||||
<img
|
<img
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
@ -232,17 +232,15 @@ export class EnergyGridSettings extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
</div>
|
</div>
|
||||||
`
|
`}
|
||||||
: ""}
|
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _fetchCO2SignalConfigEntries() {
|
private async _fetchCO2SignalConfigEntries() {
|
||||||
this._configEntries = (await getConfigEntries(this.hass)).filter(
|
const entries = await getConfigEntries(this.hass, { domain: "co2signal" });
|
||||||
(entry) => entry.domain === "co2signal"
|
this._co2ConfigEntry = entries.length ? entries[0] : undefined;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addCO2Sensor() {
|
private _addCO2Sensor() {
|
||||||
|
@ -176,8 +176,16 @@ export class DialogEnergySolarSettings
|
|||||||
|
|
||||||
private async _fetchSolarForecastConfigEntries() {
|
private async _fetchSolarForecastConfigEntries() {
|
||||||
const domains = this._params!.info.solar_forecast_domains;
|
const domains = this._params!.info.solar_forecast_domains;
|
||||||
this._configEntries = (await getConfigEntries(this.hass)).filter((entry) =>
|
this._configEntries =
|
||||||
domains.includes(entry.domain)
|
domains.length === 0
|
||||||
|
? []
|
||||||
|
: domains.length === 1
|
||||||
|
? await getConfigEntries(this.hass, {
|
||||||
|
type: "integration",
|
||||||
|
domain: domains[0],
|
||||||
|
})
|
||||||
|
: (await getConfigEntries(this.hass, { type: "integration" })).filter(
|
||||||
|
(entry) => domains.includes(entry.domain)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,50 +10,11 @@ import { customElement, property, state, query } from "lit/decorators";
|
|||||||
import { isComponentLoaded } from "../../../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../../../common/config/is_component_loaded";
|
||||||
import { dynamicElement } from "../../../../../common/dom/dynamic-element-directive";
|
import { dynamicElement } from "../../../../../common/dom/dynamic-element-directive";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
import {
|
|
||||||
deleteCounter,
|
|
||||||
fetchCounter,
|
|
||||||
updateCounter,
|
|
||||||
} from "../../../../../data/counter";
|
|
||||||
import {
|
import {
|
||||||
ExtEntityRegistryEntry,
|
ExtEntityRegistryEntry,
|
||||||
removeEntityRegistryEntry,
|
removeEntityRegistryEntry,
|
||||||
} from "../../../../../data/entity_registry";
|
} from "../../../../../data/entity_registry";
|
||||||
import {
|
import { HELPERS_CRUD } from "../../../../../data/helpers_crud";
|
||||||
deleteInputBoolean,
|
|
||||||
fetchInputBoolean,
|
|
||||||
updateInputBoolean,
|
|
||||||
} from "../../../../../data/input_boolean";
|
|
||||||
import {
|
|
||||||
deleteInputButton,
|
|
||||||
fetchInputButton,
|
|
||||||
updateInputButton,
|
|
||||||
} from "../../../../../data/input_button";
|
|
||||||
import {
|
|
||||||
deleteInputDateTime,
|
|
||||||
fetchInputDateTime,
|
|
||||||
updateInputDateTime,
|
|
||||||
} from "../../../../../data/input_datetime";
|
|
||||||
import {
|
|
||||||
deleteInputNumber,
|
|
||||||
fetchInputNumber,
|
|
||||||
updateInputNumber,
|
|
||||||
} from "../../../../../data/input_number";
|
|
||||||
import {
|
|
||||||
deleteInputSelect,
|
|
||||||
fetchInputSelect,
|
|
||||||
updateInputSelect,
|
|
||||||
} from "../../../../../data/input_select";
|
|
||||||
import {
|
|
||||||
deleteInputText,
|
|
||||||
fetchInputText,
|
|
||||||
updateInputText,
|
|
||||||
} from "../../../../../data/input_text";
|
|
||||||
import {
|
|
||||||
deleteTimer,
|
|
||||||
fetchTimer,
|
|
||||||
updateTimer,
|
|
||||||
} from "../../../../../data/timer";
|
|
||||||
import { showConfirmationDialog } from "../../../../../dialogs/generic/show-dialog-box";
|
import { showConfirmationDialog } from "../../../../../dialogs/generic/show-dialog-box";
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
@ -69,49 +30,6 @@ import "../../../helpers/forms/ha-timer-form";
|
|||||||
import "../../entity-registry-basic-editor";
|
import "../../entity-registry-basic-editor";
|
||||||
import type { HaEntityRegistryBasicEditor } from "../../entity-registry-basic-editor";
|
import type { HaEntityRegistryBasicEditor } from "../../entity-registry-basic-editor";
|
||||||
|
|
||||||
const HELPERS = {
|
|
||||||
input_boolean: {
|
|
||||||
fetch: fetchInputBoolean,
|
|
||||||
update: updateInputBoolean,
|
|
||||||
delete: deleteInputBoolean,
|
|
||||||
},
|
|
||||||
input_button: {
|
|
||||||
fetch: fetchInputButton,
|
|
||||||
update: updateInputButton,
|
|
||||||
delete: deleteInputButton,
|
|
||||||
},
|
|
||||||
input_text: {
|
|
||||||
fetch: fetchInputText,
|
|
||||||
update: updateInputText,
|
|
||||||
delete: deleteInputText,
|
|
||||||
},
|
|
||||||
input_number: {
|
|
||||||
fetch: fetchInputNumber,
|
|
||||||
update: updateInputNumber,
|
|
||||||
delete: deleteInputNumber,
|
|
||||||
},
|
|
||||||
input_datetime: {
|
|
||||||
fetch: fetchInputDateTime,
|
|
||||||
update: updateInputDateTime,
|
|
||||||
delete: deleteInputDateTime,
|
|
||||||
},
|
|
||||||
input_select: {
|
|
||||||
fetch: fetchInputSelect,
|
|
||||||
update: updateInputSelect,
|
|
||||||
delete: deleteInputSelect,
|
|
||||||
},
|
|
||||||
counter: {
|
|
||||||
fetch: fetchCounter,
|
|
||||||
update: updateCounter,
|
|
||||||
delete: deleteCounter,
|
|
||||||
},
|
|
||||||
timer: {
|
|
||||||
fetch: fetchTimer,
|
|
||||||
update: updateTimer,
|
|
||||||
delete: deleteTimer,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
@customElement("entity-settings-helper-tab")
|
@customElement("entity-settings-helper-tab")
|
||||||
export class EntityRegistrySettingsHelper extends LitElement {
|
export class EntityRegistrySettingsHelper extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -198,7 +116,7 @@ export class EntityRegistrySettingsHelper extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _getItem() {
|
private async _getItem() {
|
||||||
const items = await HELPERS[this.entry.platform].fetch(this.hass!);
|
const items = await HELPERS_CRUD[this.entry.platform].fetch(this.hass!);
|
||||||
this._item = items.find((item) => item.id === this.entry.unique_id) || null;
|
this._item = items.find((item) => item.id === this.entry.unique_id) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +124,7 @@ export class EntityRegistrySettingsHelper extends LitElement {
|
|||||||
this._submitting = true;
|
this._submitting = true;
|
||||||
try {
|
try {
|
||||||
if (this._componentLoaded && this._item) {
|
if (this._componentLoaded && this._item) {
|
||||||
await HELPERS[this.entry.platform].update(
|
await HELPERS_CRUD[this.entry.platform].update(
|
||||||
this.hass!,
|
this.hass!,
|
||||||
this._item.id,
|
this._item.id,
|
||||||
this._item
|
this._item
|
||||||
@ -236,7 +154,10 @@ export class EntityRegistrySettingsHelper extends LitElement {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (this._componentLoaded && this._item) {
|
if (this._componentLoaded && this._item) {
|
||||||
await HELPERS[this.entry.platform].delete(this.hass!, this._item.id);
|
await HELPERS_CRUD[this.entry.platform].delete(
|
||||||
|
this.hass!,
|
||||||
|
this._item.id
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const stateObj = this.hass.states[this.entry.entity_id];
|
const stateObj = this.hass.states[this.entry.entity_id];
|
||||||
if (!stateObj?.attributes.restored) {
|
if (!stateObj?.attributes.restored) {
|
||||||
|
@ -42,6 +42,12 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
|||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import { showDeviceRegistryDetailDialog } from "../devices/device-registry-detail/show-dialog-device-registry-detail";
|
import { showDeviceRegistryDetailDialog } from "../devices/device-registry-detail/show-dialog-device-registry-detail";
|
||||||
|
import {
|
||||||
|
ConfigEntry,
|
||||||
|
deleteConfigEntry,
|
||||||
|
getConfigEntries,
|
||||||
|
} from "../../../data/config_entries";
|
||||||
|
import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow";
|
||||||
|
|
||||||
const OVERRIDE_DEVICE_CLASSES = {
|
const OVERRIDE_DEVICE_CLASSES = {
|
||||||
cover: [
|
cover: [
|
||||||
@ -83,6 +89,8 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _device?: DeviceRegistryEntry;
|
@state() private _device?: DeviceRegistryEntry;
|
||||||
|
|
||||||
|
@state() private _helperConfigEntry?: ConfigEntry;
|
||||||
|
|
||||||
@state() private _error?: string;
|
@state() private _error?: string;
|
||||||
|
|
||||||
@state() private _submitting?: boolean;
|
@state() private _submitting?: boolean;
|
||||||
@ -103,6 +111,20 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps: PropertyValues): void {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
if (this.entry.config_entry_id) {
|
||||||
|
getConfigEntries(this.hass, {
|
||||||
|
type: "helper",
|
||||||
|
domain: this.entry.platform,
|
||||||
|
}).then((entries) => {
|
||||||
|
this._helperConfigEntry = entries.find(
|
||||||
|
(ent) => ent.entry_id === this.entry.config_entry_id
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected updated(changedProperties: PropertyValues) {
|
protected updated(changedProperties: PropertyValues) {
|
||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
if (changedProperties.has("entry")) {
|
if (changedProperties.has("entry")) {
|
||||||
@ -215,6 +237,21 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
|||||||
@value-changed=${this._areaPicked}
|
@value-changed=${this._areaPicked}
|
||||||
></ha-area-picker>`
|
></ha-area-picker>`
|
||||||
: ""}
|
: ""}
|
||||||
|
${this._helperConfigEntry
|
||||||
|
? html`
|
||||||
|
<div class="row">
|
||||||
|
<mwc-button
|
||||||
|
@click=${this._showOptionsFlow}
|
||||||
|
.disabled=${this._submitting}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.dialogs.entity_registry.editor.configure_state"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
|
||||||
<ha-expansion-panel
|
<ha-expansion-panel
|
||||||
.header=${this.hass.localize(
|
.header=${this.hass.localize(
|
||||||
"ui.dialogs.entity_registry.editor.advanced"
|
"ui.dialogs.entity_registry.editor.advanced"
|
||||||
@ -341,7 +378,7 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
|||||||
class="warning"
|
class="warning"
|
||||||
@click=${this._confirmDeleteEntry}
|
@click=${this._confirmDeleteEntry}
|
||||||
.disabled=${this._submitting ||
|
.disabled=${this._submitting ||
|
||||||
!(stateObj && stateObj.attributes.restored)}
|
(!this._helperConfigEntry && !stateObj.attributes.restored)}
|
||||||
>
|
>
|
||||||
${this.hass.localize("ui.dialogs.entity_registry.editor.delete")}
|
${this.hass.localize("ui.dialogs.entity_registry.editor.delete")}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
@ -471,13 +508,21 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
|||||||
this._submitting = true;
|
this._submitting = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (this._helperConfigEntry) {
|
||||||
|
await deleteConfigEntry(this.hass, this._helperConfigEntry.entry_id);
|
||||||
|
} else {
|
||||||
await removeEntityRegistryEntry(this.hass!, this._origEntityId);
|
await removeEntityRegistryEntry(this.hass!, this._origEntityId);
|
||||||
|
}
|
||||||
fireEvent(this, "close-dialog");
|
fireEvent(this, "close-dialog");
|
||||||
} finally {
|
} finally {
|
||||||
this._submitting = false;
|
this._submitting = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _showOptionsFlow() {
|
||||||
|
showOptionsFlowDialog(this, this._helperConfigEntry!);
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { Counter } from "../../../data/counter";
|
import type { Counter } from "../../../data/counter";
|
||||||
import { InputBoolean } from "../../../data/input_boolean";
|
import type { InputBoolean } from "../../../data/input_boolean";
|
||||||
import { InputButton } from "../../../data/input_button";
|
import type { InputButton } from "../../../data/input_button";
|
||||||
import { InputDateTime } from "../../../data/input_datetime";
|
import type { InputDateTime } from "../../../data/input_datetime";
|
||||||
import { InputNumber } from "../../../data/input_number";
|
import type { InputNumber } from "../../../data/input_number";
|
||||||
import { InputSelect } from "../../../data/input_select";
|
import type { InputSelect } from "../../../data/input_select";
|
||||||
import { InputText } from "../../../data/input_text";
|
import type { InputText } from "../../../data/input_text";
|
||||||
import { Timer } from "../../../data/timer";
|
import type { Timer } from "../../../data/timer";
|
||||||
|
|
||||||
export const HELPER_DOMAINS = [
|
export const HELPER_DOMAINS = [
|
||||||
"input_boolean",
|
"input_boolean",
|
||||||
|
@ -8,6 +8,8 @@ import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
|||||||
import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
|
import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
|
||||||
import { domainIcon } from "../../../common/entity/domain_icon";
|
import { domainIcon } from "../../../common/entity/domain_icon";
|
||||||
import "../../../components/ha-dialog";
|
import "../../../components/ha-dialog";
|
||||||
|
import "../../../components/ha-circular-progress";
|
||||||
|
import { getConfigFlowHandlers } from "../../../data/config_flow";
|
||||||
import { createCounter } from "../../../data/counter";
|
import { createCounter } from "../../../data/counter";
|
||||||
import { createInputBoolean } from "../../../data/input_boolean";
|
import { createInputBoolean } from "../../../data/input_boolean";
|
||||||
import { createInputButton } from "../../../data/input_button";
|
import { createInputButton } from "../../../data/input_button";
|
||||||
@ -16,6 +18,7 @@ import { createInputNumber } from "../../../data/input_number";
|
|||||||
import { createInputSelect } from "../../../data/input_select";
|
import { createInputSelect } from "../../../data/input_select";
|
||||||
import { createInputText } from "../../../data/input_text";
|
import { createInputText } from "../../../data/input_text";
|
||||||
import { createTimer } from "../../../data/timer";
|
import { createTimer } from "../../../data/timer";
|
||||||
|
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
|
||||||
import { haStyleDialog } from "../../../resources/styles";
|
import { haStyleDialog } from "../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { Helper } from "./const";
|
import { Helper } from "./const";
|
||||||
@ -27,6 +30,8 @@ import "./forms/ha-input_number-form";
|
|||||||
import "./forms/ha-input_select-form";
|
import "./forms/ha-input_select-form";
|
||||||
import "./forms/ha-input_text-form";
|
import "./forms/ha-input_text-form";
|
||||||
import "./forms/ha-timer-form";
|
import "./forms/ha-timer-form";
|
||||||
|
import { domainToName } from "../../../data/integration";
|
||||||
|
import type { ShowDialogHelperDetailParams } from "./show-dialog-helper-detail";
|
||||||
|
|
||||||
const HELPERS = {
|
const HELPERS = {
|
||||||
input_boolean: createInputBoolean,
|
input_boolean: createInputBoolean,
|
||||||
@ -47,7 +52,7 @@ export class DialogHelperDetail extends LitElement {
|
|||||||
|
|
||||||
@state() private _opened = false;
|
@state() private _opened = false;
|
||||||
|
|
||||||
@state() private _platform?: string;
|
@state() private _domain?: string;
|
||||||
|
|
||||||
@state() private _error?: string;
|
@state() private _error?: string;
|
||||||
|
|
||||||
@ -55,43 +60,39 @@ export class DialogHelperDetail extends LitElement {
|
|||||||
|
|
||||||
@query(".form") private _form?: HTMLDivElement;
|
@query(".form") private _form?: HTMLDivElement;
|
||||||
|
|
||||||
public async showDialog(): Promise<void> {
|
@state() private _helperFlows?: string[];
|
||||||
this._platform = undefined;
|
|
||||||
|
private _params?: ShowDialogHelperDetailParams;
|
||||||
|
|
||||||
|
public async showDialog(params: ShowDialogHelperDetailParams): Promise<void> {
|
||||||
|
this._params = params;
|
||||||
|
this._domain = undefined;
|
||||||
this._item = undefined;
|
this._item = undefined;
|
||||||
this._opened = true;
|
this._opened = true;
|
||||||
await this.updateComplete;
|
await this.updateComplete;
|
||||||
|
Promise.all([
|
||||||
|
getConfigFlowHandlers(this.hass, "helper"),
|
||||||
|
// Ensure the titles are loaded before we render the flows.
|
||||||
|
this.hass.loadBackendTranslation("title", undefined, true),
|
||||||
|
]).then(([flows]) => {
|
||||||
|
this._helperFlows = flows;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeDialog(): void {
|
public closeDialog(): void {
|
||||||
this._opened = false;
|
this._opened = false;
|
||||||
this._error = "";
|
this._error = "";
|
||||||
|
this._params = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
let content: TemplateResult;
|
||||||
<ha-dialog
|
|
||||||
.open=${this._opened}
|
if (this._domain) {
|
||||||
@closed=${this.closeDialog}
|
content = html`
|
||||||
class=${classMap({ "button-left": !this._platform })}
|
|
||||||
scrimClickAction
|
|
||||||
escapeKeyAction
|
|
||||||
.heading=${this._platform
|
|
||||||
? this.hass.localize(
|
|
||||||
"ui.panel.config.helpers.dialog.add_platform",
|
|
||||||
"platform",
|
|
||||||
this.hass.localize(
|
|
||||||
`ui.panel.config.helpers.types.${this._platform}`
|
|
||||||
) || this._platform
|
|
||||||
)
|
|
||||||
: this.hass.localize("ui.panel.config.helpers.dialog.add_helper")}
|
|
||||||
>
|
|
||||||
${this._platform
|
|
||||||
? html`
|
|
||||||
<div class="form" @value-changed=${this._valueChanged}>
|
<div class="form" @value-changed=${this._valueChanged}>
|
||||||
${this._error
|
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||||
? html` <div class="error">${this._error}</div> `
|
${dynamicElement(`ha-${this._domain}-form`, {
|
||||||
: ""}
|
|
||||||
${dynamicElement(`ha-${this._platform}-form`, {
|
|
||||||
hass: this.hass,
|
hass: this.hass,
|
||||||
item: this._item,
|
item: this._item,
|
||||||
new: true,
|
new: true,
|
||||||
@ -111,28 +112,45 @@ export class DialogHelperDetail extends LitElement {
|
|||||||
>
|
>
|
||||||
${this.hass!.localize("ui.common.back")}
|
${this.hass!.localize("ui.common.back")}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
`
|
`;
|
||||||
: html`
|
} else if (this._helperFlows === undefined) {
|
||||||
${Object.keys(HELPERS).map((platform: string) => {
|
content = html`<ha-circular-progress active></ha-circular-progress>`;
|
||||||
const isLoaded = isComponentLoaded(this.hass, platform);
|
} else {
|
||||||
|
const items: [string, string][] = [];
|
||||||
|
|
||||||
|
for (const helper of Object.keys(HELPERS)) {
|
||||||
|
items.push([
|
||||||
|
helper,
|
||||||
|
this.hass.localize(`ui.panel.config.helpers.types.${helper}`) ||
|
||||||
|
helper,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const domain of this._helperFlows) {
|
||||||
|
items.push([domain, domainToName(this.hass.localize, domain)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
items.sort((a, b) => a[1].localeCompare(b[1]));
|
||||||
|
|
||||||
|
content = html`
|
||||||
|
${items.map(([domain, label]) => {
|
||||||
|
// Only OG helpers need to be loaded prior adding one
|
||||||
|
const isLoaded =
|
||||||
|
!(domain in HELPERS) || isComponentLoaded(this.hass, domain);
|
||||||
return html`
|
return html`
|
||||||
<mwc-list-item
|
<mwc-list-item
|
||||||
.disabled=${!isLoaded}
|
.disabled=${!isLoaded}
|
||||||
.platform=${platform}
|
.domain=${domain}
|
||||||
@click=${this._platformPicked}
|
@click=${this._domainPicked}
|
||||||
@keydown=${this._handleEnter}
|
@keydown=${this._handleEnter}
|
||||||
dialogInitialFocus
|
dialogInitialFocus
|
||||||
graphic="icon"
|
graphic="icon"
|
||||||
>
|
>
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
slot="graphic"
|
slot="graphic"
|
||||||
.path=${domainIcon(platform)}
|
.path=${domainIcon(domain)}
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
<span class="item-text">
|
<span class="item-text"> ${label} </span>
|
||||||
${this.hass.localize(
|
|
||||||
`ui.panel.config.helpers.types.${platform}`
|
|
||||||
) || platform}
|
|
||||||
</span>
|
|
||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
${!isLoaded
|
${!isLoaded
|
||||||
? html`
|
? html`
|
||||||
@ -140,7 +158,7 @@ export class DialogHelperDetail extends LitElement {
|
|||||||
>${this.hass.localize(
|
>${this.hass.localize(
|
||||||
"ui.dialogs.helper_settings.platform_not_loaded",
|
"ui.dialogs.helper_settings.platform_not_loaded",
|
||||||
"platform",
|
"platform",
|
||||||
platform
|
domain
|
||||||
)}</paper-tooltip
|
)}</paper-tooltip
|
||||||
>
|
>
|
||||||
`
|
`
|
||||||
@ -150,7 +168,27 @@ export class DialogHelperDetail extends LitElement {
|
|||||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||||
${this.hass!.localize("ui.common.cancel")}
|
${this.hass!.localize("ui.common.cancel")}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
`}
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-dialog
|
||||||
|
.open=${this._opened}
|
||||||
|
@closed=${this.closeDialog}
|
||||||
|
class=${classMap({ "button-left": !this._domain })}
|
||||||
|
scrimClickAction
|
||||||
|
escapeKeyAction
|
||||||
|
.heading=${this._domain
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.helpers.dialog.add_platform",
|
||||||
|
"platform",
|
||||||
|
this.hass.localize(
|
||||||
|
`ui.panel.config.helpers.types.${this._domain}`
|
||||||
|
) || this._domain
|
||||||
|
)
|
||||||
|
: this.hass.localize("ui.panel.config.helpers.dialog.add_helper")}
|
||||||
|
>
|
||||||
|
${content}
|
||||||
</ha-dialog>
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -160,13 +198,13 @@ export class DialogHelperDetail extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _createItem(): Promise<void> {
|
private async _createItem(): Promise<void> {
|
||||||
if (!this._platform || !this._item) {
|
if (!this._domain || !this._item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._submitting = true;
|
this._submitting = true;
|
||||||
this._error = "";
|
this._error = "";
|
||||||
try {
|
try {
|
||||||
await HELPERS[this._platform](this.hass, this._item);
|
await HELPERS[this._domain](this.hass, this._item);
|
||||||
this.closeDialog();
|
this.closeDialog();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this._error = err.message || "Unknown error";
|
this._error = err.message || "Unknown error";
|
||||||
@ -181,12 +219,22 @@ export class DialogHelperDetail extends LitElement {
|
|||||||
}
|
}
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this._platformPicked(ev);
|
this._domainPicked(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _platformPicked(ev: Event): void {
|
private _domainPicked(ev: Event): void {
|
||||||
this._platform = (ev.currentTarget! as any).platform;
|
const domain = (ev.currentTarget! as any).domain;
|
||||||
|
|
||||||
|
if (domain in HELPERS) {
|
||||||
|
this._domain = domain;
|
||||||
this._focusForm();
|
this._focusForm();
|
||||||
|
} else {
|
||||||
|
showConfigFlowDialog(this, {
|
||||||
|
startFlowHandler: domain,
|
||||||
|
dialogClosedCallback: this._params!.dialogClosedCallback,
|
||||||
|
});
|
||||||
|
this.closeDialog();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _focusForm(): Promise<void> {
|
private async _focusForm(): Promise<void> {
|
||||||
@ -195,7 +243,7 @@ export class DialogHelperDetail extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _goBack() {
|
private _goBack() {
|
||||||
this._platform = undefined;
|
this._domain = undefined;
|
||||||
this._item = undefined;
|
this._item = undefined;
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,58 @@
|
|||||||
import { mdiPencilOff, mdiPlus } from "@mdi/js";
|
import { mdiPencilOff, mdiPlus } from "@mdi/js";
|
||||||
import "@polymer/paper-tooltip/paper-tooltip";
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoize from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||||
import { domainIcon } from "../../../common/entity/domain_icon";
|
import { domainIcon } from "../../../common/entity/domain_icon";
|
||||||
|
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||||
import {
|
import {
|
||||||
DataTableColumnContainer,
|
DataTableColumnContainer,
|
||||||
RowClickedEvent,
|
RowClickedEvent,
|
||||||
} from "../../../components/data-table/ha-data-table";
|
} from "../../../components/data-table/ha-data-table";
|
||||||
import "../../../components/ha-fab";
|
import "../../../components/ha-fab";
|
||||||
|
import "../../../components/ha-icon-overflow-menu";
|
||||||
import "../../../components/ha-icon";
|
import "../../../components/ha-icon";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
|
import { ConfigEntry, getConfigEntries } from "../../../data/config_entries";
|
||||||
|
import {
|
||||||
|
EntityRegistryEntry,
|
||||||
|
subscribeEntityRegistry,
|
||||||
|
} from "../../../data/entity_registry";
|
||||||
|
import { domainToName } from "../../../data/integration";
|
||||||
import "../../../layouts/hass-loading-screen";
|
import "../../../layouts/hass-loading-screen";
|
||||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||||
|
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||||
import { HomeAssistant, Route } from "../../../types";
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
import { showEntityEditorDialog } from "../entities/show-dialog-entity-editor";
|
import { showEntityEditorDialog } from "../entities/show-dialog-entity-editor";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
import { HELPER_DOMAINS } from "./const";
|
import { HELPER_DOMAINS } from "./const";
|
||||||
import { showHelperDetailDialog } from "./show-dialog-helper-detail";
|
import { showHelperDetailDialog } from "./show-dialog-helper-detail";
|
||||||
|
|
||||||
|
// This groups items by a key but only returns last entry per key.
|
||||||
|
const groupByOne = <T>(
|
||||||
|
items: T[],
|
||||||
|
keySelector: (item: T) => string
|
||||||
|
): Record<string, T> => {
|
||||||
|
const result: Record<string, T> = {};
|
||||||
|
for (const item of items) {
|
||||||
|
result[keySelector(item)] = item;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getConfigEntry = (
|
||||||
|
entityEntries: Record<string, EntityRegistryEntry>,
|
||||||
|
configEntries: Record<string, ConfigEntry>,
|
||||||
|
entityId: string
|
||||||
|
) => {
|
||||||
|
const configEntryId = entityEntries![entityId]?.config_entry_id;
|
||||||
|
return configEntryId ? configEntries![configEntryId] : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
@customElement("ha-config-helpers")
|
@customElement("ha-config-helpers")
|
||||||
export class HaConfigHelpers extends LitElement {
|
export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public isWide!: boolean;
|
@property() public isWide!: boolean;
|
||||||
@ -33,13 +63,16 @@ export class HaConfigHelpers extends LitElement {
|
|||||||
|
|
||||||
@state() private _stateItems: HassEntity[] = [];
|
@state() private _stateItems: HassEntity[] = [];
|
||||||
|
|
||||||
private _columns = memoize((narrow, _language): DataTableColumnContainer => {
|
@state() private _entityEntries?: Record<string, EntityRegistryEntry>;
|
||||||
|
|
||||||
|
@state() private _configEntries?: Record<string, ConfigEntry>;
|
||||||
|
|
||||||
|
private _columns = memoizeOne(
|
||||||
|
(narrow: boolean, localize: LocalizeFunc): DataTableColumnContainer => {
|
||||||
const columns: DataTableColumnContainer = {
|
const columns: DataTableColumnContainer = {
|
||||||
icon: {
|
icon: {
|
||||||
title: "",
|
title: "",
|
||||||
label: this.hass.localize(
|
label: localize("ui.panel.config.helpers.picker.headers.icon"),
|
||||||
"ui.panel.config.helpers.picker.headers.icon"
|
|
||||||
),
|
|
||||||
type: "icon",
|
type: "icon",
|
||||||
template: (icon, helper: any) =>
|
template: (icon, helper: any) =>
|
||||||
icon
|
icon
|
||||||
@ -49,9 +82,7 @@ export class HaConfigHelpers extends LitElement {
|
|||||||
></ha-svg-icon>`,
|
></ha-svg-icon>`,
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
title: this.hass.localize(
|
title: localize("ui.panel.config.helpers.picker.headers.name"),
|
||||||
"ui.panel.config.helpers.picker.headers.name"
|
|
||||||
),
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
grows: true,
|
grows: true,
|
||||||
@ -67,22 +98,22 @@ export class HaConfigHelpers extends LitElement {
|
|||||||
};
|
};
|
||||||
if (!narrow) {
|
if (!narrow) {
|
||||||
columns.entity_id = {
|
columns.entity_id = {
|
||||||
title: this.hass.localize(
|
title: localize("ui.panel.config.helpers.picker.headers.entity_id"),
|
||||||
"ui.panel.config.helpers.picker.headers.entity_id"
|
|
||||||
),
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
width: "25%",
|
width: "25%",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
columns.type = {
|
columns.type = {
|
||||||
title: this.hass.localize("ui.panel.config.helpers.picker.headers.type"),
|
title: localize("ui.panel.config.helpers.picker.headers.type"),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
width: "25%",
|
width: "25%",
|
||||||
filterable: true,
|
filterable: true,
|
||||||
template: (type) =>
|
template: (type, row) =>
|
||||||
html`
|
row.configEntry
|
||||||
${this.hass.localize(`ui.panel.config.helpers.types.${type}`) || type}
|
? domainToName(localize, type)
|
||||||
|
: html`
|
||||||
|
${localize(`ui.panel.config.helpers.types.${type}`) || type}
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
columns.editable = {
|
columns.editable = {
|
||||||
@ -110,21 +141,44 @@ export class HaConfigHelpers extends LitElement {
|
|||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
return columns;
|
return columns;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
private _getItems = memoize((stateItems: HassEntity[]) =>
|
private _getItems = memoizeOne(
|
||||||
stateItems.map((entityState) => ({
|
(
|
||||||
|
stateItems: HassEntity[],
|
||||||
|
entityEntries: Record<string, EntityRegistryEntry>,
|
||||||
|
configEntries: Record<string, ConfigEntry>
|
||||||
|
) =>
|
||||||
|
stateItems.map((entityState) => {
|
||||||
|
const configEntry = getConfigEntry(
|
||||||
|
entityEntries,
|
||||||
|
configEntries,
|
||||||
|
entityState.entity_id
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
id: entityState.entity_id,
|
id: entityState.entity_id,
|
||||||
icon: entityState.attributes.icon,
|
icon: entityState.attributes.icon,
|
||||||
name: entityState.attributes.friendly_name || "",
|
name: entityState.attributes.friendly_name || "",
|
||||||
entity_id: entityState.entity_id,
|
entity_id: entityState.entity_id,
|
||||||
editable: entityState.attributes.editable,
|
editable:
|
||||||
type: computeStateDomain(entityState),
|
configEntry !== undefined || entityState.attributes.editable,
|
||||||
}))
|
type: configEntry
|
||||||
|
? configEntry.domain
|
||||||
|
: computeStateDomain(entityState),
|
||||||
|
configEntry,
|
||||||
|
};
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.hass || this._stateItems === undefined) {
|
if (
|
||||||
|
!this.hass ||
|
||||||
|
this._stateItems === undefined ||
|
||||||
|
this._entityEntries === undefined ||
|
||||||
|
this._configEntries === undefined
|
||||||
|
) {
|
||||||
return html` <hass-loading-screen></hass-loading-screen> `;
|
return html` <hass-loading-screen></hass-loading-screen> `;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,8 +189,12 @@ export class HaConfigHelpers extends LitElement {
|
|||||||
back-path="/config"
|
back-path="/config"
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.tabs=${configSections.automations}
|
.tabs=${configSections.automations}
|
||||||
.columns=${this._columns(this.narrow, this.hass.language)}
|
.columns=${this._columns(this.narrow, this.hass.localize)}
|
||||||
.data=${this._getItems(this._stateItems)}
|
.data=${this._getItems(
|
||||||
|
this._stateItems,
|
||||||
|
this._entityEntries,
|
||||||
|
this._configEntries
|
||||||
|
)}
|
||||||
@row-click=${this._openEditDialog}
|
@row-click=${this._openEditDialog}
|
||||||
hasFab
|
hasFab
|
||||||
clickable
|
clickable
|
||||||
@ -160,34 +218,69 @@ export class HaConfigHelpers extends LitElement {
|
|||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
this._getStates();
|
this._getConfigEntries();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
protected willUpdate(changedProps: PropertyValues) {
|
||||||
super.updated(changedProps);
|
super.willUpdate(changedProps);
|
||||||
|
|
||||||
|
if (!this._entityEntries || !this._configEntries) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let changed =
|
||||||
|
!this._stateItems ||
|
||||||
|
changedProps.has("_entityEntries") ||
|
||||||
|
changedProps.has("_configEntries");
|
||||||
|
|
||||||
|
if (!changed && changedProps.has("hass")) {
|
||||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||||
if (oldHass && this._stateItems) {
|
changed = !oldHass || oldHass.states !== this.hass.states;
|
||||||
this._getStates(oldHass);
|
}
|
||||||
|
if (!changed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const extraEntities = new Set<string>();
|
||||||
|
|
||||||
|
for (const entityEntry of Object.values(this._entityEntries)) {
|
||||||
|
if (
|
||||||
|
entityEntry.config_entry_id &&
|
||||||
|
entityEntry.config_entry_id in this._configEntries
|
||||||
|
) {
|
||||||
|
extraEntities.add(entityEntry.entity_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getStates(oldHass?: HomeAssistant) {
|
const newStates = Object.values(this.hass!.states).filter(
|
||||||
let changed = false;
|
(entity) =>
|
||||||
const tempStates = Object.values(this.hass!.states).filter((entity) => {
|
extraEntities.has(entity.entity_id) ||
|
||||||
if (!HELPER_DOMAINS.includes(computeStateDomain(entity))) {
|
HELPER_DOMAINS.includes(computeStateDomain(entity))
|
||||||
return false;
|
);
|
||||||
}
|
|
||||||
if (oldHass?.states[entity.entity_id] !== entity) {
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (changed || this._stateItems.length !== tempStates.length) {
|
if (
|
||||||
this._stateItems = tempStates;
|
this._stateItems.length !== newStates.length ||
|
||||||
|
!this._stateItems.every((val, idx) => newStates[idx] === val)
|
||||||
|
) {
|
||||||
|
this._stateItems = newStates;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
|
return [
|
||||||
|
subscribeEntityRegistry(this.hass.connection!, (entries) => {
|
||||||
|
this._entityEntries = groupByOne(entries, (entry) => entry.entity_id);
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _getConfigEntries() {
|
||||||
|
this._configEntries = groupByOne(
|
||||||
|
await getConfigEntries(this.hass, { type: "helper" }),
|
||||||
|
(entry) => entry.entry_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private async _openEditDialog(ev: CustomEvent): Promise<void> {
|
private async _openEditDialog(ev: CustomEvent): Promise<void> {
|
||||||
const entityId = (ev.detail as RowClickedEvent).id;
|
const entityId = (ev.detail as RowClickedEvent).id;
|
||||||
showEntityEditorDialog(this, {
|
showEntityEditorDialog(this, {
|
||||||
@ -196,6 +289,12 @@ export class HaConfigHelpers extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _createHelpler() {
|
private _createHelpler() {
|
||||||
showHelperDetailDialog(this);
|
showHelperDetailDialog(this, {
|
||||||
|
dialogClosedCallback: (params) => {
|
||||||
|
if (params.flowFinished) {
|
||||||
|
this._getConfigEntries();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import { DataEntryFlowDialogParams } from "../../../dialogs/config-flow/show-dialog-data-entry-flow";
|
||||||
|
|
||||||
export const loadHelperDetailDialog = () => import("./dialog-helper-detail");
|
export const loadHelperDetailDialog = () => import("./dialog-helper-detail");
|
||||||
|
|
||||||
export const showHelperDetailDialog = (element: HTMLElement) => {
|
export interface ShowDialogHelperDetailParams {
|
||||||
|
// Only used for config entries
|
||||||
|
dialogClosedCallback: DataEntryFlowDialogParams["dialogClosedCallback"];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const showHelperDetailDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
params: ShowDialogHelperDetailParams
|
||||||
|
) => {
|
||||||
fireEvent(element, "show-dialog", {
|
fireEvent(element, "show-dialog", {
|
||||||
dialogTag: "dialog-helper-detail",
|
dialogTag: "dialog-helper-detail",
|
||||||
dialogImport: loadHelperDetailDialog,
|
dialogImport: loadHelperDetailDialog,
|
||||||
dialogParams: {},
|
dialogParams: params,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -521,7 +521,8 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _loadConfigEntries() {
|
private _loadConfigEntries() {
|
||||||
getConfigEntries(this.hass).then((configEntries) => {
|
getConfigEntries(this.hass, { type: "integration" }).then(
|
||||||
|
(configEntries) => {
|
||||||
this._configEntries = configEntries
|
this._configEntries = configEntries
|
||||||
.map(
|
.map(
|
||||||
(entry: ConfigEntry): ConfigEntryExtended => ({
|
(entry: ConfigEntry): ConfigEntryExtended => ({
|
||||||
@ -538,7 +539,8 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
conf2.localized_domain_name + conf2.title
|
conf2.localized_domain_name + conf2.title
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _scanUSBDevices() {
|
private async _scanUSBDevices() {
|
||||||
@ -656,7 +658,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
if (!domain) {
|
if (!domain) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const handlers = await getConfigFlowHandlers(this.hass);
|
const handlers = await getConfigFlowHandlers(this.hass, "integration");
|
||||||
|
|
||||||
if (!handlers.includes(domain)) {
|
if (!handlers.includes(domain)) {
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
|
@ -111,7 +111,9 @@ class HaPanelDevMqtt extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const configEntryId = searchParams.get("config_entry") as string;
|
const configEntryId = searchParams.get("config_entry") as string;
|
||||||
const configEntries = await getConfigEntries(this.hass);
|
const configEntries = await getConfigEntries(this.hass, {
|
||||||
|
domain: "mqtt",
|
||||||
|
});
|
||||||
const configEntry = configEntries.find(
|
const configEntry = configEntries.find(
|
||||||
(entry) => entry.entry_id === configEntryId
|
(entry) => entry.entry_id === configEntryId
|
||||||
);
|
);
|
||||||
|
@ -384,7 +384,9 @@ class ZWaveJSConfigDashboard extends LitElement {
|
|||||||
if (!this.configEntryId) {
|
if (!this.configEntryId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const configEntries = await getConfigEntries(this.hass);
|
const configEntries = await getConfigEntries(this.hass, {
|
||||||
|
domain: "zwave_js",
|
||||||
|
});
|
||||||
this._configEntry = configEntries.find(
|
this._configEntry = configEntries.find(
|
||||||
(entry) => entry.entry_id === this.configEntryId!
|
(entry) => entry.entry_id === this.configEntryId!
|
||||||
);
|
);
|
||||||
@ -467,7 +469,9 @@ class ZWaveJSConfigDashboard extends LitElement {
|
|||||||
if (!this.configEntryId) {
|
if (!this.configEntryId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const configEntries = await getConfigEntries(this.hass);
|
const configEntries = await getConfigEntries(this.hass, {
|
||||||
|
domain: "zwave_js",
|
||||||
|
});
|
||||||
const configEntry = configEntries.find(
|
const configEntry = configEntries.find(
|
||||||
(entry) => entry.entry_id === this.configEntryId
|
(entry) => entry.entry_id === this.configEntryId
|
||||||
);
|
);
|
||||||
|
@ -823,7 +823,8 @@
|
|||||||
"area": "Set entity area only",
|
"area": "Set entity area only",
|
||||||
"area_note": "By default the entities of a device are in the same area as the device. If you change the area of this entity, it will no longer follow the area of the device.",
|
"area_note": "By default the entities of a device are in the same area as the device. If you change the area of this entity, it will no longer follow the area of the device.",
|
||||||
"follow_device_area": "Follow device area",
|
"follow_device_area": "Follow device area",
|
||||||
"change_device_area": "Change device area"
|
"change_device_area": "Change device area",
|
||||||
|
"configure_state": "Configure State"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"helper_settings": {
|
"helper_settings": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user