mirror of
https://github.com/home-assistant/frontend.git
synced 2026-08-26 00:27:35 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd00b51f21 | ||
|
|
64b886eea0 |
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20250130.0"
|
||||
version = "20250129.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import memoizeOne from "memoize-one";
|
||||
import { theme2hex } from "./convert-color";
|
||||
|
||||
export const COLORS = [
|
||||
@@ -75,12 +74,3 @@ 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")
|
||||
);
|
||||
|
||||
@@ -20,7 +20,6 @@ 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;
|
||||
|
||||
@@ -184,9 +183,10 @@ export class HaChartBase extends LitElement {
|
||||
}
|
||||
const echarts = (await import("../../resources/echarts")).default;
|
||||
|
||||
echarts.registerTheme("custom", this._createTheme());
|
||||
|
||||
this.chart = echarts.init(container, "custom");
|
||||
this.chart = echarts.init(
|
||||
container,
|
||||
this._themes.darkMode ? "dark" : "light"
|
||||
);
|
||||
this.chart.on("legendselectchanged", (params: any) => {
|
||||
if (this.externalHidden) {
|
||||
const isSelected = params.selected[params.name];
|
||||
@@ -237,14 +237,24 @@ export class HaChartBase extends LitElement {
|
||||
}
|
||||
|
||||
private _createOptions(): ECOption {
|
||||
const darkMode = this._themes.darkMode ?? false;
|
||||
|
||||
const options = {
|
||||
backgroundColor: "transparent",
|
||||
animation: !this._reducedMotion,
|
||||
darkMode: this._themes.darkMode ?? false,
|
||||
darkMode,
|
||||
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(
|
||||
@@ -264,181 +274,6 @@ 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);
|
||||
}
|
||||
|
||||
@@ -158,6 +158,9 @@ 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",
|
||||
@@ -173,6 +176,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: splitLineStyle,
|
||||
},
|
||||
minInterval:
|
||||
dayDifference >= 89 // quarter
|
||||
@@ -192,8 +196,9 @@ export class StateHistoryChartLine extends LitElement {
|
||||
nameTextStyle: {
|
||||
align: "left",
|
||||
},
|
||||
axisLine: {
|
||||
show: false,
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: splitLineStyle,
|
||||
},
|
||||
axisLabel: {
|
||||
margin: 5,
|
||||
|
||||
@@ -197,12 +197,9 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
max: this.endTime,
|
||||
axisTick: {
|
||||
show: true,
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
axisLine: {
|
||||
show: false,
|
||||
lineStyle: {
|
||||
opacity: 0.4,
|
||||
},
|
||||
},
|
||||
axisLabel: getTimeAxisLabelConfig(
|
||||
this.hass.locale,
|
||||
|
||||
@@ -135,7 +135,7 @@ export class StateHistoryCharts extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
if (!Array.isArray(item)) {
|
||||
return html`<div class="entry-container line">
|
||||
return html`<div class="entry-container">
|
||||
<state-history-chart-line
|
||||
.hass=${this.hass}
|
||||
.unit=${item.unit}
|
||||
@@ -299,9 +299,6 @@ export class StateHistoryCharts extends LitElement {
|
||||
|
||||
.entry-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.entry-container.line {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,6 @@ export class HaConfigApplicationCredentials extends LitElement {
|
||||
},
|
||||
actions: {
|
||||
title: "",
|
||||
label: localize("ui.panel.config.generic.headers.actions"),
|
||||
type: "overflow-menu",
|
||||
showNarrow: true,
|
||||
hideable: false,
|
||||
|
||||
@@ -378,9 +378,8 @@ class HaBackupConfigData extends LitElement {
|
||||
}
|
||||
@media all and (max-width: 450px) {
|
||||
ha-md-select {
|
||||
min-width: 140px;
|
||||
width: 140px;
|
||||
--md-filled-field-content-space: 0;
|
||||
min-width: 160px;
|
||||
width: 160px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -403,11 +403,11 @@ class HaBackupConfigSchedule extends LitElement {
|
||||
backup_create: html`<a
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
"/integrations/backup/#action-backupcreate_automatic"
|
||||
"/integrations/backup#example-backing-up-every-night-at-300-am"
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>backup.create_automatic</a
|
||||
>backup.create</a
|
||||
>`,
|
||||
})}</ha-tip
|
||||
>
|
||||
@@ -545,12 +545,6 @@ 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 {
|
||||
@@ -559,16 +553,6 @@ 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;
|
||||
|
||||
@@ -141,10 +141,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
};
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(
|
||||
localize: LocalizeFunc,
|
||||
maxDisplayedAgents: number
|
||||
): DataTableColumnContainer<BackupRow> => ({
|
||||
(localize: LocalizeFunc): DataTableColumnContainer<BackupRow> => ({
|
||||
name: {
|
||||
title: localize("ui.panel.config.backup.name"),
|
||||
main: true,
|
||||
@@ -175,75 +172,54 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
locations: {
|
||||
title: localize("ui.panel.config.backup.locations"),
|
||||
showNarrow: true,
|
||||
// 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);
|
||||
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)) {
|
||||
return html`
|
||||
<img
|
||||
<ha-svg-icon
|
||||
.path=${mdiHarddisk}
|
||||
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>
|
||||
`;
|
||||
})}
|
||||
${agentsMore
|
||||
? html`
|
||||
<span
|
||||
style="display: flex; align-items: center; font-size: 14px;"
|
||||
>
|
||||
+${agentsMore}
|
||||
</span>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
}
|
||||
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>
|
||||
`,
|
||||
},
|
||||
actions: {
|
||||
title: "",
|
||||
@@ -317,31 +293,20 @@ 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: agentIds.sort(compareAgents),
|
||||
agent_ids: Object.keys(backup.agents).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
|
||||
@@ -378,8 +343,8 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
@selection-changed=${this._handleSelectionChanged}
|
||||
.route=${this.route}
|
||||
@row-click=${this._showBackupDetails}
|
||||
.columns=${this._columns(this.hass.localize, maxDisplayedAgents)}
|
||||
.data=${data}
|
||||
.columns=${this._columns(this.hass.localize)}
|
||||
.data=${this._data(this.backups, this._filters, this.hass.localize)}
|
||||
.noDataText=${this.hass.localize("ui.panel.config.backup.no_backups")}
|
||||
.searchLabel=${this.hass.localize(
|
||||
"ui.panel.config.backup.picker.search"
|
||||
|
||||
@@ -50,11 +50,9 @@ class HaConfigBackupSettings extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
protected firstUpdated(_changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(_changedProperties);
|
||||
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() {
|
||||
|
||||
@@ -119,7 +119,6 @@ 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",
|
||||
|
||||
+9
-77
@@ -1,9 +1,8 @@
|
||||
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 { storage } from "../../../../../common/decorators/storage";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { HASSDomEvent } from "../../../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||
import type {
|
||||
@@ -12,6 +11,9 @@ 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,
|
||||
@@ -20,10 +22,6 @@ 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")
|
||||
@@ -40,22 +38,6 @@ 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;
|
||||
@@ -75,19 +57,6 @@ 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];
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,28 +91,14 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement {
|
||||
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,
|
||||
},
|
||||
};
|
||||
@@ -153,22 +108,11 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement {
|
||||
);
|
||||
|
||||
private _dataWithNamedSourceAndIds = memoizeOne((data) =>
|
||||
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,
|
||||
};
|
||||
})
|
||||
data.map((row) => ({
|
||||
...row,
|
||||
id: row.address,
|
||||
source: this._scanners[row.source]?.name || row.source,
|
||||
}))
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -180,23 +124,11 @@ 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, {
|
||||
|
||||
+2
@@ -53,6 +53,8 @@ class DialogBluetoothDeviceInfo extends LitElement implements HassDialog {
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
|
||||
@@ -314,9 +314,8 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
|
||||
processedData.forEach((device) => {
|
||||
device.data.forEach((datapoint) => {
|
||||
totalDeviceConsumption[datapoint[compare ? 2 : 0]] =
|
||||
(totalDeviceConsumption[datapoint[compare ? 2 : 0]] || 0) +
|
||||
datapoint[1];
|
||||
totalDeviceConsumption[datapoint[0]] =
|
||||
(totalDeviceConsumption[datapoint[0]] || 0) + datapoint[1];
|
||||
});
|
||||
});
|
||||
const compareOffset = compare
|
||||
@@ -334,12 +333,10 @@ 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-${order}` : `untracked-${order}`,
|
||||
id: compare ? "compare-untracked" : "untracked",
|
||||
name: this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_devices_detail_graph.untracked_consumption"
|
||||
),
|
||||
@@ -422,10 +419,9 @@ 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}-${order}`
|
||||
: `${source.stat_consumption}-${order}`,
|
||||
? `compare-${source.stat_consumption}`
|
||||
: source.stat_consumption,
|
||||
name:
|
||||
source.name ||
|
||||
getStatisticLabel(
|
||||
@@ -442,9 +438,7 @@ export class HuiEnergyDevicesDetailGraphCard
|
||||
stack: compare ? "devicesCompare" : "devices",
|
||||
});
|
||||
});
|
||||
return sorted_devices.map(
|
||||
(device) => data.find((d) => (d.id as string).includes(device))!
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
|
||||
@@ -88,7 +88,7 @@ export class HuiEnergyDevicesGraphCard
|
||||
<ha-chart-base
|
||||
.hass=${this.hass}
|
||||
.data=${this._chartData}
|
||||
.options=${this._createOptions(this._chartData)}
|
||||
.options=${this._createOptions(this.hass.themes?.darkMode)}
|
||||
.height=${`${(this._chartData[0]?.data?.length || 0) * 28 + 50}px`}
|
||||
@chart-click=${this._handleChartClick}
|
||||
></ha-chart-base>
|
||||
@@ -110,17 +110,18 @@ export class HuiEnergyDevicesGraphCard
|
||||
}
|
||||
|
||||
private _createOptions = memoizeOne(
|
||||
(data: BarSeriesOption[]): ECOption => ({
|
||||
(darkMode: boolean): 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",
|
||||
|
||||
@@ -201,7 +201,9 @@ export class HuiGenericEntityRow extends LitElement {
|
||||
padding-inline-end: 8px;
|
||||
flex: 1 1 30%;
|
||||
min-height: 40px;
|
||||
line-height: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.info,
|
||||
.info > * {
|
||||
@@ -236,7 +238,8 @@ export class HuiGenericEntityRow extends LitElement {
|
||||
.value {
|
||||
direction: ltr;
|
||||
min-height: 40px;
|
||||
line-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -286,7 +286,10 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
|
||||
clearInterval(this.__backendPingInterval);
|
||||
this.__backendPingInterval = setInterval(() => {
|
||||
if (this.hass?.connected) {
|
||||
promiseTimeout(5000, this.hass?.connection.ping()).catch(() => {
|
||||
// If the backend if busy, or the connection is latent,
|
||||
// it can take more than 10 seconds for the ping to return.
|
||||
// We give it a 15 second timeout to be safe.
|
||||
promiseTimeout(15000, this.hass?.connection.ping()).catch(() => {
|
||||
if (!this.hass?.connected) {
|
||||
return;
|
||||
}
|
||||
@@ -296,7 +299,7 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
|
||||
this.hass?.connection.reconnect(true);
|
||||
});
|
||||
}
|
||||
}, 10000);
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
protected hassReconnected() {
|
||||
|
||||
@@ -2678,19 +2678,19 @@
|
||||
"encryption": {
|
||||
"title": "Encryption",
|
||||
"description": "All your backups are encrypted by default to keep your data private and secure.",
|
||||
"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": "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_cloud_description": "Home Assistant Cloud is the privacy-focused cloud. This is why it will only accept encrypted backups and why we don’t 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": "After confirming, backups created will be unencrypted for this location. Please ensure your backups remain private and secure.",
|
||||
"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_action": "Turn encryption off",
|
||||
"warning_encryption_turn_off": "Encryption turned off",
|
||||
"warning_encryption_turn_off_description": "Backups will be unencrypted."
|
||||
"warning_encryption_turn_off_description": "All your next backups will not be encrypted."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5330,8 +5330,6 @@
|
||||
"name": "Name",
|
||||
"source": "Source",
|
||||
"rssi": "RSSI",
|
||||
"source_address": "Source address",
|
||||
"device": "Device",
|
||||
"device_information": "Device information",
|
||||
"advertisement_data": "Advertisement data",
|
||||
"manufacturer_data": "Manufacturer data",
|
||||
|
||||
Reference in New Issue
Block a user