Merge branch 'rc'

This commit is contained in:
Bram Kragten 2025-02-21 19:31:20 +01:00
commit 4a1b7d46ca
7 changed files with 90 additions and 31 deletions

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20250214.0"
version = "20250221.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"

View File

@ -6,7 +6,6 @@ import type { DataZoomComponentOption } from "echarts/components";
import type { EChartsType } from "echarts/core";
import type {
ECElementEvent,
SetOptionOpts,
XAXisOption,
YAXisOption,
} from "echarts/types/dist/shared";
@ -25,6 +24,7 @@ import type { HomeAssistant } from "../../types";
import { isMac } from "../../util/is_mac";
import "../ha-icon-button";
import { formatTimeLabel } from "./axis-label";
import { ensureArray } from "../../common/array/ensure-array";
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
@ -68,12 +68,16 @@ export class HaChartBase extends LitElement {
private _listeners: (() => void)[] = [];
private _originalZrFlush?: () => void;
public disconnectedCallback() {
super.disconnectedCallback();
while (this._listeners.length) {
this._listeners.pop()!();
}
this.chart?.dispose();
this.chart = undefined;
this._originalZrFlush = undefined;
}
public connectedCallback() {
@ -86,7 +90,7 @@ export class HaChartBase extends LitElement {
listenMediaQuery("(prefers-reduced-motion)", (matches) => {
if (this._reducedMotion !== matches) {
this._reducedMotion = matches;
this.chart?.setOption({ animation: !this._reducedMotion });
this._setChartOptions({ animation: !this._reducedMotion });
}
})
);
@ -96,7 +100,7 @@ export class HaChartBase extends LitElement {
if ((isMac && ev.key === "Meta") || (!isMac && ev.key === "Control")) {
this._modifierPressed = true;
if (!this.options?.dataZoom) {
this.chart?.setOption({ dataZoom: this._getDataZoomConfig() });
this._setChartOptions({ dataZoom: this._getDataZoomConfig() });
}
}
};
@ -105,7 +109,7 @@ export class HaChartBase extends LitElement {
if ((isMac && ev.key === "Meta") || (!isMac && ev.key === "Control")) {
this._modifierPressed = false;
if (!this.options?.dataZoom) {
this.chart?.setOption({ dataZoom: this._getDataZoomConfig() });
this._setChartOptions({ dataZoom: this._getDataZoomConfig() });
}
}
};
@ -131,10 +135,8 @@ export class HaChartBase extends LitElement {
return;
}
let chartOptions: ECOption = {};
const chartUpdateParams: SetOptionOpts = { lazyUpdate: true };
if (changedProps.has("data")) {
chartOptions.series = this.data;
chartUpdateParams.replaceMerge = ["series"];
}
if (changedProps.has("options")) {
chartOptions = { ...chartOptions, ...this._createOptions() };
@ -142,7 +144,7 @@ export class HaChartBase extends LitElement {
chartOptions.dataZoom = this._getDataZoomConfig();
}
if (Object.keys(chartOptions).length > 0) {
this.chart.setOption(chartOptions, chartUpdateParams);
this._setChartOptions(chartOptions);
}
}
@ -509,6 +511,31 @@ export class HaChartBase extends LitElement {
return Math.max(this.clientWidth / 2, 200);
}
private _setChartOptions(options: ECOption) {
if (!this.chart) {
return;
}
if (!this._originalZrFlush) {
const dataSize = ensureArray(this.data).reduce(
(acc, series) => acc + (series.data as any[]).length,
0
);
if (dataSize > 10000) {
// delay the last bit of the render to avoid blocking the main thread
// this is not that impactful with sampling enabled but it doesn't hurt to have it
const zr = this.chart.getZr();
this._originalZrFlush = zr.flush;
zr.flush = () => {
setTimeout(() => {
this._originalZrFlush?.call(zr);
}, 5);
};
}
}
const replaceMerge = options.series ? ["series"] : [];
this.chart.setOption(options, { replaceMerge });
}
private _handleZoomReset() {
this.chart?.dispatchAction({ type: "dataZoom", start: 0, end: 100 });
}

View File

@ -354,9 +354,10 @@ export class StateHistoryChartLine extends LitElement {
name: nameY,
color,
symbol: "circle",
step: "end",
animationDurationUpdate: 0,
symbolSize: 1,
step: "end",
sampling: "minmax",
animationDurationUpdate: 0,
lineStyle: {
width: fill ? 0 : 1.5,
},

View File

@ -492,8 +492,8 @@ export class StatisticsChart extends LitElement {
: this.hass.localize(
`ui.components.statistics_charts.statistic_types.${type}`
),
symbol: "circle",
symbolSize: 0,
symbol: "none",
sampling: "minmax",
animationDurationUpdate: 0,
lineStyle: {
width: 1.5,
@ -511,7 +511,6 @@ export class StatisticsChart extends LitElement {
if (band && this.chartType === "line") {
series.stack = `band-${statistic_id}`;
series.stackStrategy = "all";
(series as LineSeriesOption).symbol = "none";
if (drawBands && type === "max") {
(series as LineSeriesOption).areaStyle = {
color: color + "3F",

View File

@ -244,20 +244,23 @@ export const restoreBackup = async (
type: HassioBackupDetail["type"],
backupSlug: string,
backupDetails: HassioPartialBackupCreateParams | HassioFullBackupCreateParams,
useSnapshotUrl: boolean
useBackupUrl: boolean
): Promise<void> => {
if (hass) {
await hass.callApi<HassioResponse<{ job_id: string }>>(
"POST",
`hassio/${useSnapshotUrl ? "snapshots" : "backups"}/${backupSlug}/restore/${type}`,
`hassio/${useBackupUrl ? "backups" : "snapshots"}/${backupSlug}/restore/${type}`,
backupDetails
);
} else {
await handleFetchPromise(
fetch(`/api/hassio/backups/${backupSlug}/restore/${type}`, {
method: "POST",
body: JSON.stringify(backupDetails),
})
fetch(
`/api/hassio/${useBackupUrl ? "backups" : "snapshots"}/${backupSlug}/restore/${type}`,
{
method: "POST",
body: JSON.stringify(backupDetails),
}
)
);
}
};

View File

@ -46,7 +46,7 @@ enum BackupScheduleTime {
}
interface RetentionData {
type: "copies" | "days";
type: "copies" | "days" | "forever";
value: number;
}
@ -55,7 +55,7 @@ const RETENTION_PRESETS: Record<
RetentionData
> = {
copies_3: { type: "copies", value: 3 },
forever: { type: "days", value: 0 },
forever: { type: "forever", value: 0 },
};
const SCHEDULE_OPTIONS = [
@ -79,7 +79,10 @@ const computeRetentionPreset = (
data: RetentionData
): RetentionPreset | undefined => {
for (const [key, value] of Object.entries(RETENTION_PRESETS)) {
if (value.type === data.type && value.value === data.value) {
if (
value.type === data.type &&
(value.type === RetentionPreset.FOREVER || value.value === data.value)
) {
return key as RetentionPreset;
}
}
@ -92,7 +95,7 @@ interface FormData {
time?: string | null;
days: BackupDay[];
retention: {
type: "copies" | "days";
type: "copies" | "days" | "forever";
value: number;
};
}
@ -142,7 +145,12 @@ class HaBackupConfigSchedule extends LitElement {
? config.schedule.days
: [],
retention: {
type: config.retention.days != null ? "days" : "copies",
type:
config.retention.days === null && config.retention.copies === null
? "forever"
: config.retention.days != null
? "days"
: "copies",
value: config.retention.days ?? config.retention.copies ?? 3,
},
};
@ -160,9 +168,11 @@ class HaBackupConfigSchedule extends LitElement {
: [],
},
retention:
data.retention.type === "days"
? { days: data.retention.value, copies: null }
: { copies: data.retention.value, days: null },
data.retention.type === "forever"
? { days: null, copies: null }
: data.retention.type === "days"
? { days: data.retention.value, copies: null }
: { copies: data.retention.value, days: null },
};
fireEvent(this, "value-changed", { value: this.value });
@ -481,9 +491,19 @@ class HaBackupConfigSchedule extends LitElement {
private _retentionPresetChanged(ev) {
ev.stopPropagation();
const target = ev.currentTarget as HaMdSelect;
const value = target.value as RetentionPreset;
let value = target.value as RetentionPreset;
// custom needs to have a type of days or copies, set it to default copies 3
if (
value === RetentionPreset.CUSTOM &&
this._retentionPreset === RetentionPreset.FOREVER
) {
this._retentionPreset = value;
value = RetentionPreset.COPIES_3;
} else {
this._retentionPreset = value;
}
this._retentionPreset = value;
if (value !== RetentionPreset.CUSTOM) {
const data = this._getData(this.value);
const retention = RETENTION_PRESETS[value];
@ -493,7 +513,7 @@ class HaBackupConfigSchedule extends LitElement {
}
this._setData({
...data,
retention: RETENTION_PRESETS[value],
retention,
});
}
}
@ -504,6 +524,7 @@ class HaBackupConfigSchedule extends LitElement {
const value = parseInt(target.value);
const clamped = clamp(value, MIN_VALUE, MAX_VALUE);
const data = this._getData(this.value);
target.value = clamped.toString();
this._setData({
...data,
retention: {

View File

@ -448,7 +448,15 @@ export class HuiEnergyDevicesDetailGraphCard
});
});
return sorted_devices
.map((device) => data.find((d) => (d.id as string).includes(device))!)
.map(
(device) =>
data.find((d) => {
const id = (d.id as string)
.replace(/^compare-/, "") // Remove compare- prefix
.replace(/-\d+$/, ""); // Remove numeric suffix
return id === device;
})!
)
.filter(Boolean);
}