mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 03:36:44 +00:00
Merge branch 'rc'
This commit is contained in:
commit
c68002214f
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20250210.0"
|
version = "20250214.0"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -6,6 +6,7 @@ import type { DataZoomComponentOption } from "echarts/components";
|
|||||||
import type { EChartsType } from "echarts/core";
|
import type { EChartsType } from "echarts/core";
|
||||||
import type {
|
import type {
|
||||||
ECElementEvent,
|
ECElementEvent,
|
||||||
|
SetOptionOpts,
|
||||||
XAXisOption,
|
XAXisOption,
|
||||||
YAXisOption,
|
YAXisOption,
|
||||||
} from "echarts/types/dist/shared";
|
} from "echarts/types/dist/shared";
|
||||||
@ -83,19 +84,19 @@ export class HaChartBase extends LitElement {
|
|||||||
|
|
||||||
this._listeners.push(
|
this._listeners.push(
|
||||||
listenMediaQuery("(prefers-reduced-motion)", (matches) => {
|
listenMediaQuery("(prefers-reduced-motion)", (matches) => {
|
||||||
|
if (this._reducedMotion !== matches) {
|
||||||
this._reducedMotion = matches;
|
this._reducedMotion = matches;
|
||||||
this.chart?.setOption({ animation: !this._reducedMotion });
|
this.chart?.setOption({ animation: !this._reducedMotion });
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add keyboard event listeners
|
// Add keyboard event listeners
|
||||||
const handleKeyDown = (ev: KeyboardEvent) => {
|
const handleKeyDown = (ev: KeyboardEvent) => {
|
||||||
if ((isMac && ev.metaKey) || (!isMac && ev.ctrlKey)) {
|
if ((isMac && ev.key === "Meta") || (!isMac && ev.key === "Control")) {
|
||||||
this._modifierPressed = true;
|
this._modifierPressed = true;
|
||||||
if (!this.options?.dataZoom) {
|
if (!this.options?.dataZoom) {
|
||||||
this.chart?.setOption({
|
this.chart?.setOption({ dataZoom: this._getDataZoomConfig() });
|
||||||
dataZoom: this._getDataZoomConfig(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -104,9 +105,7 @@ export class HaChartBase extends LitElement {
|
|||||||
if ((isMac && ev.key === "Meta") || (!isMac && ev.key === "Control")) {
|
if ((isMac && ev.key === "Meta") || (!isMac && ev.key === "Control")) {
|
||||||
this._modifierPressed = false;
|
this._modifierPressed = false;
|
||||||
if (!this.options?.dataZoom) {
|
if (!this.options?.dataZoom) {
|
||||||
this.chart?.setOption({
|
this.chart?.setOption({ dataZoom: this._getDataZoomConfig() });
|
||||||
dataZoom: this._getDataZoomConfig(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -124,27 +123,26 @@ export class HaChartBase extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public willUpdate(changedProps: PropertyValues): void {
|
public willUpdate(changedProps: PropertyValues): void {
|
||||||
super.willUpdate(changedProps);
|
if (!this.chart) {
|
||||||
|
|
||||||
if (!this.hasUpdated || !this.chart) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (changedProps.has("_themes")) {
|
if (changedProps.has("_themes")) {
|
||||||
this._setupChart();
|
this._setupChart();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let chartOptions: ECOption = {};
|
||||||
|
const chartUpdateParams: SetOptionOpts = { lazyUpdate: true };
|
||||||
if (changedProps.has("data")) {
|
if (changedProps.has("data")) {
|
||||||
this.chart.setOption(
|
chartOptions.series = this.data;
|
||||||
{ series: this.data },
|
chartUpdateParams.replaceMerge = ["series"];
|
||||||
{ lazyUpdate: true, replaceMerge: ["series"] }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (changedProps.has("options") || changedProps.has("_isZoomed")) {
|
if (changedProps.has("options")) {
|
||||||
this.chart.setOption(this._createOptions(), {
|
chartOptions = { ...chartOptions, ...this._createOptions() };
|
||||||
lazyUpdate: true,
|
} else if (this._isTouchDevice && changedProps.has("_isZoomed")) {
|
||||||
// if we replace the whole object, it will reset the dataZoom
|
chartOptions.dataZoom = this._getDataZoomConfig();
|
||||||
replaceMerge: ["grid"],
|
}
|
||||||
});
|
if (Object.keys(chartOptions).length > 0) {
|
||||||
|
this.chart.setOption(chartOptions, chartUpdateParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +156,6 @@ export class HaChartBase extends LitElement {
|
|||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
height: this.height ?? `${this._getDefaultHeight()}px`,
|
height: this.height ?? `${this._getDefaultHeight()}px`,
|
||||||
})}
|
})}
|
||||||
@wheel=${this._handleWheel}
|
|
||||||
>
|
>
|
||||||
<div class="chart"></div>
|
<div class="chart"></div>
|
||||||
${this._isZoomed
|
${this._isZoomed
|
||||||
@ -240,8 +237,8 @@ export class HaChartBase extends LitElement {
|
|||||||
type: "inside",
|
type: "inside",
|
||||||
orient: "horizontal",
|
orient: "horizontal",
|
||||||
filterMode: "none",
|
filterMode: "none",
|
||||||
moveOnMouseMove: this._isZoomed,
|
moveOnMouseMove: !this._isTouchDevice || this._isZoomed,
|
||||||
preventDefaultMouseMove: this._isZoomed,
|
preventDefaultMouseMove: !this._isTouchDevice || this._isZoomed,
|
||||||
zoomLock: !this._isTouchDevice && !this._modifierPressed,
|
zoomLock: !this._isTouchDevice && !this._modifierPressed,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -514,23 +511,6 @@ export class HaChartBase extends LitElement {
|
|||||||
|
|
||||||
private _handleZoomReset() {
|
private _handleZoomReset() {
|
||||||
this.chart?.dispatchAction({ type: "dataZoom", start: 0, end: 100 });
|
this.chart?.dispatchAction({ type: "dataZoom", start: 0, end: 100 });
|
||||||
this._modifierPressed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleWheel(e: WheelEvent) {
|
|
||||||
// if the window is not focused, we don't receive the keydown events but scroll still works
|
|
||||||
if (!this.options?.dataZoom) {
|
|
||||||
const modifierPressed = (isMac && e.metaKey) || (!isMac && e.ctrlKey);
|
|
||||||
if (modifierPressed) {
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
if (modifierPressed !== this._modifierPressed) {
|
|
||||||
this._modifierPressed = modifierPressed;
|
|
||||||
this.chart?.setOption({
|
|
||||||
dataZoom: this._getDataZoomConfig(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
|
@ -75,6 +75,8 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
|
|
||||||
@state() private _yWidth = 25;
|
@state() private _yWidth = 25;
|
||||||
|
|
||||||
|
@state() private _visualMap?: VisualMapComponentOption[];
|
||||||
|
|
||||||
private _chartTime: Date = new Date();
|
private _chartTime: Date = new Date();
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@ -92,7 +94,7 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderTooltip(params: any) {
|
private _renderTooltip = (params: any) => {
|
||||||
const time = params[0].axisValue;
|
const time = params[0].axisValue;
|
||||||
const title =
|
const title =
|
||||||
formatDateTimeWithSeconds(
|
formatDateTimeWithSeconds(
|
||||||
@ -115,7 +117,7 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// If the datapoint is not found, we need to find the last datapoint before the current time
|
// If the datapoint is not found, we need to find the last datapoint before the current time
|
||||||
let lastData;
|
let lastData: any;
|
||||||
const data = dataset.data || [];
|
const data = dataset.data || [];
|
||||||
for (let i = data.length - 1; i >= 0; i--) {
|
for (let i = data.length - 1; i >= 0; i--) {
|
||||||
const point = data[i];
|
const point = data[i];
|
||||||
@ -175,7 +177,7 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
})
|
})
|
||||||
.join("<br>")
|
.join("<br>")
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
private _datasetHidden(ev: CustomEvent) {
|
private _datasetHidden(ev: CustomEvent) {
|
||||||
this._hiddenStats.add(ev.detail.name);
|
this._hiddenStats.add(ev.detail.name);
|
||||||
@ -208,8 +210,8 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
changedProps.has("minYAxis") ||
|
changedProps.has("minYAxis") ||
|
||||||
changedProps.has("maxYAxis") ||
|
changedProps.has("maxYAxis") ||
|
||||||
changedProps.has("fitYData") ||
|
changedProps.has("fitYData") ||
|
||||||
changedProps.has("_chartData") ||
|
|
||||||
changedProps.has("paddingYAxis") ||
|
changedProps.has("paddingYAxis") ||
|
||||||
|
changedProps.has("_visualMap") ||
|
||||||
changedProps.has("_yWidth")
|
changedProps.has("_yWidth")
|
||||||
) {
|
) {
|
||||||
const rtl = computeRTL(this.hass);
|
const rtl = computeRTL(this.hass);
|
||||||
@ -280,37 +282,11 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
right: rtl ? Math.max(this.paddingYAxis, this._yWidth) : 1,
|
right: rtl ? Math.max(this.paddingYAxis, this._yWidth) : 1,
|
||||||
bottom: 30,
|
bottom: 30,
|
||||||
},
|
},
|
||||||
visualMap: this._chartData
|
visualMap: this._visualMap,
|
||||||
.map((_, seriesIndex) => {
|
|
||||||
const dataIndex = this._datasetToDataIndex[seriesIndex];
|
|
||||||
const data = this.data[dataIndex];
|
|
||||||
if (!data.statistics || data.statistics.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// render stat data with a slightly transparent line
|
|
||||||
const firstStateTS =
|
|
||||||
data.states[0]?.last_changed ?? this.endTime.getTime();
|
|
||||||
return {
|
|
||||||
show: false,
|
|
||||||
seriesIndex,
|
|
||||||
dimension: 0,
|
|
||||||
pieces: [
|
|
||||||
{
|
|
||||||
max: firstStateTS - 0.01,
|
|
||||||
colorAlpha: 0.5,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
min: firstStateTS,
|
|
||||||
colorAlpha: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter(Boolean) as VisualMapComponentOption[],
|
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: "axis",
|
trigger: "axis",
|
||||||
appendTo: document.body,
|
appendTo: document.body,
|
||||||
formatter: this._renderTooltip.bind(this),
|
formatter: this._renderTooltip,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -725,6 +701,33 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
this._chartData = datasets;
|
this._chartData = datasets;
|
||||||
this._entityIds = entityIds;
|
this._entityIds = entityIds;
|
||||||
this._datasetToDataIndex = datasetToDataIndex;
|
this._datasetToDataIndex = datasetToDataIndex;
|
||||||
|
const visualMap: VisualMapComponentOption[] = [];
|
||||||
|
this._chartData.forEach((_, seriesIndex) => {
|
||||||
|
const dataIndex = this._datasetToDataIndex[seriesIndex];
|
||||||
|
const data = this.data[dataIndex];
|
||||||
|
if (!data.statistics || data.statistics.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// render stat data with a slightly transparent line
|
||||||
|
const firstStateTS =
|
||||||
|
data.states[0]?.last_changed ?? this.endTime.getTime();
|
||||||
|
visualMap.push({
|
||||||
|
show: false,
|
||||||
|
seriesIndex,
|
||||||
|
dimension: 0,
|
||||||
|
pieces: [
|
||||||
|
{
|
||||||
|
max: firstStateTS - 0.01,
|
||||||
|
colorAlpha: 0.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
min: firstStateTS,
|
||||||
|
colorAlpha: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this._visualMap = visualMap.length > 0 ? visualMap : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _clampYAxis(value?: number | ((values: any) => number)) {
|
private _clampYAxis(value?: number | ((values: any) => number)) {
|
||||||
|
@ -273,11 +273,13 @@ export class StatisticsChart extends LitElement {
|
|||||||
this._chartOptions = {
|
this._chartOptions = {
|
||||||
xAxis: [
|
xAxis: [
|
||||||
{
|
{
|
||||||
|
id: "xAxis",
|
||||||
type: "time",
|
type: "time",
|
||||||
min: startTime,
|
min: startTime,
|
||||||
max: endTime,
|
max: this.endTime,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: "hiddenAxis",
|
||||||
type: "time",
|
type: "time",
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
@ -368,7 +370,6 @@ export class StatisticsChart extends LitElement {
|
|||||||
if (endTime > new Date()) {
|
if (endTime > new Date()) {
|
||||||
endTime = new Date();
|
endTime = new Date();
|
||||||
}
|
}
|
||||||
this.endTime = endTime;
|
|
||||||
|
|
||||||
let unit: string | undefined | null;
|
let unit: string | undefined | null;
|
||||||
|
|
||||||
|
@ -66,6 +66,18 @@ const randomTip = (hass: HomeAssistant, narrow: boolean) => {
|
|||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>${hass.localize("ui.panel.config.tips.join_x")}</a
|
>${hass.localize("ui.panel.config.tips.join_x")}</a
|
||||||
>`,
|
>`,
|
||||||
|
mastodon: html`<a
|
||||||
|
href=${documentationUrl(hass, `/mastodon`)}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>${hass.localize("ui.panel.config.tips.join_mastodon")}</a
|
||||||
|
>`,
|
||||||
|
bluesky: html`<a
|
||||||
|
href=${documentationUrl(hass, `/bluesky`)}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>${hass.localize("ui.panel.config.tips.join_bluesky")}</a
|
||||||
|
>`,
|
||||||
discord: html`<a
|
discord: html`<a
|
||||||
href=${documentationUrl(hass, `/join-chat`)}
|
href=${documentationUrl(hass, `/join-chat`)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
@ -70,7 +70,7 @@ export class HaConfigFlowCard extends LitElement {
|
|||||||
? html`<a
|
? html`<a
|
||||||
href=${this.flow.context.configuration_url.replace(
|
href=${this.flow.context.configuration_url.replace(
|
||||||
/^homeassistant:\/\//,
|
/^homeassistant:\/\//,
|
||||||
""
|
"/"
|
||||||
)}
|
)}
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
target=${this.flow.context.configuration_url.startsWith(
|
target=${this.flow.context.configuration_url.startsWith(
|
||||||
|
@ -76,6 +76,8 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
|||||||
@state()
|
@state()
|
||||||
private _statistics?: ZWaveJSControllerStatisticsUpdatedMessage;
|
private _statistics?: ZWaveJSControllerStatisticsUpdatedMessage;
|
||||||
|
|
||||||
|
private _dialogOpen = false;
|
||||||
|
|
||||||
protected async firstUpdated() {
|
protected async firstUpdated() {
|
||||||
if (this.hass) {
|
if (this.hass) {
|
||||||
await this._fetchData();
|
await this._fetchData();
|
||||||
@ -104,11 +106,17 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
subscribeS2Inclusion(this.hass, this.configEntryId, (message) => {
|
subscribeS2Inclusion(this.hass, this.configEntryId, (message) => {
|
||||||
|
if (!this._dialogOpen) {
|
||||||
showZWaveJSAddNodeDialog(this, {
|
showZWaveJSAddNodeDialog(this, {
|
||||||
entry_id: this.configEntryId,
|
entry_id: this.configEntryId,
|
||||||
dsk: message.dsk,
|
dsk: message.dsk,
|
||||||
onStop: () => setTimeout(() => this._fetchData(), 100),
|
onStop: () => {
|
||||||
|
setTimeout(() => this._fetchData(), 100);
|
||||||
|
this._dialogOpen = false;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
this._dialogOpen = true;
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -570,11 +578,17 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _addNodeClicked() {
|
private async _addNodeClicked() {
|
||||||
|
if (!this._dialogOpen) {
|
||||||
showZWaveJSAddNodeDialog(this, {
|
showZWaveJSAddNodeDialog(this, {
|
||||||
entry_id: this.configEntryId!,
|
entry_id: this.configEntryId!,
|
||||||
// refresh the data after the dialog is closed. add a small delay for the inclusion state to update
|
// refresh the data after the dialog is closed. add a small delay for the inclusion state to update
|
||||||
onStop: () => setTimeout(() => this._fetchData(), 100),
|
onStop: () => {
|
||||||
|
setTimeout(() => this._fetchData(), 100);
|
||||||
|
this._dialogOpen = false;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
this._dialogOpen = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _removeNodeClicked() {
|
private async _removeNodeClicked() {
|
||||||
|
@ -327,7 +327,7 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
|
|||||||
);
|
);
|
||||||
const endDate = this._energyEnd;
|
const endDate = this._energyEnd;
|
||||||
try {
|
try {
|
||||||
let unitClass;
|
let unitClass: string | undefined | null;
|
||||||
if (this._config!.unit && this._metadata) {
|
if (this._config!.unit && this._metadata) {
|
||||||
const metadata = Object.values(this._metadata).find(
|
const metadata = Object.values(this._metadata).find(
|
||||||
(metaData) =>
|
(metaData) =>
|
||||||
|
@ -240,6 +240,7 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
|
|||||||
.container.edit-mode {
|
.container.edit-mode {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border-radius: var(--ha-card-border-radius, 12px);
|
border-radius: var(--ha-card-border-radius, 12px);
|
||||||
|
border-start-end-radius: 0;
|
||||||
border: 2px dashed var(--divider-color);
|
border: 2px dashed var(--divider-color);
|
||||||
min-height: var(--row-height);
|
min-height: var(--row-height);
|
||||||
}
|
}
|
||||||
|
@ -6052,8 +6052,10 @@
|
|||||||
},
|
},
|
||||||
"tips": {
|
"tips": {
|
||||||
"tip": "Tip!",
|
"tip": "Tip!",
|
||||||
"join": "Join the community on our {forums}, {twitter}, {discord}, {blog} or {newsletter}",
|
"join": "Join the community on our {forums}, {mastodon}, {bluesky}, {twitter}, {discord}, {blog} or {newsletter}",
|
||||||
"join_x": "X (formerly Twitter)",
|
"join_x": "X (formerly Twitter)",
|
||||||
|
"join_mastodon": "Mastodon",
|
||||||
|
"join_bluesky": "Bluesky",
|
||||||
"join_forums": "Forums",
|
"join_forums": "Forums",
|
||||||
"join_chat": "Chat",
|
"join_chat": "Chat",
|
||||||
"join_blog": "Blog",
|
"join_blog": "Blog",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user