): boolean => {
if (typeof obj !== "object") {
@@ -44,8 +34,6 @@ export class HaYamlEditor extends LitElement {
@internalProperty() private _yaml = "";
- @query("ha-code-editor") private _editor?: HaCodeEditor;
-
public setValue(value): void {
try {
this._yaml = value && !isEmpty(value) ? safeDump(value) : "";
@@ -54,12 +42,6 @@ export class HaYamlEditor extends LitElement {
console.error(err, value);
alert(`There was an error converting to YAML: ${err}`);
}
- afterNextRender(() => {
- if (this._editor?.codemirror) {
- this._editor.codemirror.refresh();
- }
- afterNextRender(() => fireEvent(this, "editor-refreshed"));
- });
}
protected firstUpdated(): void {
diff --git a/src/components/state-history-charts.ts b/src/components/state-history-charts.ts
index a8faeb80d9..49ac8049c5 100644
--- a/src/components/state-history-charts.ts
+++ b/src/components/state-history-charts.ts
@@ -6,6 +6,7 @@ import {
html,
LitElement,
property,
+ PropertyValues,
TemplateResult,
} from "lit-element";
import "./state-history-chart-line";
@@ -83,6 +84,10 @@ class StateHistoryCharts extends LitElement {
`;
}
+ protected shouldUpdate(changedProps: PropertyValues): boolean {
+ return !(changedProps.size === 1 && changedProps.has("hass"));
+ }
+
private _isHistoryEmpty(): boolean {
const historyDataEmpty =
!this.historyData ||
diff --git a/src/data/config_entries.ts b/src/data/config_entries.ts
index 925d9cc9b1..d75de78536 100644
--- a/src/data/config_entries.ts
+++ b/src/data/config_entries.ts
@@ -9,6 +9,7 @@ export interface ConfigEntry {
connection_class: string;
supports_options: boolean;
supports_unload: boolean;
+ disabled_by: string | null;
}
export interface ConfigEntryMutableParams {
@@ -43,6 +44,27 @@ export const reloadConfigEntry = (hass: HomeAssistant, configEntryId: string) =>
require_restart: boolean;
}>("POST", `config/config_entries/entry/${configEntryId}/reload`);
+export const disableConfigEntry = (
+ hass: HomeAssistant,
+ configEntryId: string
+) =>
+ hass.callWS<{
+ require_restart: boolean;
+ }>({
+ type: "config_entries/disable",
+ entry_id: configEntryId,
+ disabled_by: "user",
+ });
+
+export const enableConfigEntry = (hass: HomeAssistant, configEntryId: string) =>
+ hass.callWS<{
+ require_restart: boolean;
+ }>({
+ type: "config_entries/disable",
+ entry_id: configEntryId,
+ disabled_by: null,
+ });
+
export const getConfigEntrySystemOptions = (
hass: HomeAssistant,
configEntryId: string
diff --git a/src/data/selector.ts b/src/data/selector.ts
index a3eedee92d..7722d56de8 100644
--- a/src/data/selector.ts
+++ b/src/data/selector.ts
@@ -1,4 +1,5 @@
export type Selector =
+ | AddonSelector
| EntitySelector
| DeviceSelector
| AreaSelector
@@ -30,6 +31,13 @@ export interface DeviceSelector {
};
}
+export interface AddonSelector {
+ addon: {
+ name?: string;
+ slug?: string;
+ };
+}
+
export interface AreaSelector {
area: {
entity?: {
diff --git a/src/dialogs/more-info/controls/more-info-fan.js b/src/dialogs/more-info/controls/more-info-fan.js
index a5b1b5df52..93bbe59396 100644
--- a/src/dialogs/more-info/controls/more-info-fan.js
+++ b/src/dialogs/more-info/controls/more-info-fan.js
@@ -52,6 +52,7 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
caption="[[localize('ui.card.fan.speed')]]"
min="0"
max="100"
+ step="[[computePercentageStepSize(stateObj)]]"
value="{{percentageSliderValue}}"
on-change="percentageChanged"
pin=""
@@ -113,7 +114,7 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
`;
}
@@ -154,6 +155,13 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
}
}
+ computePercentageStepSize(stateObj) {
+ if (stateObj.attributes.percentage_step) {
+ return stateObj.attributes.percentage_step;
+ }
+ return 1;
+ }
+
computeClassNames(stateObj) {
return (
"more-info-fan " +
diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts
index 9aef24e530..6c882bb6a3 100644
--- a/src/panels/config/devices/ha-config-device-page.ts
+++ b/src/panels/config/devices/ha-config-device-page.ts
@@ -12,13 +12,14 @@ import {
import { ifDefined } from "lit-html/directives/if-defined";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
+import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { compare } from "../../../common/string/compare";
import { slugify } from "../../../common/string/slugify";
import "../../../components/entity/ha-battery-icon";
import "../../../components/ha-icon-next";
import { AreaRegistryEntry } from "../../../data/area_registry";
-import { ConfigEntry } from "../../../data/config_entries";
+import { ConfigEntry, disableConfigEntry } from "../../../data/config_entries";
import {
computeDeviceName,
DeviceRegistryEntry,
@@ -160,6 +161,8 @@ export class HaConfigDevicePage extends LitElement {
const batteryState = batteryEntity
? this.hass.states[batteryEntity.entity_id]
: undefined;
+ const batteryIsBinary = batteryState
+ && computeStateDomain(batteryState) === "binary_sensor";
const batteryChargingState = batteryChargingEntity
? this.hass.states[batteryChargingEntity.entity_id]
: undefined;
@@ -215,7 +218,7 @@ export class HaConfigDevicePage extends LitElement {
batteryState
? html`
- ${batteryState.state}%
+ ${batteryIsBinary ? "" : batteryState.state + "%"}
-
-
- ${this.hass.localize("ui.common.enable")}
-
-
+ ${device.disabled_by === "user"
+ ? html`
+
+ ${this.hass.localize("ui.common.enable")}
+
+
`
+ : ""}
`
: html``
}
@@ -626,6 +631,41 @@ export class HaConfigDevicePage extends LitElement {
updateEntry: async (updates) => {
const oldDeviceName = device.name_by_user || device.name;
const newDeviceName = updates.name_by_user;
+ const disabled =
+ updates.disabled_by === "user" && device.disabled_by !== "user";
+
+ if (disabled) {
+ for (const cnfg_entry of device.config_entries) {
+ if (
+ !this.devices.some(
+ (dvc) =>
+ dvc.id !== device.id &&
+ dvc.config_entries.includes(cnfg_entry)
+ )
+ ) {
+ const config_entry = this.entries.find(
+ (entry) => entry.entry_id === cnfg_entry
+ );
+ if (
+ config_entry &&
+ !config_entry.disabled_by &&
+ // eslint-disable-next-line no-await-in-loop
+ (await showConfirmationDialog(this, {
+ title: this.hass.localize(
+ "ui.panel.config.devices.confirm_disable_config_entry",
+ "entry_name",
+ config_entry.title
+ ),
+ confirmText: this.hass.localize("ui.common.yes"),
+ dismissText: this.hass.localize("ui.common.no"),
+ }))
+ ) {
+ disableConfigEntry(this.hass, cnfg_entry);
+ delete updates.disabled_by;
+ }
+ }
+ }
+ }
await updateDeviceRegistryEntry(this.hass, this.deviceId, updates);
if (
diff --git a/src/panels/config/devices/ha-config-devices-dashboard.ts b/src/panels/config/devices/ha-config-devices-dashboard.ts
index ca82762c01..3c09085b28 100644
--- a/src/panels/config/devices/ha-config-devices-dashboard.ts
+++ b/src/panels/config/devices/ha-config-devices-dashboard.ts
@@ -15,6 +15,7 @@ import {
import { classMap } from "lit-html/directives/class-map";
import memoizeOne from "memoize-one";
import { HASSDomEvent } from "../../../common/dom/fire_event";
+import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { navigate } from "../../../common/navigate";
import { LocalizeFunc } from "../../../common/translations/localize";
import { computeRTL } from "../../../common/util/compute_rtl";
@@ -293,9 +294,11 @@ export class HaConfigDeviceDashboard extends LitElement {
batteryEntityPair && batteryEntityPair[1]
? this.hass.states[batteryEntityPair[1]]
: undefined;
- return battery && !isNaN(battery.state as any)
+ const batteryIsBinary =
+ battery && computeStateDomain(battery) === "binary_sensor";
+ return battery && (batteryIsBinary || !isNaN(battery.state as any))
? html`
- ${battery.state}%
+ ${batteryIsBinary ? "" : battery.state + "%"}
, ConfigEntryExtended[]] => {
+ ): [
+ Map,
+ ConfigEntryExtended[],
+ Map
+ ] => {
const filteredConfigEnties = this._filterConfigEntries(
configEntries,
filter
);
const ignored: ConfigEntryExtended[] = [];
+ const disabled: ConfigEntryExtended[] = [];
for (let i = filteredConfigEnties.length - 1; i >= 0; i--) {
if (filteredConfigEnties[i].source === "ignore") {
ignored.push(filteredConfigEnties.splice(i, 1)[0]);
}
+ if (filteredConfigEnties[i].disabled_by !== null) {
+ disabled.push(filteredConfigEnties.splice(i, 1)[0]);
+ }
}
- return [groupByIntegration(filteredConfigEnties), ignored];
+ return [
+ groupByIntegration(filteredConfigEnties),
+ ignored,
+ groupByIntegration(disabled),
+ ];
}
);
@@ -254,6 +269,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
const [
groupedConfigEntries,
ignoredConfigEntries,
+ disabledConfigEntries,
] = this._filterGroupConfigEntries(this._configEntries, this._filter);
const configEntriesInProgress = this._filterConfigEntriesInProgress(
this._configEntriesInProgress,
@@ -289,7 +305,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
+
+ ${this.hass.localize(
+ this._showDisabled
+ ? "ui.panel.config.integrations.disable.hide_disabled"
+ : "ui.panel.config.integrations.disable.show_disabled"
+ )}
+
${!this.narrow
@@ -319,6 +342,20 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
"ui.panel.config.integrations.search"
)}
>
+ ${!this._showDisabled && disabledConfigEntries.size
+ ? html`
+ ${this.hass.localize(
+ "ui.panel.config.integrations.disable.disabled_integrations",
+ "number",
+ disabledConfigEntries.size
+ )}
+ ${this.hass.localize(
+ "ui.panel.config.filtering.show"
+ )}
+
`
+ : ""}
`
: ""}
@@ -433,6 +470,21 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
}
)
: ""}
+ ${this._showDisabled
+ ? Array.from(disabledConfigEntries.entries()).map(
+ ([domain, items]) =>
+ html` `
+ )
+ : ""}
${groupedConfigEntries.size
? Array.from(groupedConfigEntries.entries()).map(
([domain, items]) =>
@@ -596,10 +648,25 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
getConfigFlowInProgressCollection(this.hass.connection).refresh();
}
+ private _handleMenuAction(ev: CustomEvent) {
+ switch (ev.detail.index) {
+ case 0:
+ this._toggleShowIgnored();
+ break;
+ case 1:
+ this._toggleShowDisabled();
+ break;
+ }
+ }
+
private _toggleShowIgnored() {
this._showIgnored = !this._showIgnored;
}
+ private _toggleShowDisabled() {
+ this._showDisabled = !this._showDisabled;
+ }
+
private async _removeIgnoredIntegration(ev: Event) {
const entry = (ev.target! as any).entry;
showConfirmationDialog(this, {
@@ -767,11 +834,14 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
margin-left: 16px;
}
.search {
+ display: flex;
+ align-items: center;
padding: 0 16px;
background: var(--sidebar-background-color);
border-bottom: 1px solid var(--divider-color);
}
.search search-input {
+ flex: 1;
position: relative;
top: 2px;
}
@@ -796,6 +866,32 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
text-overflow: ellipsis;
white-space: normal;
}
+ .active-filters {
+ color: var(--primary-text-color);
+ position: relative;
+ display: flex;
+ align-items: center;
+ padding: 2px 2px 2px 8px;
+ margin-left: 4px;
+ font-size: 14px;
+ }
+ .active-filters ha-icon {
+ color: var(--primary-color);
+ }
+ .active-filters mwc-button {
+ margin-left: 8px;
+ }
+ .active-filters::before {
+ background-color: var(--primary-color);
+ opacity: 0.12;
+ border-radius: 4px;
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ content: "";
+ }
`,
];
}
diff --git a/src/panels/config/integrations/ha-integration-card.ts b/src/panels/config/integrations/ha-integration-card.ts
index 178869c026..09d9f6f859 100644
--- a/src/panels/config/integrations/ha-integration-card.ts
+++ b/src/panels/config/integrations/ha-integration-card.ts
@@ -9,12 +9,15 @@ import {
property,
TemplateResult,
} from "lit-element";
+import { classMap } from "lit-html/directives/class-map";
import { fireEvent } from "../../../common/dom/fire_event";
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
import "../../../components/ha-icon-next";
import {
ConfigEntry,
deleteConfigEntry,
+ disableConfigEntry,
+ enableConfigEntry,
reloadConfigEntry,
updateConfigEntry,
} from "../../../data/config_entries";
@@ -88,6 +91,8 @@ export class HaIntegrationCard extends LitElement {
@property() public selectedConfigEntryId?: string;
+ @property({ type: Boolean }) public disabled = false;
+
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
}
@@ -109,7 +114,14 @@ export class HaIntegrationCard extends LitElement {
private _renderGroupedIntegration(): TemplateResult {
return html`
-
+
+ ${this.disabled
+ ? html``
+ : ""}