mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-30 11:39:26 +00:00
Compare commits
9 Commits
move-defau
...
20250527.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
06270c771f | ||
![]() |
ae49de8e71 | ||
![]() |
6abdeeae20 | ||
![]() |
116716c51d | ||
![]() |
77ee69b64d | ||
![]() |
1a57eeddde | ||
![]() |
9131bf6dfd | ||
![]() |
1611423ca5 | ||
![]() |
de56c3376e |
@@ -1,7 +1,30 @@
|
|||||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
|
let changeFunction;
|
||||||
|
|
||||||
export const mockFrontend = (hass: MockHomeAssistant) => {
|
export const mockFrontend = (hass: MockHomeAssistant) => {
|
||||||
hass.mockWS("frontend/get_user_data", () => ({
|
hass.mockWS("frontend/get_user_data", () => ({
|
||||||
value: null,
|
value: null,
|
||||||
}));
|
}));
|
||||||
|
hass.mockWS("frontend/set_user_data", ({ key, value }) => {
|
||||||
|
if (key === "sidebar") {
|
||||||
|
changeFunction?.({
|
||||||
|
value: {
|
||||||
|
panelOrder: value.panelOrder || [],
|
||||||
|
hiddenPanels: value.hiddenPanels || [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
hass.mockWS("frontend/subscribe_user_data", (_msg, _hass, onChange) => {
|
||||||
|
changeFunction = onChange;
|
||||||
|
onChange?.({
|
||||||
|
value: {
|
||||||
|
panelOrder: [],
|
||||||
|
hiddenPanels: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
return () => {};
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20250430.0"
|
version = "20250527.0"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
license-files = ["LICENSE*"]
|
license-files = ["LICENSE*"]
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
|
@@ -220,11 +220,11 @@ export class HaChartBase extends LitElement {
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
const datasets = ensureArray(this.data);
|
const datasets = ensureArray(this.data);
|
||||||
const items = (legend.data ||
|
const items: LegendComponentOption["data"] =
|
||||||
datasets
|
legend.data ||
|
||||||
|
((datasets
|
||||||
.filter((d) => (d.data as any[])?.length && (d.id || d.name))
|
.filter((d) => (d.data as any[])?.length && (d.id || d.name))
|
||||||
.map((d) => d.name ?? d.id) ||
|
.map((d) => d.name ?? d.id) || []) as string[]);
|
||||||
[]) as string[];
|
|
||||||
|
|
||||||
const isMobile = window.matchMedia(
|
const isMobile = window.matchMedia(
|
||||||
"all and (max-width: 450px), all and (max-height: 500px)"
|
"all and (max-width: 450px), all and (max-height: 500px)"
|
||||||
@@ -239,20 +239,32 @@ export class HaChartBase extends LitElement {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<ul>
|
<ul>
|
||||||
${items.map((item: string, index: number) => {
|
${items.map((item, index) => {
|
||||||
if (!this.expandLegend && index >= overflowLimit) {
|
if (!this.expandLegend && index >= overflowLimit) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
let itemStyle: Record<string, any> = {};
|
||||||
|
let name = "";
|
||||||
|
if (typeof item === "string") {
|
||||||
|
name = item;
|
||||||
const dataset = datasets.find(
|
const dataset = datasets.find(
|
||||||
(d) => d.id === item || d.name === item
|
(d) => d.id === item || d.name === item
|
||||||
);
|
);
|
||||||
const color = dataset?.color as string;
|
itemStyle = {
|
||||||
const borderColor = dataset?.itemStyle?.borderColor as string;
|
color: dataset?.color as string,
|
||||||
|
...(dataset?.itemStyle as { borderColor?: string }),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
name = item.name ?? "";
|
||||||
|
itemStyle = item.itemStyle ?? {};
|
||||||
|
}
|
||||||
|
const color = itemStyle?.color as string;
|
||||||
|
const borderColor = itemStyle?.borderColor as string;
|
||||||
return html`<li
|
return html`<li
|
||||||
.name=${item}
|
.name=${name}
|
||||||
@click=${this._legendClick}
|
@click=${this._legendClick}
|
||||||
class=${classMap({ hidden: this._hiddenDatasets.has(item) })}
|
class=${classMap({ hidden: this._hiddenDatasets.has(name) })}
|
||||||
.title=${item}
|
.title=${name}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="bullet"
|
class="bullet"
|
||||||
@@ -261,7 +273,7 @@ export class HaChartBase extends LitElement {
|
|||||||
borderColor: borderColor || color,
|
borderColor: borderColor || color,
|
||||||
})}
|
})}
|
||||||
></div>
|
></div>
|
||||||
<div class="label">${item}</div>
|
<div class="label">${name}</div>
|
||||||
</li>`;
|
</li>`;
|
||||||
})}
|
})}
|
||||||
${items.length > overflowLimit
|
${items.length > overflowLimit
|
||||||
|
@@ -82,6 +82,8 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
|
|
||||||
private _chartTime: Date = new Date();
|
private _chartTime: Date = new Date();
|
||||||
|
|
||||||
|
private _previousYAxisLabelValue = 0;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<ha-chart-base
|
<ha-chart-base
|
||||||
@@ -258,32 +260,7 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
},
|
},
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
margin: 5,
|
margin: 5,
|
||||||
formatter: (value: number) => {
|
formatter: this._formatYAxisLabel,
|
||||||
const formatOptions =
|
|
||||||
value >= 1 || value <= -1
|
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
// show the first significant digit for tiny values
|
|
||||||
maximumFractionDigits: Math.max(
|
|
||||||
2,
|
|
||||||
-Math.floor(Math.log10(Math.abs(value % 1 || 1)))
|
|
||||||
),
|
|
||||||
};
|
|
||||||
const label = formatNumber(
|
|
||||||
value,
|
|
||||||
this.hass.locale,
|
|
||||||
formatOptions
|
|
||||||
);
|
|
||||||
const width = measureTextWidth(label, 12) + 5;
|
|
||||||
if (width > this._yWidth) {
|
|
||||||
this._yWidth = width;
|
|
||||||
fireEvent(this, "y-width-changed", {
|
|
||||||
value: this._yWidth,
|
|
||||||
chartIndex: this.chartIndex,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return label;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
} as YAXisOption,
|
} as YAXisOption,
|
||||||
legend: {
|
legend: {
|
||||||
@@ -745,6 +722,33 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
this._visualMap = visualMap.length > 0 ? visualMap : undefined;
|
this._visualMap = visualMap.length > 0 ? visualMap : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _formatYAxisLabel = (value: number) => {
|
||||||
|
const formatOptions =
|
||||||
|
value >= 1 || value <= -1
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
// show the first significant digit for tiny values
|
||||||
|
maximumFractionDigits: Math.max(
|
||||||
|
2,
|
||||||
|
// use the difference to the previous value to determine the number of significant digits #25526
|
||||||
|
-Math.floor(
|
||||||
|
Math.log10(Math.abs(value - this._previousYAxisLabelValue || 1))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
const label = formatNumber(value, this.hass.locale, formatOptions);
|
||||||
|
const width = measureTextWidth(label, 12) + 5;
|
||||||
|
if (width > this._yWidth) {
|
||||||
|
this._yWidth = width;
|
||||||
|
fireEvent(this, "y-width-changed", {
|
||||||
|
value: this._yWidth,
|
||||||
|
chartIndex: this.chartIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._previousYAxisLabelValue = value;
|
||||||
|
return label;
|
||||||
|
};
|
||||||
|
|
||||||
private _clampYAxis(value?: number | ((values: any) => number)) {
|
private _clampYAxis(value?: number | ((values: any) => number)) {
|
||||||
if (this.logarithmicScale) {
|
if (this.logarithmicScale) {
|
||||||
// log(0) is -Infinity, so we need to set a minimum value
|
// log(0) is -Infinity, so we need to set a minimum value
|
||||||
|
@@ -262,7 +262,7 @@ export class HaItemDisplayEditor extends LitElement {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.sort((a, b) =>
|
return visibleItems.sort((a, b) =>
|
||||||
a.disableSorting && !b.disableSorting ? -1 : compare(a.value, b.value)
|
a.disableSorting && !b.disableSorting ? -1 : compare(a.value, b.value)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -368,7 +368,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
if (!this._panelOrder || !this._hiddenPanels) {
|
if (!this._panelOrder || !this._hiddenPanels) {
|
||||||
return html`
|
return html`
|
||||||
<ha-fade-in .delay=${500}
|
<ha-fade-in .delay=${500}
|
||||||
><ha-spinner size="large"></ha-spinner
|
><ha-spinner size="small"></ha-spinner
|
||||||
></ha-fade-in>
|
></ha-fade-in>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -640,6 +640,12 @@ export const mergeHistoryResults = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const item of ltsResult.line) {
|
for (const item of ltsResult.line) {
|
||||||
|
if (item.unit === BLANK_UNIT) {
|
||||||
|
// disabled entities have no unit, so we need to find the unit from the history result
|
||||||
|
item.unit =
|
||||||
|
historyResult.line.find((line) => line.identifier === item.identifier)
|
||||||
|
?.unit ?? BLANK_UNIT;
|
||||||
|
}
|
||||||
const key = computeGroupKey(
|
const key = computeGroupKey(
|
||||||
item.unit,
|
item.unit,
|
||||||
item.device_class,
|
item.device_class,
|
||||||
|
@@ -6,6 +6,7 @@ import { customElement, property, state } from "lit/decorators";
|
|||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import type { BarSeriesOption } from "echarts/charts";
|
import type { BarSeriesOption } from "echarts/charts";
|
||||||
|
import type { LegendComponentOption } from "echarts/components";
|
||||||
import { getGraphColorByIndex } from "../../../../common/color/colors";
|
import { getGraphColorByIndex } from "../../../../common/color/colors";
|
||||||
import { getEnergyColor } from "./common/color";
|
import { getEnergyColor } from "./common/color";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
@@ -54,6 +55,8 @@ export class HuiEnergyDevicesDetailGraphCard
|
|||||||
|
|
||||||
@state() private _data?: EnergyData;
|
@state() private _data?: EnergyData;
|
||||||
|
|
||||||
|
@state() private _legendData?: LegendComponentOption["data"];
|
||||||
|
|
||||||
@state() private _start = startOfToday();
|
@state() private _start = startOfToday();
|
||||||
|
|
||||||
@state() private _end = endOfToday();
|
@state() private _end = endOfToday();
|
||||||
@@ -185,6 +188,7 @@ export class HuiEnergyDevicesDetailGraphCard
|
|||||||
legend: {
|
legend: {
|
||||||
show: true,
|
show: true,
|
||||||
type: "custom",
|
type: "custom",
|
||||||
|
data: this._legendData,
|
||||||
selected: this._hiddenStats.reduce((acc, stat) => {
|
selected: this._hiddenStats.reduce((acc, stat) => {
|
||||||
acc[stat] = false;
|
acc[stat] = false;
|
||||||
return acc;
|
return acc;
|
||||||
@@ -310,6 +314,13 @@ export class HuiEnergyDevicesDetailGraphCard
|
|||||||
);
|
);
|
||||||
|
|
||||||
datasets.push(...processedData);
|
datasets.push(...processedData);
|
||||||
|
this._legendData = processedData.map((d) => ({
|
||||||
|
name: d.name as string,
|
||||||
|
itemStyle: {
|
||||||
|
color: d.color as string,
|
||||||
|
borderColor: d.itemStyle?.borderColor as string,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
if (showUntracked) {
|
if (showUntracked) {
|
||||||
const untrackedData = this._processUntracked(
|
const untrackedData = this._processUntracked(
|
||||||
@@ -319,6 +330,13 @@ export class HuiEnergyDevicesDetailGraphCard
|
|||||||
false
|
false
|
||||||
);
|
);
|
||||||
datasets.push(untrackedData);
|
datasets.push(untrackedData);
|
||||||
|
this._legendData.push({
|
||||||
|
name: untrackedData.name as string,
|
||||||
|
itemStyle: {
|
||||||
|
color: untrackedData.color as string,
|
||||||
|
borderColor: untrackedData.itemStyle?.borderColor as string,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fillDataGapsAndRoundCaps(datasets);
|
fillDataGapsAndRoundCaps(datasets);
|
||||||
|
@@ -224,19 +224,19 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
|||||||
filter: colored ? stateColorBrightness(stateObj) : undefined,
|
filter: colored ? stateColorBrightness(stateObj) : undefined,
|
||||||
height: this._config.icon_height
|
height: this._config.icon_height
|
||||||
? this._config.icon_height
|
? this._config.icon_height
|
||||||
: "",
|
: undefined,
|
||||||
})}
|
})}
|
||||||
></ha-state-icon>
|
></ha-state-icon>
|
||||||
`
|
`
|
||||||
: ""}
|
: nothing}
|
||||||
${this._config.show_name
|
${this._config.show_name
|
||||||
? html`<span tabindex="-1" .title=${name}>${name}</span>`
|
? html`<span tabindex="-1" .title=${name}>${name}</span>`
|
||||||
: ""}
|
: nothing}
|
||||||
${this._config.show_state && stateObj
|
${this._config.show_state && stateObj
|
||||||
? html`<span class="state">
|
? html`<span class="state">
|
||||||
${this.hass.formatEntityState(stateObj)}
|
${this.hass.formatEntityState(stateObj)}
|
||||||
</span>`
|
</span>`
|
||||||
: ""}
|
: nothing}
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -282,7 +282,8 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 4% 0;
|
padding: 4% 0;
|
||||||
font-size: 16.8px;
|
font-size: var(--ha-font-size-l);
|
||||||
|
line-height: var(--ha-line-height-condensed);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@@ -5131,7 +5131,7 @@
|
|||||||
"restore_entity_id_selected": {
|
"restore_entity_id_selected": {
|
||||||
"button": "Recreate entity IDs of selected",
|
"button": "Recreate entity IDs of selected",
|
||||||
"confirm_title": "Recreate entity IDs?",
|
"confirm_title": "Recreate entity IDs?",
|
||||||
"confirm_text": "Are you sure you want to change the entity IDs of these entities? You will have to change you dashboards, automations and scripts to use the new entity IDs.",
|
"confirm_text": "Are you sure you want to change the entity IDs of these entities? You will have to change your dashboards, automations and scripts to use the new entity IDs.",
|
||||||
"changes": "The following entity IDs will be updated:"
|
"changes": "The following entity IDs will be updated:"
|
||||||
},
|
},
|
||||||
"delete_selected": {
|
"delete_selected": {
|
||||||
|
Reference in New Issue
Block a user