mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Merge branch 'rc'
This commit is contained in:
commit
4a1b7d46ca
@ -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"
|
||||
|
@ -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 });
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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",
|
||||
|
@ -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),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -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: {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user