Compare commits

...

18 Commits

Author SHA1 Message Date
Paul Bottein
4fa1b60cb1 Only use opacity for bar graph 2025-01-31 09:54:16 +01:00
karwosts
653aeae3d8 Improve statistics graph axis when using energy_date_selection (#23974) 2025-01-31 08:45:37 +02:00
ildar170975
0aea6141ad Fix for "Increase generic entity row touch target (2) (#23973)
* Revert "Fix for "Increase generic entity row touch target" (#23953)"

This reverts commit 028472fc7b.

* conditional style
2025-01-31 08:41:38 +02:00
renovate[bot]
5243c1d871 Update typescript-eslint monorepo to v8.22.0 (#23972)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-31 08:39:18 +02:00
Simon Lamon
6ac6d9c6eb Backup location translations improvements (#23940)
* Backup location translations improvements

* Apply better translations
2025-01-30 18:06:57 +01:00
Norbert Rittel
6ba0071296 Add localizable "Actions" label to OAuth credentials picker (#23958)
* Add localizable "Actions" label to OAuth credentials picker

* Prettier
2025-01-30 18:05:18 +01:00
Paul Bottein
fef5dc4232 Fix location icon when many locations in backup datatable (#23964)
* Fix location icon when many locations in backup datatable

* Reuse data

* Don't copy twice

* Improve naming
2025-01-30 17:02:56 +00:00
Paul Bottein
ce58962dbb Fix backup location config not updated (#23965) 2025-01-30 17:43:39 +01:00
Petar Petrov
9fb1e1d2ed Dynamically reorder energy devices (echarts) (#23966)
* Dynamically reorder energy devices (echarts)

* fix initial sorting in hui-energy-devices-detail-graph-card

* fix dynamic reordering in devices detail
2025-01-30 17:43:06 +01:00
Paul Bottein
a29544c1e6 Improve backup settings display on mobile (#23967) 2025-01-30 17:49:05 +02:00
Petar Petrov
b2b71edd04 Use CSS variables to theme echarts (#23963)
* Use CSS variables to theme echarts

* fix styles
2025-01-30 14:39:59 +01:00
ildar170975
028472fc7b Fix for "Increase generic entity row touch target" (#23953)
fix for "touch target"
2025-01-30 13:26:11 +01:00
Paul Bottein
b056ce228b Display device name in bluetooth panel (#23960) 2025-01-30 12:36:10 +01:00
Wendelin
0cd4256c0e Add correct link to backup.create_automatic (#23959) 2025-01-30 11:05:33 +00:00
Yosi Levy
e274c5b23f Add node memory to allow commit (#23954) 2025-01-30 11:06:50 +02:00
karwosts
ea57846465 Fix untracked energy in compare (#23949) 2025-01-30 09:57:54 +01:00
Paul Bottein
3f2e2bc659 Restore scroll position go back to backup settings page (#23955) 2025-01-30 09:56:52 +01:00
J. Nick Koston
e3f2f66206 Reduce size of address column on Bluetooth Advertisement monitor (#23942) 2025-01-29 19:01:47 +00:00
22 changed files with 499 additions and 172 deletions

View File

@@ -11,6 +11,9 @@
"DEV_CONTAINER": "1",
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
},
"remoteEnv": {
"NODE_OPTIONS": "--max_old_space_size=8192"
},
"customizations": {
"vscode": {
"extensions": [

View File

@@ -183,8 +183,8 @@
"@types/tar": "6.1.13",
"@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29",
"@typescript-eslint/eslint-plugin": "8.21.0",
"@typescript-eslint/parser": "8.21.0",
"@typescript-eslint/eslint-plugin": "8.22.0",
"@typescript-eslint/parser": "8.22.0",
"@vitest/coverage-v8": "3.0.4",
"babel-loader": "9.2.1",
"babel-plugin-template-html-minifier": "4.1.0",

View File

@@ -1,3 +1,4 @@
import memoizeOne from "memoize-one";
import { theme2hex } from "./convert-color";
export const COLORS = [
@@ -74,3 +75,12 @@ export function getGraphColorByIndex(
getColorByIndex(index);
return theme2hex(themeColor);
}
export const getAllGraphColors = memoizeOne(
(style: CSSStyleDeclaration) =>
COLORS.map((_color, index) => getGraphColorByIndex(index, style)),
(newArgs: [CSSStyleDeclaration], lastArgs: [CSSStyleDeclaration]) =>
// this is not ideal, but we need to memoize the colors
newArgs[0].getPropertyValue("--graph-color-1") ===
lastArgs[0].getPropertyValue("--graph-color-1")
);

View File

@@ -20,6 +20,7 @@ import type { ECOption } from "../../resources/echarts";
import { listenMediaQuery } from "../../common/dom/media_query";
import type { Themes } from "../../data/ws-themes";
import { themesContext } from "../../data/context";
import { getAllGraphColors } from "../../common/color/colors";
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
@@ -183,10 +184,9 @@ export class HaChartBase extends LitElement {
}
const echarts = (await import("../../resources/echarts")).default;
this.chart = echarts.init(
container,
this._themes.darkMode ? "dark" : "light"
);
echarts.registerTheme("custom", this._createTheme());
this.chart = echarts.init(container, "custom");
this.chart.on("legendselectchanged", (params: any) => {
if (this.externalHidden) {
const isSelected = params.selected[params.name];
@@ -237,24 +237,14 @@ export class HaChartBase extends LitElement {
}
private _createOptions(): ECOption {
const darkMode = this._themes.darkMode ?? false;
const options = {
backgroundColor: "transparent",
animation: !this._reducedMotion,
darkMode,
darkMode: this._themes.darkMode ?? false,
aria: {
show: true,
},
dataZoom: this._getDataZoomConfig(),
...this.options,
legend: this.options?.legend
? {
// we should create our own theme but this is a quick fix for now
inactiveColor: darkMode ? "#444" : "#ccc",
...this.options.legend,
}
: undefined,
};
const isMobile = window.matchMedia(
@@ -274,6 +264,181 @@ export class HaChartBase extends LitElement {
return options;
}
private _createTheme() {
const style = getComputedStyle(this);
return {
color: getAllGraphColors(style),
backgroundColor: "transparent",
textStyle: {
color: style.getPropertyValue("--primary-text-color"),
},
title: {
textStyle: {
color: style.getPropertyValue("--primary-text-color"),
},
subtextStyle: {
color: style.getPropertyValue("--secondary-text-color"),
},
},
line: {
lineStyle: {
width: 1.5,
},
symbolSize: 1,
symbol: "circle",
smooth: false,
},
bar: {
itemStyle: {
barBorderWidth: 1.5,
},
},
categoryAxis: {
axisLine: {
show: false,
},
axisTick: {
show: false,
},
axisLabel: {
show: true,
color: style.getPropertyValue("--primary-text-color"),
},
splitLine: {
show: false,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
splitArea: {
show: false,
areaStyle: {
color: [
style.getPropertyValue("--divider-color") + "3F",
style.getPropertyValue("--divider-color") + "7F",
],
},
},
},
valueAxis: {
axisLine: {
show: true,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
axisTick: {
show: true,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
axisLabel: {
show: true,
color: style.getPropertyValue("--primary-text-color"),
},
splitLine: {
show: true,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
splitArea: {
show: false,
areaStyle: {
color: [
style.getPropertyValue("--divider-color") + "3F",
style.getPropertyValue("--divider-color") + "7F",
],
},
},
},
logAxis: {
axisLine: {
show: true,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
axisTick: {
show: true,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
axisLabel: {
show: true,
color: style.getPropertyValue("--primary-text-color"),
},
splitLine: {
show: true,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
splitArea: {
show: false,
areaStyle: {
color: [
style.getPropertyValue("--divider-color") + "3F",
style.getPropertyValue("--divider-color") + "7F",
],
},
},
},
timeAxis: {
axisLine: {
show: true,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
axisTick: {
show: true,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
axisLabel: {
show: true,
color: style.getPropertyValue("--primary-text-color"),
},
splitLine: {
show: true,
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
splitArea: {
show: false,
areaStyle: {
color: [
style.getPropertyValue("--divider-color") + "3F",
style.getPropertyValue("--divider-color") + "7F",
],
},
},
},
legend: {
textStyle: {
color: style.getPropertyValue("--primary-text-color"),
},
inactiveColor: style.getPropertyValue("--disabled-text-color"),
},
tooltip: {
axisPointer: {
lineStyle: {
color: style.getPropertyValue("--divider-color"),
},
crossStyle: {
color: style.getPropertyValue("--divider-color"),
},
},
},
timeline: {},
};
}
private _getDefaultHeight() {
return Math.max(this.clientWidth / 2, 400);
}

View File

@@ -158,9 +158,6 @@ export class StateHistoryChartLine extends LitElement {
) {
const dayDifference = differenceInDays(this.endTime, this.startTime);
const rtl = computeRTL(this.hass);
const splitLineStyle = this.hass.themes?.darkMode
? { opacity: 0.15 }
: {};
this._chartOptions = {
xAxis: {
type: "time",
@@ -176,7 +173,6 @@ export class StateHistoryChartLine extends LitElement {
},
splitLine: {
show: true,
lineStyle: splitLineStyle,
},
minInterval:
dayDifference >= 89 // quarter
@@ -196,9 +192,8 @@ export class StateHistoryChartLine extends LitElement {
nameTextStyle: {
align: "left",
},
splitLine: {
show: true,
lineStyle: splitLineStyle,
axisLine: {
show: false,
},
axisLabel: {
margin: 5,

View File

@@ -197,9 +197,12 @@ export class StateHistoryChartTimeline extends LitElement {
max: this.endTime,
axisTick: {
show: true,
lineStyle: {
opacity: 0.4,
},
},
splitLine: {
show: false,
},
axisLine: {
show: false,
},
axisLabel: getTimeAxisLabelConfig(
this.hass.locale,

View File

@@ -135,7 +135,7 @@ export class StateHistoryCharts extends LitElement {
return html``;
}
if (!Array.isArray(item)) {
return html`<div class="entry-container">
return html`<div class="entry-container line">
<state-history-chart-line
.hass=${this.hass}
.unit=${item.unit}
@@ -299,6 +299,9 @@ export class StateHistoryCharts extends LitElement {
.entry-container {
width: 100%;
}
.entry-container.line {
flex: 1;
}

View File

@@ -56,6 +56,8 @@ export class StatisticsChart extends LitElement {
@property() public unit?: string;
@property({ attribute: false }) public startTime?: Date;
@property({ attribute: false }) public endTime?: Date;
@property({ attribute: false, type: Array })
@@ -124,6 +126,8 @@ export class StatisticsChart extends LitElement {
changedProps.has("fitYData") ||
changedProps.has("logarithmicScale") ||
changedProps.has("hideLegend") ||
changedProps.has("startTime") ||
changedProps.has("endTime") ||
changedProps.has("_legendData")
) {
this._createOptions();
@@ -218,6 +222,8 @@ export class StatisticsChart extends LitElement {
this.hass.config,
dayDifference
),
min: this.startTime,
max: this.endTime,
axisLine: {
show: false,
},
@@ -449,7 +455,11 @@ export class StatisticsChart extends LitElement {
borderWidth: 1.5,
}
: undefined,
color: band ? color + "3F" : color + "7F",
color: band
? color + "3F"
: this.chartType === "bar"
? color + "7F"
: color,
};
if (band && this.chartType === "line") {
series.stack = `band-${statistic_id}`;

View File

@@ -106,6 +106,7 @@ export class HaConfigApplicationCredentials extends LitElement {
},
actions: {
title: "",
label: localize("ui.panel.config.generic.headers.actions"),
type: "overflow-menu",
showNarrow: true,
hideable: false,

View File

@@ -378,8 +378,9 @@ class HaBackupConfigData extends LitElement {
}
@media all and (max-width: 450px) {
ha-md-select {
min-width: 160px;
width: 160px;
min-width: 140px;
width: 140px;
--md-filled-field-content-space: 0;
}
}
`;

View File

@@ -403,11 +403,11 @@ class HaBackupConfigSchedule extends LitElement {
backup_create: html`<a
href=${documentationUrl(
this.hass,
"/integrations/backup#example-backing-up-every-night-at-300-am"
"/integrations/backup/#action-backupcreate_automatic"
)}
target="_blank"
rel="noopener noreferrer"
>backup.create</a
>backup.create_automatic</a
>`,
})}</ha-tip
>
@@ -545,6 +545,12 @@ class HaBackupConfigSchedule extends LitElement {
ha-md-select,
ha-time-input {
min-width: 160px;
width: 160px;
--md-filled-field-content-space: 0;
}
ha-time-input {
min-width: 120px;
width: 120px;
}
}
ha-md-textfield#value {
@@ -553,6 +559,16 @@ class HaBackupConfigSchedule extends LitElement {
ha-md-select#type {
min-width: 100px;
}
@media all and (max-width: 450px) {
ha-md-textfield#value {
min-width: 60px;
margin: 0 -8px;
}
ha-md-select#type {
min-width: 120px;
width: 120px;
}
}
ha-expansion-panel {
--expansion-panel-summary-padding: 0 16px;
--expansion-panel-content-padding: 0 16px;

View File

@@ -141,7 +141,10 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
};
private _columns = memoizeOne(
(localize: LocalizeFunc): DataTableColumnContainer<BackupRow> => ({
(
localize: LocalizeFunc,
maxDisplayedAgents: number
): DataTableColumnContainer<BackupRow> => ({
name: {
title: localize("ui.panel.config.backup.name"),
main: true,
@@ -172,54 +175,75 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
locations: {
title: localize("ui.panel.config.backup.locations"),
showNarrow: true,
minWidth: "60px",
template: (backup) => html`
<div style="display: flex; gap: 4px;">
${(backup.agent_ids || []).map((agentId) => {
const name = computeBackupAgentName(
this.hass.localize,
agentId,
this.agents
);
if (isLocalAgent(agentId)) {
// 24 icon size, 4 gap, 16 left and right padding
minWidth: `${maxDisplayedAgents * 24 + (maxDisplayedAgents - 1) * 4 + 32}px`,
template: (backup) => {
const agentIds = backup.agent_ids;
const displayedAgentIds =
agentIds.length > maxDisplayedAgents
? [...agentIds].splice(0, maxDisplayedAgents - 1)
: agentIds;
const agentsMore = Math.max(
agentIds.length - displayedAgentIds.length,
0
);
return html`
<div style="display: flex; gap: 4px;">
${displayedAgentIds.map((agentId) => {
const name = computeBackupAgentName(
this.hass.localize,
agentId,
this.agents
);
if (isLocalAgent(agentId)) {
return html`
<ha-svg-icon
.path=${mdiHarddisk}
title=${name}
style="flex-shrink: 0;"
></ha-svg-icon>
`;
}
if (isNetworkMountAgent(agentId)) {
return html`
<ha-svg-icon
.path=${mdiNas}
title=${name}
style="flex-shrink: 0;"
></ha-svg-icon>
`;
}
const domain = computeDomain(agentId);
return html`
<ha-svg-icon
.path=${mdiHarddisk}
<img
title=${name}
.src=${brandsUrl({
domain,
type: "icon",
useFallback: true,
darkOptimized: this.hass.themes?.darkMode,
})}
height="24"
crossorigin="anonymous"
referrerpolicy="no-referrer"
alt=${name}
slot="graphic"
style="flex-shrink: 0;"
></ha-svg-icon>
/>
`;
}
if (isNetworkMountAgent(agentId)) {
return html`
<ha-svg-icon
.path=${mdiNas}
title=${name}
style="flex-shrink: 0;"
></ha-svg-icon>
`;
}
const domain = computeDomain(agentId);
return html`
<img
title=${name}
.src=${brandsUrl({
domain,
type: "icon",
useFallback: true,
darkOptimized: this.hass.themes?.darkMode,
})}
height="24"
crossorigin="anonymous"
referrerpolicy="no-referrer"
alt=${name}
slot="graphic"
style="flex-shrink: 0;"
/>
`;
})}
</div>
`,
})}
${agentsMore
? html`
<span
style="display: flex; align-items: center; font-size: 14px;"
>
+${agentsMore}
</span>
`
: nothing}
</div>
`;
},
},
actions: {
title: "",
@@ -293,20 +317,31 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
}
return filteredBackups.map((backup) => {
const type = backup.with_automatic_settings ? "automatic" : "manual";
const agentIds = Object.keys(backup.agents);
return {
...backup,
size: computeBackupSize(backup),
agent_ids: Object.keys(backup.agents).sort(compareAgents),
agent_ids: agentIds.sort(compareAgents),
formatted_type: localize(`ui.panel.config.backup.type.${type}`),
};
});
}
);
private _maxAgents = memoizeOne((data: BackupRow[]): number =>
Math.max(...data.map((row) => row.agent_ids.length))
);
protected render(): TemplateResult {
const backupInProgress =
"state" in this.manager && this.manager.state === "in_progress";
const data = this._data(this.backups, this._filters, this.hass.localize);
const maxDisplayedAgents = Math.min(
this._maxAgents(data),
this.narrow ? 3 : 5
);
return html`
<hass-tabs-subpage-data-table
has-fab
@@ -343,8 +378,8 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
@selection-changed=${this._handleSelectionChanged}
.route=${this.route}
@row-click=${this._showBackupDetails}
.columns=${this._columns(this.hass.localize)}
.data=${this._data(this.backups, this._filters, this.hass.localize)}
.columns=${this._columns(this.hass.localize, maxDisplayedAgents)}
.data=${data}
.noDataText=${this.hass.localize("ui.panel.config.backup.no_backups")}
.searchLabel=${this.hass.localize(
"ui.panel.config.backup.picker.search"

View File

@@ -50,9 +50,11 @@ class HaConfigBackupSettings extends LitElement {
}
}
protected firstUpdated(_changedProperties: PropertyValues): void {
super.firstUpdated(_changedProperties);
public connectedCallback(): void {
super.connectedCallback();
this._scrollToSection();
// Update config the page is displayed (e.g. when coming back from a location detail page)
this._config = this.config;
}
private async _scrollToSection() {

View File

@@ -119,6 +119,7 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) {
settings: {
tag: "ha-config-backup-settings",
load: () => import("./ha-config-backup-settings"),
cache: true,
},
location: {
tag: "ha-config-backup-location",

View File

@@ -1,8 +1,9 @@
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { CSSResultGroup, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import { storage } from "../../../../../common/decorators/storage";
import type { HASSDomEvent } from "../../../../../common/dom/fire_event";
import type { LocalizeFunc } from "../../../../../common/translations/localize";
import type {
@@ -11,9 +12,6 @@ import type {
} from "../../../../../components/data-table/ha-data-table";
import "../../../../../components/ha-fab";
import "../../../../../components/ha-icon-button";
import "../../../../../layouts/hass-tabs-subpage-data-table";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import type {
BluetoothDeviceData,
BluetoothScannersDetails,
@@ -22,6 +20,10 @@ import {
subscribeBluetoothAdvertisements,
subscribeBluetoothScannersDetails,
} from "../../../../../data/bluetooth";
import type { DeviceRegistryEntry } from "../../../../../data/device_registry";
import "../../../../../layouts/hass-tabs-subpage-data-table";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import { showBluetoothDeviceInfoDialog } from "./show-dialog-bluetooth-device-info";
@customElement("bluetooth-advertisement-monitor")
@@ -38,6 +40,22 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement {
@state() private _scanners: BluetoothScannersDetails = {};
@state() private _sourceDevices: Record<string, DeviceRegistryEntry> = {};
@storage({
key: "bluetooth-advertisement-table-grouping",
state: false,
subscribe: false,
})
private _activeGrouping?: string = "source";
@storage({
key: "bluetooth-advertisement-table-collapsed",
state: false,
subscribe: false,
})
private _activeCollapsed: string[] = [];
private _unsub_advertisements?: UnsubscribeFunc;
private _unsub_scanners?: UnsubscribeFunc;
@@ -57,6 +75,19 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement {
this._scanners = scanners;
}
);
const devices = Object.values(this.hass.devices);
const bluetoothDevices = devices.filter((device) =>
device.connections.find((connection) => connection[0] === "bluetooth")
);
this._sourceDevices = Object.fromEntries(
bluetoothDevices.map((device) => {
const connection = device.connections.find(
(c) => c[0] === "bluetooth"
)!;
return [connection[1], device];
})
);
}
}
@@ -84,21 +115,35 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement {
hideable: false,
moveable: false,
direction: "asc",
flex: 2,
flex: 1,
},
name: {
title: localize("ui.panel.config.bluetooth.name"),
filterable: true,
sortable: true,
},
device: {
title: localize("ui.panel.config.bluetooth.device"),
filterable: true,
sortable: true,
template: (data) => html`${data.device || "-"}`,
},
source: {
title: localize("ui.panel.config.bluetooth.source"),
filterable: true,
sortable: true,
groupable: true,
},
source_address: {
title: localize("ui.panel.config.bluetooth.source_address"),
filterable: true,
sortable: true,
defaultHidden: true,
},
rssi: {
title: localize("ui.panel.config.bluetooth.rssi"),
type: "numeric",
maxWidth: "60px",
sortable: true,
},
};
@@ -108,11 +153,22 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement {
);
private _dataWithNamedSourceAndIds = memoizeOne((data) =>
data.map((row) => ({
...row,
id: row.address,
source: this._scanners[row.source]?.name || row.source,
}))
data.map((row) => {
const device = this._sourceDevices[row.address];
const scannerDevice = this._sourceDevices[row.source];
const scanner = this._scanners[row.source];
return {
...row,
id: row.address,
source_address: row.source,
source:
scannerDevice?.name_by_user ||
scannerDevice?.name ||
scanner?.name ||
row.source,
device: device?.name_by_user || device?.name || undefined,
};
})
);
protected render(): TemplateResult {
@@ -124,11 +180,23 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement {
.columns=${this._columns(this.hass.localize)}
.data=${this._dataWithNamedSourceAndIds(this._data)}
@row-click=${this._handleRowClicked}
.initialGroupColumn=${this._activeGrouping}
.initialCollapsedGroups=${this._activeCollapsed}
@grouping-changed=${this._handleGroupingChanged}
@collapsed-changed=${this._handleCollapseChanged}
clickable
></hass-tabs-subpage-data-table>
`;
}
private _handleGroupingChanged(ev: CustomEvent) {
this._activeGrouping = ev.detail.value;
}
private _handleCollapseChanged(ev: CustomEvent) {
this._activeCollapsed = ev.detail.value;
}
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const entry = this._data.find((ent) => ent.address === ev.detail.id);
showBluetoothDeviceInfoDialog(this, {

View File

@@ -53,8 +53,6 @@ class DialogBluetoothDeviceInfo extends LitElement implements HassDialog {
return html`
<ha-dialog
open
scrimClickAction
escapeKeyAction
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,

View File

@@ -314,8 +314,9 @@ export class HuiEnergyDevicesDetailGraphCard
processedData.forEach((device) => {
device.data.forEach((datapoint) => {
totalDeviceConsumption[datapoint[0]] =
(totalDeviceConsumption[datapoint[0]] || 0) + datapoint[1];
totalDeviceConsumption[datapoint[compare ? 2 : 0]] =
(totalDeviceConsumption[datapoint[compare ? 2 : 0]] || 0) +
datapoint[1];
});
});
const compareOffset = compare
@@ -333,10 +334,12 @@ export class HuiEnergyDevicesDetailGraphCard
}
untrackedConsumption.push(dataPoint);
});
// random id to always add untracked at the end
const order = Date.now();
const dataset: BarSeriesOption = {
type: "bar",
cursor: "default",
id: compare ? "compare-untracked" : "untracked",
id: compare ? `compare-untracked-${order}` : `untracked-${order}`,
name: this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_devices_detail_graph.untracked_consumption"
),
@@ -419,9 +422,10 @@ export class HuiEnergyDevicesDetailGraphCard
data.push({
type: "bar",
cursor: "default",
// add order to id, otherwise echarts refuses to reorder them
id: compare
? `compare-${source.stat_consumption}`
: source.stat_consumption,
? `compare-${source.stat_consumption}-${order}`
: `${source.stat_consumption}-${order}`,
name:
source.name ||
getStatisticLabel(
@@ -438,7 +442,9 @@ export class HuiEnergyDevicesDetailGraphCard
stack: compare ? "devicesCompare" : "devices",
});
});
return data;
return sorted_devices.map(
(device) => data.find((d) => (d.id as string).includes(device))!
);
}
static styles = css`

View File

@@ -88,7 +88,7 @@ export class HuiEnergyDevicesGraphCard
<ha-chart-base
.hass=${this.hass}
.data=${this._chartData}
.options=${this._createOptions(this.hass.themes?.darkMode)}
.options=${this._createOptions(this._chartData)}
.height=${`${(this._chartData[0]?.data?.length || 0) * 28 + 50}px`}
@chart-click=${this._handleChartClick}
></ha-chart-base>
@@ -110,18 +110,17 @@ export class HuiEnergyDevicesGraphCard
}
private _createOptions = memoizeOne(
(darkMode: boolean): ECOption => ({
(data: BarSeriesOption[]): ECOption => ({
xAxis: {
type: "value",
name: "kWh",
splitLine: {
lineStyle: darkMode ? { opacity: 0.15 } : {},
},
},
yAxis: {
type: "category",
inverse: true,
triggerEvent: true,
// take order from data
data: data[0]?.data?.map((d: any) => d.value[1]),
axisLabel: {
formatter: this._getDeviceName.bind(this),
overflow: "truncate",

View File

@@ -6,7 +6,10 @@ import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import "../../../components/ha-card";
import { getEnergyDataCollection } from "../../../data/energy";
import { getSuggestedPeriod } from "./energy/common/energy-chart-options";
import {
getSuggestedMax,
getSuggestedPeriod,
} from "./energy/common/energy-chart-options";
import type {
Statistics,
StatisticsMetaData,
@@ -274,10 +277,19 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
.unit=${this._unit}
.minYAxis=${this._config.min_y_axis}
.maxYAxis=${this._config.max_y_axis}
.startTime=${this._energyStart}
.endTime=${this._energyEnd && this._energyStart
? getSuggestedMax(
differenceInDays(this._energyEnd, this._energyStart),
this._energyEnd
)
: undefined}
.fitYData=${this._config.fit_y_data || false}
.hideLegend=${this._config.hide_legend || false}
.logarithmicScale=${this._config.logarithmic_scale || false}
.daysToShow=${this._config.days_to_show || DEFAULT_DAYS_TO_SHOW}
.daysToShow=${this._energyStart && this._energyEnd
? differenceInDays(this._energyEnd, this._energyStart)
: this._config.days_to_show || DEFAULT_DAYS_TO_SHOW}
.height=${this._config.grid_options?.rows ? "100%" : undefined}
></statistics-chart>
</div>

View File

@@ -200,10 +200,9 @@ export class HuiGenericEntityRow extends LitElement {
padding-inline-start: 16px;
padding-inline-end: 8px;
flex: 1 1 30%;
min-height: 40px;
display: flex;
flex-direction: column;
justify-content: center;
}
.info:not(:has(.secondary)) {
line-height: 40px;
}
.info,
.info > * {
@@ -237,9 +236,7 @@ export class HuiGenericEntityRow extends LitElement {
}
.value {
direction: ltr;
min-height: 40px;
display: flex;
align-items: center;
line-height: 40px;
}
`;
}

View File

@@ -2678,19 +2678,19 @@
"encryption": {
"title": "Encryption",
"description": "All your backups are encrypted by default to keep your data private and secure.",
"location_encrypted": "This location is encrypted",
"location_unencrypted": "This location is unencrypted",
"location_encrypted_description": "Your data private and secure by securing it with your encryption key.",
"location_encrypted": "Backups made to this location will be encrypted",
"location_unencrypted": "Backups made to this location will be unencrypted",
"location_encrypted_description": "Your data is private and secure by encrypting backups with your encryption key.",
"location_encrypted_cloud_description": "Home Assistant Cloud is the privacy-focused cloud. This is why it will only accept encrypted backups and why we dont store your encryption key.",
"location_encrypted_cloud_learn_more": "Learn more",
"location_unencrypted_description": "Please keep your backups private and secure.",
"encryption_turn_on": "Turn on",
"encryption_turn_off": "Turn off",
"encryption_turn_off_confirm_title": "Turn encryption off?",
"encryption_turn_off_confirm_text": "All your next backups will not be encrypted for this location. Please keep your backups private and secure.",
"encryption_turn_off_confirm_text": "After confirming, backups created will be unencrypted for this location. Please ensure your backups remain private and secure.",
"encryption_turn_off_confirm_action": "Turn encryption off",
"warning_encryption_turn_off": "Encryption turned off",
"warning_encryption_turn_off_description": "All your next backups will not be encrypted."
"warning_encryption_turn_off_description": "Backups will be unencrypted."
}
}
},
@@ -5330,6 +5330,8 @@
"name": "Name",
"source": "Source",
"rssi": "RSSI",
"source_address": "Source address",
"device": "Device",
"device_information": "Device information",
"advertisement_data": "Advertisement data",
"manufacturer_data": "Manufacturer data",

104
yarn.lock
View File

@@ -5000,15 +5000,15 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:8.21.0":
version: 8.21.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.21.0"
"@typescript-eslint/eslint-plugin@npm:8.22.0":
version: 8.22.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.22.0"
dependencies:
"@eslint-community/regexpp": "npm:^4.10.0"
"@typescript-eslint/scope-manager": "npm:8.21.0"
"@typescript-eslint/type-utils": "npm:8.21.0"
"@typescript-eslint/utils": "npm:8.21.0"
"@typescript-eslint/visitor-keys": "npm:8.21.0"
"@typescript-eslint/scope-manager": "npm:8.22.0"
"@typescript-eslint/type-utils": "npm:8.22.0"
"@typescript-eslint/utils": "npm:8.22.0"
"@typescript-eslint/visitor-keys": "npm:8.22.0"
graphemer: "npm:^1.4.0"
ignore: "npm:^5.3.1"
natural-compare: "npm:^1.4.0"
@@ -5017,64 +5017,64 @@ __metadata:
"@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.8.0"
checksum: 10/4c7c274bd0f7d8ee2097278d9fb0829b883c28783b9a1c41e5f4e74dee0412c53063978db3590ad7609d538a38058e43f832895746e6af677da7558a8b16fbdd
checksum: 10/7211ad95f20a27182e2b55ef50102dfee4a7084c267c4e24cca24f0a28daa0360074a38bb71e407dad6d99db1165096b324b708cf35904b1d4f62fc9d5fd0f98
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:8.21.0":
version: 8.21.0
resolution: "@typescript-eslint/parser@npm:8.21.0"
"@typescript-eslint/parser@npm:8.22.0":
version: 8.22.0
resolution: "@typescript-eslint/parser@npm:8.22.0"
dependencies:
"@typescript-eslint/scope-manager": "npm:8.21.0"
"@typescript-eslint/types": "npm:8.21.0"
"@typescript-eslint/typescript-estree": "npm:8.21.0"
"@typescript-eslint/visitor-keys": "npm:8.21.0"
"@typescript-eslint/scope-manager": "npm:8.22.0"
"@typescript-eslint/types": "npm:8.22.0"
"@typescript-eslint/typescript-estree": "npm:8.22.0"
"@typescript-eslint/visitor-keys": "npm:8.22.0"
debug: "npm:^4.3.4"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.8.0"
checksum: 10/c403f56b0a856ad25ffc0d78d4f0ede64d622edb279ace8bc7554c82518c0462f608a1e06d62111633a57b9ffcc37e063378c3980fba138f93d14a7aad5d0db1
checksum: 10/6b7fee52345e8a32d8cfea1ac4aeb563cb0c44ba46290686afde1cd541b787fcf61bec0e6960559f544e9ba3b72670a68f8eda860384aebb5744101f0f1a68c9
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.21.0":
version: 8.21.0
resolution: "@typescript-eslint/scope-manager@npm:8.21.0"
"@typescript-eslint/scope-manager@npm:8.22.0":
version: 8.22.0
resolution: "@typescript-eslint/scope-manager@npm:8.22.0"
dependencies:
"@typescript-eslint/types": "npm:8.21.0"
"@typescript-eslint/visitor-keys": "npm:8.21.0"
checksum: 10/99aa8257c758546c8c4905bd34381be446adea7642dbc279269039308dc33b8dbcf3d7b7d12da7bec8f8d8760b813a5852dc53d75e953cbe327fac05d3f18fc4
"@typescript-eslint/types": "npm:8.22.0"
"@typescript-eslint/visitor-keys": "npm:8.22.0"
checksum: 10/7fb4bae6d9f8b86a43405b24828cd36ba0751cce4346d86821a4827cded93227f92668044e5e6d802a32096b50cfcaf2ce9ab65322310fa68f5e3819bef70168
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.21.0":
version: 8.21.0
resolution: "@typescript-eslint/type-utils@npm:8.21.0"
"@typescript-eslint/type-utils@npm:8.22.0":
version: 8.22.0
resolution: "@typescript-eslint/type-utils@npm:8.22.0"
dependencies:
"@typescript-eslint/typescript-estree": "npm:8.21.0"
"@typescript-eslint/utils": "npm:8.21.0"
"@typescript-eslint/typescript-estree": "npm:8.22.0"
"@typescript-eslint/utils": "npm:8.22.0"
debug: "npm:^4.3.4"
ts-api-utils: "npm:^2.0.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.8.0"
checksum: 10/b4bce1325a2e5e1a74b6919b3187356b7246475ac4d62898134ed68572e39e52fe5daa89d0bb66d78aef7b2057612cccc00400c0b81f9d5b75acec3174114c8d
checksum: 10/1da2447ce12f09370082daeef88f8922842e39d2a7b0abe3def21442f85bf4250524c60cbb97276d5cd876783b976dcb0ed85aeb8c0b100d83b7f3a59cdfccbf
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.21.0":
version: 8.21.0
resolution: "@typescript-eslint/types@npm:8.21.0"
checksum: 10/a22c99f5687358c7343789b942c9885bc1b49eb239562b792f22e2ac4f0d3f04102f204cd2d749202d6888767566fba86f54447894955310890ec307fec8ed8d
"@typescript-eslint/types@npm:8.22.0":
version: 8.22.0
resolution: "@typescript-eslint/types@npm:8.22.0"
checksum: 10/b43ea5b05ed0b43dcee8d2fa98b2c3f79c604780cbd56e6ba7f89e3066798b7169848694f59523fd2003e8fa699ddc97f28b0860a4eb04eea26c96d5ac9346bd
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.21.0":
version: 8.21.0
resolution: "@typescript-eslint/typescript-estree@npm:8.21.0"
"@typescript-eslint/typescript-estree@npm:8.22.0":
version: 8.22.0
resolution: "@typescript-eslint/typescript-estree@npm:8.22.0"
dependencies:
"@typescript-eslint/types": "npm:8.21.0"
"@typescript-eslint/visitor-keys": "npm:8.21.0"
"@typescript-eslint/types": "npm:8.22.0"
"@typescript-eslint/visitor-keys": "npm:8.22.0"
debug: "npm:^4.3.4"
fast-glob: "npm:^3.3.2"
is-glob: "npm:^4.0.3"
@@ -5083,32 +5083,32 @@ __metadata:
ts-api-utils: "npm:^2.0.0"
peerDependencies:
typescript: ">=4.8.4 <5.8.0"
checksum: 10/1a8bcd2968490dcf047273a36e1d2cd51725e893ad874e554e4b81e62bf54e4ff2b7ee2af206208a2ae9ac2cc5c8b50e2244dd4fe9c42ef34122df4360e9f9c2
checksum: 10/e3c0b191e2a0f55101c3e3333904f3a255d635e4ea0d026981cc25e83b62660a3a8a7993ac4a3d0c8756afb7dc272099eec48fd93e100a2b8467a5b80ef0026c
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.21.0":
version: 8.21.0
resolution: "@typescript-eslint/utils@npm:8.21.0"
"@typescript-eslint/utils@npm:8.22.0":
version: 8.22.0
resolution: "@typescript-eslint/utils@npm:8.22.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.4.0"
"@typescript-eslint/scope-manager": "npm:8.21.0"
"@typescript-eslint/types": "npm:8.21.0"
"@typescript-eslint/typescript-estree": "npm:8.21.0"
"@typescript-eslint/scope-manager": "npm:8.22.0"
"@typescript-eslint/types": "npm:8.22.0"
"@typescript-eslint/typescript-estree": "npm:8.22.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <5.8.0"
checksum: 10/e44b4e87b8227f7524b4cd16e833ea37fbb73d3829caf484e7ca737060908817788755b9481d053bc4371bbcc99d2477e32b7ad43a421a3e61ce46c2c48c0bd7
checksum: 10/92a5ae5d79a5988e88fdda8d5e88f73e7b9ce24b339098d72698dba766ded274c24d0e2857bcb799c0aa7a59257e54a273eabdaaab39a5cd20283669201eeb53
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.21.0":
version: 8.21.0
resolution: "@typescript-eslint/visitor-keys@npm:8.21.0"
"@typescript-eslint/visitor-keys@npm:8.22.0":
version: 8.22.0
resolution: "@typescript-eslint/visitor-keys@npm:8.22.0"
dependencies:
"@typescript-eslint/types": "npm:8.21.0"
"@typescript-eslint/types": "npm:8.22.0"
eslint-visitor-keys: "npm:^4.2.0"
checksum: 10/781cafa354177de4e864511435dbe61d896a7d1331bca76dd0a151295cc27b9677412444d47e1d5c6d2e7de5fee29b7bef0489d76c494fa59139e421f860506a
checksum: 10/1a172620d46e23362c5d1e1e7c8186856dff6b6f1c2697d67f9aac1b3dfd0de96c2c73487e4deed80fad3bfa5cf74cfed3519221657c6ede602b04ac091525a4
languageName: node
linkType: hard
@@ -9230,8 +9230,8 @@ __metadata:
"@types/tar": "npm:6.1.13"
"@types/ua-parser-js": "npm:0.7.39"
"@types/webspeechapi": "npm:0.0.29"
"@typescript-eslint/eslint-plugin": "npm:8.21.0"
"@typescript-eslint/parser": "npm:8.21.0"
"@typescript-eslint/eslint-plugin": "npm:8.22.0"
"@typescript-eslint/parser": "npm:8.22.0"
"@vaadin/combo-box": "npm:24.6.2"
"@vaadin/vaadin-themable-mixin": "npm:24.6.2"
"@vibrant/color": "npm:4.0.0"