mirror of
https://github.com/home-assistant/frontend.git
synced 2026-05-01 15:03:05 +00:00
Compare commits
2 Commits
missing-to
...
energy-bat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45823fe4a8 | ||
|
|
76568379e7 |
@@ -201,7 +201,7 @@
|
||||
"terser-webpack-plugin": "5.5.0",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "8.59.1",
|
||||
"typescript-eslint": "8.59.0",
|
||||
"vite-tsconfig-paths": "6.1.1",
|
||||
"vitest": "4.1.5",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
import {
|
||||
mdiBattery,
|
||||
mdiBattery10,
|
||||
mdiBattery20,
|
||||
mdiBattery30,
|
||||
mdiBattery40,
|
||||
mdiBattery50,
|
||||
mdiBattery60,
|
||||
mdiBattery70,
|
||||
mdiBattery80,
|
||||
mdiBattery90,
|
||||
mdiBatteryAlertVariantOutline,
|
||||
mdiBatteryUnknown,
|
||||
} from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
|
||||
const BATTERY_ICONS = {
|
||||
@@ -12,6 +26,18 @@ const BATTERY_ICONS = {
|
||||
90: "mdi:battery-90",
|
||||
100: "mdi:battery",
|
||||
};
|
||||
const BATTERY_ICON_PATHS = {
|
||||
10: mdiBattery10,
|
||||
20: mdiBattery20,
|
||||
30: mdiBattery30,
|
||||
40: mdiBattery40,
|
||||
50: mdiBattery50,
|
||||
60: mdiBattery60,
|
||||
70: mdiBattery70,
|
||||
80: mdiBattery80,
|
||||
90: mdiBattery90,
|
||||
100: mdiBattery,
|
||||
};
|
||||
const BATTERY_CHARGING_ICONS = {
|
||||
10: "mdi:battery-charging-10",
|
||||
20: "mdi:battery-charging-20",
|
||||
@@ -57,3 +83,15 @@ export const batteryLevelIcon = (
|
||||
}
|
||||
return BATTERY_ICONS[batteryRound];
|
||||
};
|
||||
|
||||
export const batteryLevelIconPath = (batteryLevel: number | string): string => {
|
||||
const batteryValue = Number(batteryLevel);
|
||||
if (isNaN(batteryValue)) {
|
||||
return mdiBatteryUnknown;
|
||||
}
|
||||
if (batteryValue <= 5) {
|
||||
return mdiBatteryAlertVariantOutline;
|
||||
}
|
||||
const batteryRound = Math.round(batteryValue / 10) * 10;
|
||||
return BATTERY_ICON_PATHS[batteryRound];
|
||||
};
|
||||
|
||||
@@ -1499,7 +1499,6 @@ export class HaChartBase extends LitElement {
|
||||
margin-inline-start: var(--ha-space-1);
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
line-height: 1;
|
||||
}
|
||||
.chart-legend .legend-toggle {
|
||||
background: none;
|
||||
|
||||
@@ -160,8 +160,6 @@ export class HaEntityToggle extends LitElement {
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
min-width: 38px;
|
||||
}
|
||||
|
||||
@@ -189,20 +189,6 @@ export const updateBackupConfig = (
|
||||
config: BackupMutableConfig
|
||||
) => hass.callWS({ type: "backup/config/update", ...config });
|
||||
|
||||
export const saveBackupConfig = (hass: HomeAssistant, config: BackupConfig) =>
|
||||
updateBackupConfig(hass, {
|
||||
create_backup: {
|
||||
agent_ids: config.create_backup.agent_ids,
|
||||
include_folders: config.create_backup.include_folders ?? [],
|
||||
include_database: config.create_backup.include_database,
|
||||
include_addons: config.create_backup.include_addons ?? [],
|
||||
include_all_addons: config.create_backup.include_all_addons,
|
||||
password: config.create_backup.password,
|
||||
},
|
||||
retention: config.retention,
|
||||
schedule: config.schedule,
|
||||
});
|
||||
|
||||
export const getBackupDownloadUrl = (
|
||||
id: string,
|
||||
agentId: string,
|
||||
|
||||
@@ -164,6 +164,7 @@ export interface BatterySourceTypeEnergyPreference {
|
||||
stat_energy_to: string;
|
||||
stat_rate?: string; // always available if power_config is set
|
||||
power_config?: PowerConfig;
|
||||
stat_soc?: string;
|
||||
}
|
||||
export interface GasSourceTypeEnergyPreference {
|
||||
type: "gas";
|
||||
|
||||
@@ -134,7 +134,6 @@ type AutomationItem = AutomationEntity & {
|
||||
formatted_state: string;
|
||||
category: string | undefined;
|
||||
label_entries: LabelRegistryEntry[];
|
||||
labels: string[]; // search only
|
||||
assistants: string[];
|
||||
assistants_sortable_key: string | undefined;
|
||||
};
|
||||
@@ -257,9 +256,6 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
const category = entityRegEntry?.categories.automation;
|
||||
const labels = labelReg && entityRegEntry?.labels;
|
||||
const label_entries = (labels || [])
|
||||
.map((lbl) => labelReg!.find((label) => label.label_id === lbl)!)
|
||||
.filter(Boolean);
|
||||
const assistants = getEntityVoiceAssistantsIds(
|
||||
entityReg,
|
||||
automation.entity_id
|
||||
@@ -275,8 +271,9 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
||||
category: category
|
||||
? categoryReg?.find((cat) => cat.category_id === category)?.name
|
||||
: undefined,
|
||||
label_entries,
|
||||
labels: label_entries.map((lbl) => lbl.name),
|
||||
label_entries: (labels || [])
|
||||
.map((lbl) => labelReg!.find((label) => label.label_id === lbl)!)
|
||||
.filter(Boolean),
|
||||
assistants,
|
||||
assistants_sortable_key: getAssistantsSortableKey(assistants),
|
||||
selectable: entityRegEntry !== undefined,
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
import { mdiPuzzle } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import {
|
||||
getSupervisorUpdateConfig,
|
||||
type SupervisorUpdateConfig,
|
||||
} from "../../../../../data/supervisor/update";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
|
||||
@customElement("ha-backup-overview-app-update-backup")
|
||||
class HaBackupOverviewAppUpdateBackup extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _supervisorUpdateConfig?: SupervisorUpdateConfig;
|
||||
|
||||
protected firstUpdated() {
|
||||
this._fetchSupervisorUpdateConfig();
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this.hasUpdated) {
|
||||
this._fetchSupervisorUpdateConfig();
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchSupervisorUpdateConfig() {
|
||||
try {
|
||||
this._supervisorUpdateConfig = await getSupervisorUpdateConfig(this.hass);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
private _appUpdateBackupDescription() {
|
||||
if (!this._supervisorUpdateConfig) {
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.settings.app_update_backup.local_only"
|
||||
);
|
||||
}
|
||||
|
||||
if (!this._supervisorUpdateConfig.add_on_backup_before_update) {
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.backup.schedule.update_preference.skip_backups"
|
||||
);
|
||||
}
|
||||
|
||||
const copies =
|
||||
this._supervisorUpdateConfig.add_on_backup_retain_copies || 1;
|
||||
|
||||
return `${this.hass.localize(
|
||||
"ui.panel.config.backup.schedule.update_preference.backup_before_update"
|
||||
)} ${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.settings.schedule_copies_backups",
|
||||
{ count: copies }
|
||||
)}`;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.app_update_backup.title"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<ha-md-list>
|
||||
<ha-md-list-item
|
||||
type="link"
|
||||
href="/config/backup/app-update-backups"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPuzzle}></ha-svg-icon>
|
||||
<div slot="headline">${this._appUpdateBackupDescription()}</div>
|
||||
<div slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.overview.app_update_backup.description"
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.card-header {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-backup-overview-app-update-backup": HaBackupOverviewAppUpdateBackup;
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-card";
|
||||
import {
|
||||
getSupervisorUpdateConfig,
|
||||
updateSupervisorUpdateConfig,
|
||||
type SupervisorUpdateConfig,
|
||||
} from "../../../data/supervisor/update";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "./components/config/ha-backup-config-addon";
|
||||
|
||||
@customElement("ha-config-backup-app-update-backups")
|
||||
class HaConfigBackupAppUpdateBackups extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@state() private _supervisorUpdateConfig?: SupervisorUpdateConfig;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (
|
||||
!this.hasUpdated &&
|
||||
this.hass &&
|
||||
isComponentLoaded(this.hass.config, "hassio")
|
||||
) {
|
||||
this._getSupervisorUpdateConfig();
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<hass-subpage
|
||||
back-path="/config/backup/overview"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.backup.app_update_backups.header"
|
||||
)}
|
||||
>
|
||||
<div class="content">
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.app_update_backup.description"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.app_update_backup.local_only"
|
||||
)}
|
||||
</p>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: nothing}
|
||||
<ha-backup-config-addon
|
||||
.hass=${this.hass}
|
||||
.supervisorUpdateConfig=${this._supervisorUpdateConfig}
|
||||
@update-config-changed=${this._supervisorUpdateConfigChanged}
|
||||
></ha-backup-config-addon>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _getSupervisorUpdateConfig() {
|
||||
try {
|
||||
this._supervisorUpdateConfig = await getSupervisorUpdateConfig(this.hass);
|
||||
this._error = undefined;
|
||||
} catch (err: any) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
this._error = this.hass.localize(
|
||||
"ui.panel.config.backup.settings.app_update_backup.error_load",
|
||||
{
|
||||
error: err?.message || err,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async _supervisorUpdateConfigChanged(ev) {
|
||||
const config = ev.detail.value as SupervisorUpdateConfig;
|
||||
this._supervisorUpdateConfig = {
|
||||
...this._supervisorUpdateConfig,
|
||||
...config,
|
||||
} as SupervisorUpdateConfig;
|
||||
this._debounceSaveSupervisorUpdateConfig();
|
||||
}
|
||||
|
||||
private _debounceSaveSupervisorUpdateConfig = debounce(
|
||||
() => this._saveSupervisorUpdateConfig(),
|
||||
500
|
||||
);
|
||||
|
||||
private async _saveSupervisorUpdateConfig() {
|
||||
if (!this._supervisorUpdateConfig) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await updateSupervisorUpdateConfig(
|
||||
this.hass,
|
||||
this._supervisorUpdateConfig
|
||||
);
|
||||
this._error = undefined;
|
||||
} catch (err: any) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
this._error = this.hass.localize(
|
||||
"ui.panel.config.backup.settings.app_update_backup.error_save",
|
||||
{
|
||||
error: err?.message || err?.toString(),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
p {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 28px 20px 0;
|
||||
max-width: 690px;
|
||||
margin: 0 auto;
|
||||
gap: var(--ha-space-6);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-config-backup-app-update-backups": HaConfigBackupAppUpdateBackups;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
import { mdiDotsVertical, mdiPlus, mdiUpload } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-dropdown";
|
||||
@@ -24,7 +23,6 @@ import {
|
||||
computeBackupAgentName,
|
||||
generateBackup,
|
||||
generateBackupWithAutomaticSettings,
|
||||
saveBackupConfig,
|
||||
} from "../../../data/backup";
|
||||
import type { ManagerStateEvent } from "../../../data/backup_manager";
|
||||
import type { CloudStatus } from "../../../data/cloud";
|
||||
@@ -34,12 +32,10 @@ import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import { showAlertDialog } from "../../lovelace/custom-card-helpers";
|
||||
import "./components/overview/ha-backup-overview-backups";
|
||||
import "./components/overview/ha-backup-overview-app-update-backup";
|
||||
import "./components/overview/ha-backup-overview-onboarding";
|
||||
import "./components/overview/ha-backup-overview-progress";
|
||||
import "./components/overview/ha-backup-overview-settings";
|
||||
import "./components/overview/ha-backup-overview-summary";
|
||||
import "./components/config/ha-backup-config-encryption-key";
|
||||
import { showBackupOnboardingDialog } from "./dialogs/show-dialog-backup_onboarding";
|
||||
import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup";
|
||||
import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup";
|
||||
@@ -72,54 +68,10 @@ class HaConfigBackupOverview extends LitElement {
|
||||
{ uploaded_bytes: number; total_bytes: number }
|
||||
> = {};
|
||||
|
||||
@state() private _config?: BackupConfig;
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
if (changedProperties.has("config") && !this._config) {
|
||||
this._config = this.config;
|
||||
}
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
// Update config when the page is displayed (e.g. when coming back from a settings page)
|
||||
this._config = this.config;
|
||||
}
|
||||
|
||||
private _uploadBackup = async () => {
|
||||
await showUploadBackupDialog(this, {});
|
||||
};
|
||||
|
||||
private _encryptionKeyChanged(ev) {
|
||||
if (!this._config) {
|
||||
return;
|
||||
}
|
||||
|
||||
const password = ev.detail.value as string;
|
||||
this._config = {
|
||||
...this._config,
|
||||
create_backup: {
|
||||
...this._config.create_backup,
|
||||
password,
|
||||
},
|
||||
};
|
||||
|
||||
this._debounceSaveConfig();
|
||||
}
|
||||
|
||||
private _debounceSaveConfig = debounce(() => this._saveConfig(), 500);
|
||||
|
||||
private async _saveConfig() {
|
||||
if (!this._config) {
|
||||
return;
|
||||
}
|
||||
|
||||
await saveBackupConfig(this.hass, this._config);
|
||||
|
||||
fireEvent(this, "ha-refresh-backup-config");
|
||||
}
|
||||
|
||||
private _handleOnboardingButtonClick(ev) {
|
||||
ev.stopPropagation();
|
||||
this._setupAutomaticBackup(true);
|
||||
@@ -282,41 +234,13 @@ class HaConfigBackupOverview extends LitElement {
|
||||
.backups=${this.backups}
|
||||
></ha-backup-overview-backups>
|
||||
|
||||
${!this._needsOnboarding && this._config
|
||||
${!this._needsOnboarding && this.config
|
||||
? html`
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.encryption_key.title"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.encryption_key.description"
|
||||
)}
|
||||
</p>
|
||||
<ha-backup-config-encryption-key
|
||||
.hass=${this.hass}
|
||||
.value=${this._config.create_backup.password}
|
||||
@value-changed=${this._encryptionKeyChanged}
|
||||
></ha-backup-config-encryption-key>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
<ha-backup-overview-settings
|
||||
.hass=${this.hass}
|
||||
.config=${this._config}
|
||||
.config=${this.config!}
|
||||
.agents=${this.agents}
|
||||
></ha-backup-overview-settings>
|
||||
|
||||
${this.hass.config.components.includes("hassio")
|
||||
? html`
|
||||
<ha-backup-overview-app-update-backup
|
||||
.hass=${this.hass}
|
||||
></ha-backup-overview-app-update-backup>
|
||||
`
|
||||
: nothing}
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
@@ -346,10 +270,6 @@ class HaConfigBackupOverview extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
p {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 28px 20px 0;
|
||||
max-width: 690px;
|
||||
@@ -363,6 +283,10 @@ class HaConfigBackupOverview extends LitElement {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.card-content {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
.loading {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import type { BackupAgent, BackupConfig } from "../../../data/backup";
|
||||
import { saveBackupConfig } from "../../../data/backup";
|
||||
import { updateBackupConfig } from "../../../data/backup";
|
||||
import type { CloudStatus } from "../../../data/cloud";
|
||||
import {
|
||||
getSupervisorUpdateConfig,
|
||||
@@ -27,9 +27,11 @@ import "../../../layouts/hass-subpage";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import "./components/config/ha-backup-config-addon";
|
||||
import "./components/config/ha-backup-config-agents";
|
||||
import "./components/config/ha-backup-config-data";
|
||||
import type { BackupConfigData } from "./components/config/ha-backup-config-data";
|
||||
import "./components/config/ha-backup-config-encryption-key";
|
||||
import "./components/config/ha-backup-config-schedule";
|
||||
import type { BackupConfigSchedule } from "./components/config/ha-backup-config-schedule";
|
||||
import { showLocalBackupLocationDialog } from "./dialogs/show-dialog-local-backup-location";
|
||||
@@ -77,7 +79,7 @@ class HaConfigBackupSettings extends LitElement {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
this._supervisorUpdateConfigError = this.hass.localize(
|
||||
"ui.panel.config.backup.settings.schedule.error_load",
|
||||
"ui.panel.config.backup.settings.app_update_backup.error_load",
|
||||
{
|
||||
error: err?.message || err,
|
||||
}
|
||||
@@ -313,6 +315,57 @@ class HaConfigBackupSettings extends LitElement {
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-card>
|
||||
${supervisor
|
||||
? html`<ha-card>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.app_update_backup.title"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.app_update_backup.description"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.app_update_backup.local_only"
|
||||
)}
|
||||
</p>
|
||||
${this._supervisorUpdateConfigError
|
||||
? html`<ha-alert alert-type="error">
|
||||
${this._supervisorUpdateConfigError}
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
<ha-backup-config-addon
|
||||
.hass=${this.hass}
|
||||
.supervisorUpdateConfig=${this._supervisorUpdateConfig}
|
||||
@update-config-changed=${this
|
||||
._supervisorUpdateConfigChanged}
|
||||
></ha-backup-config-addon>
|
||||
</div>
|
||||
</ha-card>`
|
||||
: nothing}
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.encryption_key.title"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.settings.encryption_key.description"
|
||||
)}
|
||||
</p>
|
||||
<ha-backup-config-encryption-key
|
||||
.hass=${this.hass}
|
||||
.value=${this._config.create_backup.password}
|
||||
@value-changed=${this._encryptionKeyChanged}
|
||||
></ha-backup-config-encryption-key>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
@@ -385,6 +438,18 @@ class HaConfigBackupSettings extends LitElement {
|
||||
this._debounceSave();
|
||||
}
|
||||
|
||||
private _encryptionKeyChanged(ev) {
|
||||
const password = ev.detail.value as string;
|
||||
this._config = {
|
||||
...this._config!,
|
||||
create_backup: {
|
||||
...this._config!.create_backup,
|
||||
password: password,
|
||||
},
|
||||
};
|
||||
this._debounceSave();
|
||||
}
|
||||
|
||||
private _debounceSaveSupervisorUpdateConfig = debounce(
|
||||
() => this._saveSupervisorUpdateConfig(),
|
||||
500
|
||||
@@ -403,7 +468,7 @@ class HaConfigBackupSettings extends LitElement {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
this._supervisorUpdateConfigError = this.hass.localize(
|
||||
"ui.panel.config.backup.settings.schedule.error_save",
|
||||
"ui.panel.config.backup.settings.app_update_backup.error_save",
|
||||
{
|
||||
error: err?.message || err?.toString(),
|
||||
}
|
||||
@@ -414,7 +479,18 @@ class HaConfigBackupSettings extends LitElement {
|
||||
private _debounceSave = debounce(() => this._save(), 500);
|
||||
|
||||
private async _save() {
|
||||
await saveBackupConfig(this.hass, this._config!);
|
||||
await updateBackupConfig(this.hass, {
|
||||
create_backup: {
|
||||
agent_ids: this._config!.create_backup.agent_ids,
|
||||
include_folders: this._config!.create_backup.include_folders ?? [],
|
||||
include_database: this._config!.create_backup.include_database,
|
||||
include_addons: this._config!.create_backup.include_addons ?? [],
|
||||
include_all_addons: this._config!.create_backup.include_all_addons,
|
||||
password: this._config!.create_backup.password,
|
||||
},
|
||||
retention: this._config!.retention,
|
||||
schedule: this._config!.schedule,
|
||||
});
|
||||
fireEvent(this, "ha-refresh-backup-config");
|
||||
}
|
||||
|
||||
|
||||
@@ -125,11 +125,6 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) {
|
||||
load: () => import("./ha-config-backup-settings"),
|
||||
cache: true,
|
||||
},
|
||||
"app-update-backups": {
|
||||
tag: "ha-config-backup-app-update-backups",
|
||||
load: () => import("./ha-config-backup-app-update-backups"),
|
||||
cache: true,
|
||||
},
|
||||
location: {
|
||||
tag: "ha-config-backup-location",
|
||||
load: () => import("./ha-config-backup-location"),
|
||||
|
||||
@@ -244,13 +244,9 @@ class HaPanelDevAssist extends SubscribeMixin(LitElement) {
|
||||
max-width: 1040px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--ha-space-4);
|
||||
}
|
||||
.description {
|
||||
margin: 0;
|
||||
margin-bottom: var(--ha-space-4);
|
||||
}
|
||||
ha-textarea {
|
||||
width: 100%;
|
||||
|
||||
@@ -29,6 +29,8 @@ import {
|
||||
import type { EnergySettingsBatteryDialogParams } from "./show-dialogs-energy";
|
||||
|
||||
const energyUnitClasses = ["energy"];
|
||||
const socStatisticsUnits = ["%"];
|
||||
const socDeviceClass = "battery";
|
||||
|
||||
@customElement("dialog-energy-battery-settings")
|
||||
export class DialogEnergyBatterySettings
|
||||
@@ -180,6 +182,21 @@ export class DialogEnergyBatterySettings
|
||||
@power-config-changed=${this._handlePowerConfigChanged}
|
||||
></ha-energy-power-config>
|
||||
|
||||
<ha-statistic-picker
|
||||
.hass=${this.hass}
|
||||
.helpMissingEntityUrl=${energyStatisticHelpUrl}
|
||||
.value=${this._source.stat_soc}
|
||||
.includeStatisticsUnitOfMeasurement=${socStatisticsUnits}
|
||||
.includeDeviceClass=${socDeviceClass}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.energy.battery.dialog.state_of_charge"
|
||||
)}
|
||||
.helper=${this.hass.localize(
|
||||
"ui.panel.config.energy.battery.dialog.state_of_charge_helper"
|
||||
)}
|
||||
@value-changed=${this._statisticSocChanged}
|
||||
></ha-statistic-picker>
|
||||
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
@@ -232,6 +249,13 @@ export class DialogEnergyBatterySettings
|
||||
this._powerConfig = ev.detail.powerConfig;
|
||||
}
|
||||
|
||||
private _statisticSocChanged(ev: ValueChangedEvent<string>) {
|
||||
this._source = {
|
||||
...this._source!,
|
||||
stat_soc: ev.detail.value || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private async _save() {
|
||||
try {
|
||||
const source: BatterySourceTypeEnergyPreference = {
|
||||
@@ -245,6 +269,10 @@ export class DialogEnergyBatterySettings
|
||||
source.power_config = { ...this._powerConfig };
|
||||
}
|
||||
|
||||
if (this._source!.stat_soc) {
|
||||
source.stat_soc = this._source!.stat_soc;
|
||||
}
|
||||
|
||||
await this._params!.saveCallback(source);
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
@@ -257,7 +285,8 @@ export class DialogEnergyBatterySettings
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-statistic-picker {
|
||||
ha-statistic-picker,
|
||||
ha-energy-power-config {
|
||||
display: block;
|
||||
margin-bottom: var(--ha-space-4);
|
||||
}
|
||||
|
||||
@@ -146,7 +146,6 @@ interface HelperItem {
|
||||
category: string | undefined;
|
||||
area?: string;
|
||||
label_entries: LabelRegistryEntry[];
|
||||
labels: string[]; // search only
|
||||
assistants: string[];
|
||||
assistants_sortable_key: string | undefined;
|
||||
disabled?: boolean;
|
||||
@@ -553,9 +552,6 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
const entityRegEntry =
|
||||
entityRegistryByEntityId(entityReg)[item.entity_id];
|
||||
const labels = labelReg && entityRegEntry?.labels;
|
||||
const label_entries = (labels || []).map(
|
||||
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
|
||||
);
|
||||
const category = entityRegEntry?.categories.helpers;
|
||||
const deviceId = entityRegEntry?.device_id;
|
||||
const areaId =
|
||||
@@ -576,8 +572,9 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
`ui.panel.config.helpers.types.${item.type}` as LocalizeKeys
|
||||
) ||
|
||||
item.type,
|
||||
label_entries,
|
||||
labels: label_entries.map((lbl) => lbl.name),
|
||||
label_entries: (labels || []).map(
|
||||
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
|
||||
),
|
||||
category: category
|
||||
? categoryReg?.find((cat) => cat.category_id === category)?.name
|
||||
: undefined,
|
||||
|
||||
@@ -122,7 +122,6 @@ type SceneItem = SceneEntity & {
|
||||
area: string | undefined;
|
||||
category: string | undefined;
|
||||
label_entries: LabelRegistryEntry[];
|
||||
labels: string[]; // search only
|
||||
assistants: string[];
|
||||
assistants_sortable_key: string | undefined;
|
||||
editable: boolean;
|
||||
@@ -240,9 +239,6 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
const category = entityRegEntry?.categories.scene;
|
||||
const labels = labelReg && entityRegEntry?.labels;
|
||||
const label_entries = (labels || []).map(
|
||||
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
|
||||
);
|
||||
const assistants = getEntityVoiceAssistantsIds(
|
||||
entityReg,
|
||||
scene.entity_id
|
||||
@@ -256,8 +252,9 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
||||
category: category
|
||||
? categoryReg?.find((cat) => cat.category_id === category)?.name
|
||||
: undefined,
|
||||
label_entries,
|
||||
labels: label_entries.map((lbl) => lbl.name),
|
||||
label_entries: (labels || []).map(
|
||||
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
|
||||
),
|
||||
assistants,
|
||||
assistants_sortable_key: getAssistantsSortableKey(assistants),
|
||||
selectable: entityRegEntry !== undefined,
|
||||
|
||||
@@ -127,7 +127,6 @@ type ScriptItem = ScriptEntity & {
|
||||
last_triggered: string | undefined;
|
||||
category: string | undefined;
|
||||
label_entries: LabelRegistryEntry[];
|
||||
labels: string[]; // search only
|
||||
assistants: string[];
|
||||
assistants_sortable_key: string | undefined;
|
||||
};
|
||||
@@ -246,9 +245,6 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
const category = entityRegEntry?.categories.script;
|
||||
const labels = labelReg && entityRegEntry?.labels;
|
||||
const label_entries = (labels || []).map(
|
||||
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
|
||||
);
|
||||
const assistants = getEntityVoiceAssistantsIds(
|
||||
entityReg,
|
||||
script.entity_id
|
||||
@@ -263,8 +259,9 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
||||
category: category
|
||||
? categoryReg?.find((cat) => cat.category_id === category)?.name
|
||||
: undefined,
|
||||
label_entries,
|
||||
labels: label_entries.map((lbl) => lbl.name),
|
||||
label_entries: (labels || []).map(
|
||||
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
|
||||
),
|
||||
assistants,
|
||||
assistants_sortable_key: getAssistantsSortableKey(assistants),
|
||||
selectable: entityRegEntry !== undefined,
|
||||
|
||||
@@ -16,6 +16,7 @@ import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing, svg } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { batteryLevelIconPath } from "../../../../common/entity/battery_icon";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
@@ -100,14 +101,34 @@ class HuiEnergyDistrubutionCard
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
return (
|
||||
if (
|
||||
hasConfigChanged(this, changedProps) ||
|
||||
changedProps.size > 1 ||
|
||||
!changedProps.has("hass") ||
|
||||
(!!this._data?.co2SignalEntity &&
|
||||
this.hass.states[this._data.co2SignalEntity] !==
|
||||
changedProps.get("hass").states[this._data.co2SignalEntity])
|
||||
);
|
||||
!changedProps.has("hass")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
const oldStates = changedProps.get("hass").states;
|
||||
if (
|
||||
this._data?.co2SignalEntity &&
|
||||
this.hass.states[this._data.co2SignalEntity] !==
|
||||
oldStates[this._data.co2SignalEntity]
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
const batteries = this._data
|
||||
? energySourcesByType(this._data.prefs).battery
|
||||
: undefined;
|
||||
if (
|
||||
batteries?.some(
|
||||
(source) =>
|
||||
source.stat_soc &&
|
||||
this.hass.states[source.stat_soc] !== oldStates[source.stat_soc]
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected willUpdate() {
|
||||
@@ -174,10 +195,24 @@ class HuiEnergyDistrubutionCard
|
||||
|
||||
let totalBatteryIn: number | null = null;
|
||||
let totalBatteryOut: number | null = null;
|
||||
let batteryIconPath = mdiBatteryHigh;
|
||||
|
||||
if (hasBattery) {
|
||||
totalBatteryIn = summedData.total.to_battery ?? 0;
|
||||
totalBatteryOut = summedData.total.from_battery ?? 0;
|
||||
|
||||
const socValues = types
|
||||
.battery!.map((source) =>
|
||||
source.stat_soc
|
||||
? Number(this.hass.states[source.stat_soc]?.state)
|
||||
: NaN
|
||||
)
|
||||
.filter((value) => Number.isFinite(value));
|
||||
if (socValues.length) {
|
||||
const averageSoc =
|
||||
socValues.reduce((sum, value) => sum + value, 0) / socValues.length;
|
||||
batteryIconPath = batteryLevelIconPath(averageSoc);
|
||||
}
|
||||
}
|
||||
|
||||
let returnedToGrid: number | null = null;
|
||||
@@ -569,7 +604,7 @@ class HuiEnergyDistrubutionCard
|
||||
${hasBattery
|
||||
? html` <div class="circle-container battery">
|
||||
<div class="circle">
|
||||
<ha-svg-icon .path=${mdiBatteryHigh}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${batteryIconPath}></ha-svg-icon>
|
||||
<span class="battery-in">
|
||||
<ha-svg-icon
|
||||
class="small"
|
||||
|
||||
@@ -35,10 +35,7 @@ import {
|
||||
HOME_SUMMARIES_ICONS,
|
||||
type HomeSummary,
|
||||
} from "../strategies/home/helpers/home-summaries";
|
||||
import {
|
||||
filterLowBatteryEntities,
|
||||
filterUnavailableBatteryEntities,
|
||||
} from "../../maintenance/strategies/maintenance-view-strategy";
|
||||
import { filterNeedsAttentionEntities } from "../../maintenance/strategies/maintenance-view-strategy";
|
||||
import type { LovelaceCard, LovelaceGridOptions } from "../types";
|
||||
import { tileCardStyle } from "./tile/tile-card-style";
|
||||
import type { HomeSummaryCard } from "./types";
|
||||
@@ -261,48 +258,19 @@ export class HuiHomeSummaryCard
|
||||
maintenanceFilters
|
||||
);
|
||||
|
||||
const lowBatteryEntities = filterLowBatteryEntities(
|
||||
const needsAttentionEntities = filterNeedsAttentionEntities(
|
||||
this.hass!,
|
||||
maintenanceEntities
|
||||
);
|
||||
|
||||
const unavailableBatteryEntities = filterUnavailableBatteryEntities(
|
||||
this.hass!,
|
||||
maintenanceEntities
|
||||
);
|
||||
|
||||
const lowBatteryText =
|
||||
lowBatteryEntities.length > 0
|
||||
? this.hass.localize(
|
||||
"ui.card.home-summary.count_maintenance_low_battery_issues",
|
||||
{
|
||||
count: lowBatteryEntities.length,
|
||||
}
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const unavailableText =
|
||||
unavailableBatteryEntities.length > 0
|
||||
? this.hass.localize(
|
||||
"ui.card.home-summary.count_maintenance_issues_unavailable_battery_entities",
|
||||
{
|
||||
count: unavailableBatteryEntities.length,
|
||||
}
|
||||
)
|
||||
: undefined;
|
||||
|
||||
if (lowBatteryText && unavailableText) {
|
||||
return `${lowBatteryText}, ${unavailableText}`;
|
||||
if (needsAttentionEntities.length > 0) {
|
||||
return this.hass.localize(
|
||||
"ui.card.home-summary.count_maintenance_issues",
|
||||
{
|
||||
count: needsAttentionEntities.length,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (lowBatteryText) {
|
||||
return lowBatteryText;
|
||||
}
|
||||
|
||||
if (unavailableText) {
|
||||
return unavailableText;
|
||||
}
|
||||
|
||||
return this.hass.localize("ui.card.home-summary.all_maintenance_good");
|
||||
}
|
||||
case "energy": {
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction, hasAnyAction } from "../common/has-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { hasConfigChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
@@ -52,17 +52,10 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
|
||||
throw new Error("Image required");
|
||||
}
|
||||
|
||||
if (config.image_entity) {
|
||||
this._config = {
|
||||
tap_action: { action: "more-info" },
|
||||
...config,
|
||||
};
|
||||
} else {
|
||||
this._config = {
|
||||
tap_action: { action: "none" },
|
||||
...config,
|
||||
};
|
||||
}
|
||||
this._config = {
|
||||
tap_action: { action: "more-info" },
|
||||
...config,
|
||||
};
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
@@ -174,11 +167,6 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const clickable = Boolean(
|
||||
(this._config.image_entity && !this._config.tap_action) ||
|
||||
hasAnyAction(this._config)
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
@action=${this._handleAction}
|
||||
@@ -192,7 +180,15 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
|
||||
: undefined
|
||||
)}
|
||||
class=${classMap({
|
||||
clickable,
|
||||
clickable: Boolean(
|
||||
(this._config.image_entity && !this._config.tap_action) ||
|
||||
(this._config.tap_action &&
|
||||
this._config.tap_action.action !== "none") ||
|
||||
(this._config.hold_action &&
|
||||
this._config.hold_action.action !== "none") ||
|
||||
(this._config.double_tap_action &&
|
||||
this._config.double_tap_action.action !== "none")
|
||||
),
|
||||
})}
|
||||
>
|
||||
<img
|
||||
|
||||
@@ -419,7 +419,6 @@ export class HuiDialogEditBadge
|
||||
.content {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
gap: var(--ha-space-3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -419,7 +419,6 @@ export class HuiDialogEditCard
|
||||
.content {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
gap: var(--ha-space-3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,9 @@ export class HuiPictureCardEditor
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {},
|
||||
ui_action: {
|
||||
default_action: "more-info",
|
||||
},
|
||||
},
|
||||
context: ACTION_RELATED_CONTEXT,
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction, hasAnyAction } from "../common/has-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import type { LovelaceHeaderFooter } from "../types";
|
||||
import type { PictureHeaderFooterConfig } from "./types";
|
||||
|
||||
@@ -56,7 +56,9 @@ export class HuiPictureHeaderFooter
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const clickable = hasAnyAction(this._config);
|
||||
const clickable = Boolean(
|
||||
this._config.tap_action || this._config.hold_action
|
||||
);
|
||||
|
||||
return html`
|
||||
<img
|
||||
|
||||
@@ -31,7 +31,7 @@ export const maintenanceEntityFilters: EntityFilter[] = [
|
||||
|
||||
const LOW_BATTERY_THRESHOLD = 20;
|
||||
|
||||
export const filterLowBatteryEntities = (
|
||||
export const filterNeedsAttentionEntities = (
|
||||
hass: HomeAssistant,
|
||||
entityIds: string[]
|
||||
): string[] =>
|
||||
@@ -40,14 +40,6 @@ export const filterLowBatteryEntities = (
|
||||
return !isNaN(stateValue) && stateValue <= LOW_BATTERY_THRESHOLD;
|
||||
});
|
||||
|
||||
export const filterUnavailableBatteryEntities = (
|
||||
hass: HomeAssistant,
|
||||
entityIds: string[]
|
||||
): string[] =>
|
||||
entityIds.filter((entityId) => {
|
||||
return hass.states[entityId]?.state === "unavailable";
|
||||
});
|
||||
|
||||
const computeBatteryTileCard = (entityId: string): TileCardConfig => ({
|
||||
type: "tile",
|
||||
entity: entityId,
|
||||
|
||||
@@ -218,8 +218,7 @@
|
||||
"all_secure": "All secure",
|
||||
"no_media_playing": "No media playing",
|
||||
"count_media_playing": "{count} {count, plural,\n one {playing}\n other {playing}\n}",
|
||||
"count_maintenance_low_battery_issues": "{count} {count, plural,\n one {low battery}\n other {low batteries}\n}",
|
||||
"count_maintenance_issues_unavailable_battery_entities": "{count} {count, plural,\n one {unavailable device}\n other {unavailable devices}\n}",
|
||||
"count_maintenance_issues": "{count} {count, plural,\n one {issue}\n other {issues}\n}",
|
||||
"all_maintenance_good": "All good",
|
||||
"count_persons_home": "{count} {count, plural,\n one {person}\n other {people}\n} home",
|
||||
"nobody_home": "No one home"
|
||||
@@ -3567,7 +3566,7 @@
|
||||
"show_all": "Show all backups"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Automatic backup",
|
||||
"title": "Backup settings",
|
||||
"configure": "Configure backup settings",
|
||||
"schedule": "Automatic backup schedule and retention",
|
||||
"schedule_copies_all": "and keep all backups",
|
||||
@@ -3613,10 +3612,6 @@
|
||||
"sat": "Sa",
|
||||
"sun": "Su"
|
||||
}
|
||||
},
|
||||
"app_update_backup": {
|
||||
"title": "App update backup",
|
||||
"description": "Backup behavior for app updates"
|
||||
}
|
||||
},
|
||||
"backups": {
|
||||
@@ -3628,15 +3623,13 @@
|
||||
"new_backup": "[%key:ui::panel::config::backup::overview::new_backup%]"
|
||||
},
|
||||
"settings": {
|
||||
"header": "Automatic backups",
|
||||
"header": "Backup settings",
|
||||
"menu": {
|
||||
"change_default_location": "Change default action location"
|
||||
},
|
||||
"schedule": {
|
||||
"title": "Backup cycle",
|
||||
"description": "Let Home Assistant take care of your backups by creating a scheduled backup that also removes older backups.",
|
||||
"error_load": "Error loading Supervisor update config: {error}",
|
||||
"error_save": "Error saving Supervisor update config: {error}"
|
||||
"title": "Automatic backups",
|
||||
"description": "Let Home Assistant take care of your backups by creating a scheduled backup that also removes older backups."
|
||||
},
|
||||
"data": {
|
||||
"title": "Backup data"
|
||||
@@ -3664,9 +3657,6 @@
|
||||
"error_save": "Error saving Supervisor update config: {error}"
|
||||
}
|
||||
},
|
||||
"app_update_backups": {
|
||||
"header": "App update backups"
|
||||
},
|
||||
"details": {
|
||||
"header": "Backup",
|
||||
"not_found": "Not found",
|
||||
@@ -4171,6 +4161,8 @@
|
||||
"energy_helper_out": "Pick a sensor that measures the electricity flowing out of the battery in either of {unit}.",
|
||||
"energy_into_battery": "Energy charged into the battery",
|
||||
"energy_out_of_battery": "Energy discharged from the battery",
|
||||
"state_of_charge": "Battery state of charge sensor",
|
||||
"state_of_charge_helper": "Sensor reporting battery state of charge as %.",
|
||||
"power": "Battery power",
|
||||
"power_helper": "Pick a sensor which measures the electricity flowing into and out of the battery in either of {unit}. Positive values indicate discharging the battery, negative values indicate charging the battery.",
|
||||
"sensor_type": "Type of power measurement",
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import {
|
||||
mdiBattery,
|
||||
mdiBattery10,
|
||||
mdiBattery50,
|
||||
mdiBattery90,
|
||||
mdiBatteryAlertVariantOutline,
|
||||
mdiBatteryUnknown,
|
||||
} from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
batteryIcon,
|
||||
batteryLevelIcon,
|
||||
batteryLevelIconPath,
|
||||
} from "../../../src/common/entity/battery_icon";
|
||||
|
||||
describe("batteryIcon", () => {
|
||||
@@ -43,3 +52,24 @@ describe("batteryLevelIcon", () => {
|
||||
expect(batteryLevelIcon("on")).toBe("mdi:battery-alert");
|
||||
});
|
||||
});
|
||||
|
||||
describe("batteryLevelIconPath", () => {
|
||||
it("rounds to the nearest 10% bucket", () => {
|
||||
expect(batteryLevelIconPath(46)).toBe(mdiBattery50);
|
||||
expect(batteryLevelIconPath(94)).toBe(mdiBattery90);
|
||||
expect(batteryLevelIconPath(95)).toBe(mdiBattery);
|
||||
});
|
||||
|
||||
it("returns the alert path for very low levels", () => {
|
||||
expect(batteryLevelIconPath(0)).toBe(mdiBatteryAlertVariantOutline);
|
||||
expect(batteryLevelIconPath(5)).toBe(mdiBatteryAlertVariantOutline);
|
||||
});
|
||||
|
||||
it("returns the 10% bucket just above the alert threshold", () => {
|
||||
expect(batteryLevelIconPath(6)).toBe(mdiBattery10);
|
||||
});
|
||||
|
||||
it("returns the unknown path for non-numeric input", () => {
|
||||
expect(batteryLevelIconPath("unavailable")).toBe(mdiBatteryUnknown);
|
||||
});
|
||||
});
|
||||
|
||||
146
yarn.lock
146
yarn.lock
@@ -4383,105 +4383,105 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/eslint-plugin@npm:8.59.1":
|
||||
version: 8.59.1
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.59.1"
|
||||
"@typescript-eslint/eslint-plugin@npm:8.59.0":
|
||||
version: 8.59.0
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.59.0"
|
||||
dependencies:
|
||||
"@eslint-community/regexpp": "npm:^4.12.2"
|
||||
"@typescript-eslint/scope-manager": "npm:8.59.1"
|
||||
"@typescript-eslint/type-utils": "npm:8.59.1"
|
||||
"@typescript-eslint/utils": "npm:8.59.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.59.1"
|
||||
"@typescript-eslint/scope-manager": "npm:8.59.0"
|
||||
"@typescript-eslint/type-utils": "npm:8.59.0"
|
||||
"@typescript-eslint/utils": "npm:8.59.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.59.0"
|
||||
ignore: "npm:^7.0.5"
|
||||
natural-compare: "npm:^1.4.0"
|
||||
ts-api-utils: "npm:^2.5.0"
|
||||
peerDependencies:
|
||||
"@typescript-eslint/parser": ^8.59.1
|
||||
"@typescript-eslint/parser": ^8.59.0
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/c736ee32211a3751e31151b51dacc8cfa5bf18e086f2a87aba7ee325f7e2fa96d8b9febdbaf4dfa70d14954312b7b9740fbe5d5886b3f8561c4a94a9c7ff7688
|
||||
checksum: 10/fcf2c85cb37d61854d2c03fa11c66ad58d99f4eee731dd09663f20f0395e642b12edeab2a6c368ac1806505b2071a01de01bc30b9011fa309299836e868a293a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/parser@npm:8.59.1":
|
||||
version: 8.59.1
|
||||
resolution: "@typescript-eslint/parser@npm:8.59.1"
|
||||
"@typescript-eslint/parser@npm:8.59.0":
|
||||
version: 8.59.0
|
||||
resolution: "@typescript-eslint/parser@npm:8.59.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager": "npm:8.59.1"
|
||||
"@typescript-eslint/types": "npm:8.59.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.59.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.59.1"
|
||||
"@typescript-eslint/scope-manager": "npm:8.59.0"
|
||||
"@typescript-eslint/types": "npm:8.59.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.59.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.59.0"
|
||||
debug: "npm:^4.4.3"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/b014b485e5ec9c7430a87117271836b86fd80083fe6b1d216167313518f26222f45c0ee3f4cbc0616dbd6335cbde50336d8953ca5ffefecc55b2d896ac7645f9
|
||||
checksum: 10/b8990e1b67e6f55aa4884807e6c3b6bd08c58f96ea4f03f19e7aba3fc1b16f040fe58378490de9bd831c804eb48e633e30e5baf291b8e8dd53960424e5751391
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/project-service@npm:8.59.1":
|
||||
version: 8.59.1
|
||||
resolution: "@typescript-eslint/project-service@npm:8.59.1"
|
||||
"@typescript-eslint/project-service@npm:8.59.0":
|
||||
version: 8.59.0
|
||||
resolution: "@typescript-eslint/project-service@npm:8.59.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.59.1"
|
||||
"@typescript-eslint/types": "npm:^8.59.1"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.59.0"
|
||||
"@typescript-eslint/types": "npm:^8.59.0"
|
||||
debug: "npm:^4.4.3"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/dd98f49a407cb21999d31ec527a0f8c2c34422dde9fdb21210d66c3cc3d498d9d3678d95c99d76450af68ce3392692902d9ba044718d6c99122655df7afdc0a7
|
||||
checksum: 10/b842f1e0623c3a679d21d76c7ca38698787681d40f6a9ce93c8983120fb6795a2395907d530e4f8d89b4ac5bc65e71bbfdf2d8060f210c8487cffdae40baea74
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/scope-manager@npm:8.59.1":
|
||||
version: 8.59.1
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.59.1"
|
||||
"@typescript-eslint/scope-manager@npm:8.59.0":
|
||||
version: 8.59.0
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.59.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.59.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.59.1"
|
||||
checksum: 10/50c941d1af470d3e67a9bd2247c541a676ae6bb2931440a44458682d61382ba1194ce29d0388dd1e538c5a35d7a542febd9519d8170abe758692d1b6cd196eab
|
||||
"@typescript-eslint/types": "npm:8.59.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.59.0"
|
||||
checksum: 10/8bb1182559e7676120ba12bdac11edea9fb414bd33d379a1902b035b8b4b68d23ad239d845bfe6943b5da13ecd938ea1482c73e8c6ddb4d7e3e0f8e111467e28
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.59.1, @typescript-eslint/tsconfig-utils@npm:^8.59.1":
|
||||
version: 8.59.1
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.59.1"
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.59.0, @typescript-eslint/tsconfig-utils@npm:^8.59.0":
|
||||
version: 8.59.0
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.59.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/9e3351bb182bb02f6f140759472f08ce334c7c96f4ebfeec8e9e404fe60b8fe1865e1a6d1b50526f83f41e7224301485e46459df6c3675923f3b657177415cd7
|
||||
checksum: 10/9c094c199be4803d696dbf7cb5cdb76741876e412bf97ddde0133a75e11bc47345354b3bb188a0ff101b7ce2c582187e758696ab89c1981892a43162f36d0af1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/type-utils@npm:8.59.1":
|
||||
version: 8.59.1
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.59.1"
|
||||
"@typescript-eslint/type-utils@npm:8.59.0":
|
||||
version: 8.59.0
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.59.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.59.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.59.1"
|
||||
"@typescript-eslint/utils": "npm:8.59.1"
|
||||
"@typescript-eslint/types": "npm:8.59.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.59.0"
|
||||
"@typescript-eslint/utils": "npm:8.59.0"
|
||||
debug: "npm:^4.4.3"
|
||||
ts-api-utils: "npm:^2.5.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/8a8a71656f8fab446024e55b24f6f6c4b3ee4d4cdcb593ff68ec0ca10530fcb4d451628c03898c929e91445a999cbe980c0cfaec1b53a7c5ddc8ac899ad665fa
|
||||
checksum: 10/9c2d34c10676d5726f93b975136295971ac7d2a53f74bfba51ae71deefaa36292adda79d016782196b729429143634b7f90224c27dcdb3a884b9771128be7490
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:8.59.1, @typescript-eslint/types@npm:^8.56.0, @typescript-eslint/types@npm:^8.59.1":
|
||||
version: 8.59.1
|
||||
resolution: "@typescript-eslint/types@npm:8.59.1"
|
||||
checksum: 10/4d324a01c2314d8e196b43b9dc5fe9a4d82c1b65f4915cd2f965879c5565d4453603b6f7b6bcdc436fb629135f07ad0f9d274e4697b02ce8bc1c0310916f7ace
|
||||
"@typescript-eslint/types@npm:8.59.0, @typescript-eslint/types@npm:^8.56.0, @typescript-eslint/types@npm:^8.59.0":
|
||||
version: 8.59.0
|
||||
resolution: "@typescript-eslint/types@npm:8.59.0"
|
||||
checksum: 10/51a773339c58a350d0ddaecba46ba735696f11829cab1f9b3d5d58a4bbd498693296ae742e3959d32f3bb29676c8e6bd120b970379d749a5a9b419393696930d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:8.59.1":
|
||||
version: 8.59.1
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.59.1"
|
||||
"@typescript-eslint/typescript-estree@npm:8.59.0":
|
||||
version: 8.59.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.59.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service": "npm:8.59.1"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.59.1"
|
||||
"@typescript-eslint/types": "npm:8.59.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.59.1"
|
||||
"@typescript-eslint/project-service": "npm:8.59.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.59.0"
|
||||
"@typescript-eslint/types": "npm:8.59.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.59.0"
|
||||
debug: "npm:^4.4.3"
|
||||
minimatch: "npm:^10.2.2"
|
||||
semver: "npm:^7.7.3"
|
||||
@@ -4489,32 +4489,32 @@ __metadata:
|
||||
ts-api-utils: "npm:^2.5.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/8ede99640ac8b08ac73905bbc66dd06b2c4dc211240a4a9cb532b0fcf5c36ec9e7639ed7e1c17f86a948499279ff93e9dbcdf9170661d9f8347fcb53e8266772
|
||||
checksum: 10/48eba6a117a36c4bf569aa1a728463619b131a45a6891cc0a5d2454828d9d3d07a499e9906de0df31de57761ce1d13aebb635a059782f3cc16563e3e63a29713
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/utils@npm:8.59.1":
|
||||
version: 8.59.1
|
||||
resolution: "@typescript-eslint/utils@npm:8.59.1"
|
||||
"@typescript-eslint/utils@npm:8.59.0":
|
||||
version: 8.59.0
|
||||
resolution: "@typescript-eslint/utils@npm:8.59.0"
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils": "npm:^4.9.1"
|
||||
"@typescript-eslint/scope-manager": "npm:8.59.1"
|
||||
"@typescript-eslint/types": "npm:8.59.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.59.1"
|
||||
"@typescript-eslint/scope-manager": "npm:8.59.0"
|
||||
"@typescript-eslint/types": "npm:8.59.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.59.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/26ae39a574e56d92b6fc406113e797c354fce8b377721cc5dd50579a0e9f8c3efe23c7693826ce2c16be96490520dd6ce7e145c4c39c22d8d00f2614791603ba
|
||||
checksum: 10/70547510f16459ca29e207584676f7c15626b5f7e2562643144fe037a1a9c4ca7116be99e67b9045f0de60db0022affb58c34c553a5370276ff8f542f7b05732
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:8.59.1":
|
||||
version: 8.59.1
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.59.1"
|
||||
"@typescript-eslint/visitor-keys@npm:8.59.0":
|
||||
version: 8.59.0
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.59.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.59.1"
|
||||
"@typescript-eslint/types": "npm:8.59.0"
|
||||
eslint-visitor-keys: "npm:^5.0.0"
|
||||
checksum: 10/5343f3424cafdcaf2550fade29eca6b86ad3f6ac953aef6ba1dccd39789a1c38520634fbbc0814419d9227f508789053c1c9f59c2841d72e56431c3fdd93ac65
|
||||
checksum: 10/b81753b9ddddeb3564e44d1199ba5546028731c7b5b3270938525f1f2b549d1df5fa8f203d9b3eacc120fa6b5af314cb1fb69d3a12d1dcce18a52a0fe316628d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8275,7 +8275,7 @@ __metadata:
|
||||
tinykeys: "npm:3.0.0"
|
||||
ts-lit-plugin: "npm:2.0.2"
|
||||
typescript: "npm:6.0.3"
|
||||
typescript-eslint: "npm:8.59.1"
|
||||
typescript-eslint: "npm:8.59.0"
|
||||
vite-tsconfig-paths: "npm:6.1.1"
|
||||
vitest: "npm:4.1.5"
|
||||
webpack-stats-plugin: "npm:1.1.3"
|
||||
@@ -12568,18 +12568,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript-eslint@npm:8.59.1":
|
||||
version: 8.59.1
|
||||
resolution: "typescript-eslint@npm:8.59.1"
|
||||
"typescript-eslint@npm:8.59.0":
|
||||
version: 8.59.0
|
||||
resolution: "typescript-eslint@npm:8.59.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.59.1"
|
||||
"@typescript-eslint/parser": "npm:8.59.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.59.1"
|
||||
"@typescript-eslint/utils": "npm:8.59.1"
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.59.0"
|
||||
"@typescript-eslint/parser": "npm:8.59.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.59.0"
|
||||
"@typescript-eslint/utils": "npm:8.59.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/d35d30bdcff5b9644c9bf500d3ad74cebbd41612bf2669c3e3208cd74ee43302941666336acfca65bc44352b9b58c0455afe0a9e7106f12e54789b8c1f16dc11
|
||||
checksum: 10/e015494cae2ae88c291e87d9d8c2c8d9924536f2edfac1a1da5e05f5ee083df7a8d916549f87af8a7b818d01de2bd505e29fdf991a086522a062387b4c2f1f64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user