mirror of
https://github.com/home-assistant/frontend.git
synced 2025-10-05 17:59:37 +00:00
Compare commits
21 Commits
loading-an
...
energy-pie
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1d45c6c3fa | ||
![]() |
9f40b344ec | ||
![]() |
046d87d828 | ||
![]() |
165f265694 | ||
![]() |
ec3c8b616f | ||
![]() |
36daad1a10 | ||
![]() |
0d3315936e | ||
![]() |
2d6d0300b8 | ||
![]() |
3c15fc32b3 | ||
![]() |
552691e200 | ||
![]() |
91258c86c1 | ||
![]() |
3750a378cd | ||
![]() |
3231316b27 | ||
![]() |
ed467991cf | ||
![]() |
c208431956 | ||
![]() |
12d3304c72 | ||
![]() |
49f5512dd4 | ||
![]() |
aaa95266b6 | ||
![]() |
246100809d | ||
![]() |
6efca93186 | ||
![]() |
6280647b9a |
@@ -34,7 +34,7 @@
|
||||
"@codemirror/legacy-modes": "6.5.1",
|
||||
"@codemirror/search": "6.5.11",
|
||||
"@codemirror/state": "6.5.2",
|
||||
"@codemirror/view": "6.38.3",
|
||||
"@codemirror/view": "6.38.4",
|
||||
"@date-fns/tz": "1.4.1",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "6.18.0",
|
||||
@@ -203,7 +203,7 @@
|
||||
"husky": "9.1.7",
|
||||
"jsdom": "27.0.0",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "16.2.1",
|
||||
"lint-staged": "16.2.3",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.5.0",
|
||||
|
@@ -32,6 +32,8 @@ export const numberFormatToLocale = (
|
||||
return ["de", "es", "it"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89
|
||||
case NumberFormat.space_comma:
|
||||
return ["fr", "sv", "cs"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89
|
||||
case NumberFormat.quote_decimal:
|
||||
return ["de-CH"]; // Use German (Switzerland) formatting 1'234'567.89
|
||||
case NumberFormat.system:
|
||||
return undefined;
|
||||
default:
|
||||
|
@@ -22,7 +22,6 @@ import {
|
||||
eventOptions,
|
||||
property,
|
||||
query,
|
||||
queryAll,
|
||||
state,
|
||||
} from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@@ -41,7 +40,7 @@ import { updateCanInstall } from "../data/update";
|
||||
import { showEditSidebarDialog } from "../dialogs/sidebar/show-dialog-edit-sidebar";
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
|
||||
import { haStyleAnimations, haStyleScrollbar } from "../resources/styles";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant, PanelInfo, Route } from "../types";
|
||||
import "./ha-fade-in";
|
||||
import "./ha-icon";
|
||||
@@ -211,8 +210,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
|
||||
@query(".tooltip") private _tooltip!: HTMLDivElement;
|
||||
|
||||
@queryAll("ha-md-list-item") private _listItems!: NodeListOf<HaMdListItem>;
|
||||
|
||||
public hassSubscribe() {
|
||||
return [
|
||||
subscribeFrontendUserData(
|
||||
@@ -325,15 +322,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
if (changedProps.has("alwaysExpand")) {
|
||||
toggleAttribute(this, "expanded", this.alwaysExpand);
|
||||
}
|
||||
|
||||
// Staggered animation for list items based on index
|
||||
this._listItems.forEach((item, index) => {
|
||||
(item as HTMLElement).style.setProperty(
|
||||
"--animation-index",
|
||||
String(index + 1)
|
||||
);
|
||||
});
|
||||
|
||||
if (!changedProps.has("hass")) {
|
||||
return;
|
||||
}
|
||||
@@ -705,7 +693,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleScrollbar,
|
||||
haStyleAnimations,
|
||||
css`
|
||||
:host {
|
||||
overflow: visible;
|
||||
@@ -752,14 +739,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
.menu ha-icon-button {
|
||||
color: var(--sidebar-icon-color);
|
||||
animation: fadeInSlideDown var(--ha-animation-duration) ease-out both;
|
||||
animation-delay: var(--ha-animation-delay-base) / 2;
|
||||
}
|
||||
ha-md-list-item {
|
||||
animation: fadeInSlideDown var(--ha-animation-duration) ease-out both;
|
||||
animation-delay: calc(
|
||||
var(--ha-animation-delay-base) * var(--animation-index, 1) / 2
|
||||
);
|
||||
}
|
||||
.title {
|
||||
margin-left: 3px;
|
||||
@@ -930,6 +909,11 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
padding: 4px;
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
}
|
||||
|
||||
.menu ha-icon-button {
|
||||
-webkit-transform: scaleX(var(--scale-direction));
|
||||
transform: scaleX(var(--scale-direction));
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ export enum NumberFormat {
|
||||
system = "system",
|
||||
comma_decimal = "comma_decimal",
|
||||
decimal_comma = "decimal_comma",
|
||||
quote_decimal = "quote_decimal",
|
||||
space_comma = "space_comma",
|
||||
none = "none",
|
||||
}
|
||||
|
@@ -2,15 +2,21 @@ import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { mdiChartDonut, mdiChartBar } from "@mdi/js";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { BarSeriesOption } from "echarts/charts";
|
||||
import type { BarSeriesOption, PieSeriesOption } from "echarts/charts";
|
||||
import { PieChart } from "echarts/charts";
|
||||
import type { ECElementEvent } from "echarts/types/dist/shared";
|
||||
import { getGraphColorByIndex } from "../../../../common/color/colors";
|
||||
import { formatNumber } from "../../../../common/number/format_number";
|
||||
import "../../../../components/chart/ha-chart-base";
|
||||
import type { EnergyData } from "../../../../data/energy";
|
||||
import { getEnergyDataCollection } from "../../../../data/energy";
|
||||
import {
|
||||
computeConsumptionData,
|
||||
getEnergyDataCollection,
|
||||
getSummedData,
|
||||
} from "../../../../data/energy";
|
||||
import {
|
||||
calculateStatisticSumGrowth,
|
||||
getStatisticLabel,
|
||||
@@ -25,6 +31,8 @@ import type { ECOption } from "../../../../resources/echarts";
|
||||
import "../../../../components/ha-card";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { measureTextWidth } from "../../../../util/text";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
|
||||
@customElement("hui-energy-devices-graph-card")
|
||||
export class HuiEnergyDevicesGraphCard
|
||||
@@ -35,10 +43,20 @@ export class HuiEnergyDevicesGraphCard
|
||||
|
||||
@state() private _config?: EnergyDevicesGraphCardConfig;
|
||||
|
||||
@state() private _chartData: BarSeriesOption[] = [];
|
||||
@state() private _chartData: (BarSeriesOption | PieSeriesOption)[] = [];
|
||||
|
||||
@state() private _data?: EnergyData;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
key: "energy-devices-graph-chart-type",
|
||||
state: true,
|
||||
subscribe: false,
|
||||
})
|
||||
private _chartType: "bar" | "pie" = "bar";
|
||||
|
||||
private _compoundStats: string[] = [];
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
@@ -75,9 +93,16 @@ export class HuiEnergyDevicesGraphCard
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
${this._config.title
|
||||
? html`<h1 class="card-header">${this._config.title}</h1>`
|
||||
: ""}
|
||||
<div class="card-header">
|
||||
<span>${this._config.title ? this._config.title : nothing}</span>
|
||||
<ha-icon-button
|
||||
path=${this._chartType === "pie" ? mdiChartBar : mdiChartDonut}
|
||||
label=${this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_devices_graph.change_chart_type"
|
||||
)}
|
||||
@click=${this._handleChartTypeChange}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
<div
|
||||
class="content ${classMap({
|
||||
"has-header": !!this._config.title,
|
||||
@@ -86,9 +111,10 @@ export class HuiEnergyDevicesGraphCard
|
||||
<ha-chart-base
|
||||
.hass=${this.hass}
|
||||
.data=${this._chartData}
|
||||
.options=${this._createOptions(this._chartData)}
|
||||
.height=${`${(this._chartData[0]?.data?.length || 0) * 28 + 50}px`}
|
||||
.options=${this._createOptions(this._chartData, this._chartType)}
|
||||
.height=${`${Math.max(300, (this._chartData[0]?.data?.length || 0) * 28 + 50)}px`}
|
||||
@chart-click=${this._handleChartClick}
|
||||
.extraComponents=${[PieChart]}
|
||||
></ha-chart-base>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -97,71 +123,86 @@ export class HuiEnergyDevicesGraphCard
|
||||
|
||||
private _renderTooltip(params: any) {
|
||||
const title = `<h4 style="text-align: center; margin: 0;">${this._getDeviceName(
|
||||
params.value[1]
|
||||
params.name
|
||||
)}</h4>`;
|
||||
const value = `${formatNumber(
|
||||
params.value[0] as number,
|
||||
this.hass.locale,
|
||||
params.value[0] < 0.1 ? { maximumFractionDigits: 3 } : undefined
|
||||
params.value < 0.1 ? { maximumFractionDigits: 3 } : undefined
|
||||
)} kWh`;
|
||||
return `${title}${params.marker} ${params.seriesName}: ${value}`;
|
||||
}
|
||||
|
||||
private _createOptions = memoizeOne((data: BarSeriesOption[]): ECOption => {
|
||||
const isMobile = window.matchMedia(
|
||||
"all and (max-width: 450px), all and (max-height: 500px)"
|
||||
).matches;
|
||||
return {
|
||||
xAxis: {
|
||||
type: "value",
|
||||
name: "kWh",
|
||||
},
|
||||
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",
|
||||
fontSize: 12,
|
||||
margin: 5,
|
||||
width: Math.min(
|
||||
isMobile ? 100 : 200,
|
||||
Math.max(
|
||||
...(data[0]?.data?.map(
|
||||
(d: any) =>
|
||||
measureTextWidth(this._getDeviceName(d.value[1]), 12) + 5
|
||||
) || [])
|
||||
)
|
||||
),
|
||||
private _createOptions = memoizeOne(
|
||||
(
|
||||
data: (BarSeriesOption | PieSeriesOption)[],
|
||||
chartType: "bar" | "pie"
|
||||
): ECOption => {
|
||||
const options: ECOption = {
|
||||
grid: {
|
||||
top: 5,
|
||||
left: 5,
|
||||
right: 40,
|
||||
bottom: 0,
|
||||
containLabel: true,
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
top: 5,
|
||||
left: 5,
|
||||
right: 40,
|
||||
bottom: 0,
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
formatter: this._renderTooltip.bind(this),
|
||||
},
|
||||
};
|
||||
});
|
||||
tooltip: {
|
||||
show: true,
|
||||
formatter: this._renderTooltip.bind(this),
|
||||
},
|
||||
xAxis: { show: false },
|
||||
yAxis: { show: false },
|
||||
};
|
||||
if (chartType === "bar") {
|
||||
const isMobile = window.matchMedia(
|
||||
"all and (max-width: 450px), all and (max-height: 500px)"
|
||||
).matches;
|
||||
options.xAxis = {
|
||||
show: true,
|
||||
type: "value",
|
||||
name: "kWh",
|
||||
};
|
||||
options.yAxis = {
|
||||
show: true,
|
||||
type: "category",
|
||||
inverse: true,
|
||||
triggerEvent: true,
|
||||
// take order from data
|
||||
data: data[0]?.data?.map((d: any) => d.name),
|
||||
axisLabel: {
|
||||
formatter: this._getDeviceName.bind(this),
|
||||
overflow: "truncate",
|
||||
fontSize: 12,
|
||||
margin: 5,
|
||||
width: Math.min(
|
||||
isMobile ? 100 : 200,
|
||||
Math.max(
|
||||
...(data[0]?.data?.map(
|
||||
(d: any) =>
|
||||
measureTextWidth(this._getDeviceName(d.name), 12) + 5
|
||||
) || [])
|
||||
)
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
return options;
|
||||
}
|
||||
);
|
||||
|
||||
private _getDeviceName(statisticId: string): string {
|
||||
const suffix = this._compoundStats.includes(statisticId)
|
||||
? ` (${this.hass.localize("ui.panel.lovelace.cards.energy.energy_devices_graph.untracked")})`
|
||||
: "";
|
||||
return (
|
||||
this._data?.prefs.device_consumption.find(
|
||||
(this._data?.prefs.device_consumption.find(
|
||||
(d) => d.stat_consumption === statisticId
|
||||
)?.name ||
|
||||
getStatisticLabel(
|
||||
this.hass,
|
||||
statisticId,
|
||||
this._data?.statsMetadata[statisticId]
|
||||
)
|
||||
getStatisticLabel(
|
||||
this.hass,
|
||||
statisticId,
|
||||
this._data?.statsMetadata[statisticId]
|
||||
)) + suffix
|
||||
);
|
||||
}
|
||||
|
||||
@@ -169,51 +210,105 @@ export class HuiEnergyDevicesGraphCard
|
||||
const data = energyData.stats;
|
||||
const compareData = energyData.statsCompare;
|
||||
|
||||
const chartData: NonNullable<BarSeriesOption["data"]> = [];
|
||||
const chartDataCompare: NonNullable<BarSeriesOption["data"]> = [];
|
||||
const chartData: NonNullable<(BarSeriesOption | PieSeriesOption)["data"]> =
|
||||
[];
|
||||
const chartDataCompare: NonNullable<
|
||||
(BarSeriesOption | PieSeriesOption)["data"]
|
||||
> = [];
|
||||
|
||||
const datasets: BarSeriesOption[] = [
|
||||
const datasets: (BarSeriesOption | PieSeriesOption)[] = [
|
||||
{
|
||||
type: "bar",
|
||||
type: this._chartType,
|
||||
radius: [compareData ? "50%" : "40%", "70%"],
|
||||
universalTransition: true,
|
||||
name: this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_devices_graph.energy_usage"
|
||||
),
|
||||
itemStyle: {
|
||||
borderRadius: [0, 4, 4, 0],
|
||||
borderRadius: this._chartType === "bar" ? [0, 4, 4, 0] : 4,
|
||||
},
|
||||
data: chartData,
|
||||
barWidth: compareData ? 10 : 20,
|
||||
cursor: "default",
|
||||
},
|
||||
minShowLabelAngle: 15,
|
||||
label:
|
||||
this._chartType === "pie"
|
||||
? {
|
||||
formatter: ({ name }) => this._getDeviceName(name),
|
||||
}
|
||||
: undefined,
|
||||
} as BarSeriesOption | PieSeriesOption,
|
||||
];
|
||||
|
||||
if (compareData) {
|
||||
datasets.push({
|
||||
type: "bar",
|
||||
type: this._chartType,
|
||||
radius: ["30%", "50%"],
|
||||
universalTransition: true,
|
||||
name: this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_devices_graph.previous_energy_usage"
|
||||
),
|
||||
itemStyle: {
|
||||
borderRadius: [0, 4, 4, 0],
|
||||
borderRadius: this._chartType === "bar" ? [0, 4, 4, 0] : 4,
|
||||
},
|
||||
data: chartDataCompare,
|
||||
barWidth: 10,
|
||||
cursor: "default",
|
||||
});
|
||||
label: this._chartType === "pie" ? { show: false } : undefined,
|
||||
emphasis:
|
||||
this._chartType === "pie"
|
||||
? {
|
||||
focus: "series",
|
||||
blurScope: "global",
|
||||
}
|
||||
: undefined,
|
||||
} as BarSeriesOption | PieSeriesOption);
|
||||
}
|
||||
|
||||
const computedStyle = getComputedStyle(this);
|
||||
|
||||
energyData.prefs.device_consumption.forEach((device, id) => {
|
||||
const value =
|
||||
this._compoundStats = energyData.prefs.device_consumption
|
||||
.map((d) => d.included_in_stat)
|
||||
.filter(Boolean) as string[];
|
||||
|
||||
const devices = energyData.prefs.device_consumption;
|
||||
const devicesTotals: Record<string, number> = {};
|
||||
devices.forEach((device) => {
|
||||
devicesTotals[device.stat_consumption] =
|
||||
device.stat_consumption in data
|
||||
? calculateStatisticSumGrowth(data[device.stat_consumption]) || 0
|
||||
: 0;
|
||||
const color = getGraphColorByIndex(id, computedStyle);
|
||||
});
|
||||
const devicesTotalsCompare: Record<string, number> = {};
|
||||
if (compareData) {
|
||||
devices.forEach((device) => {
|
||||
devicesTotalsCompare[device.stat_consumption] =
|
||||
device.stat_consumption in compareData
|
||||
? calculateStatisticSumGrowth(
|
||||
compareData[device.stat_consumption]
|
||||
) || 0
|
||||
: 0;
|
||||
});
|
||||
}
|
||||
devices.forEach((device, idx) => {
|
||||
let value = devicesTotals[device.stat_consumption];
|
||||
if (!this._config?.hide_compound_stats) {
|
||||
const childSum = devices.reduce((acc, d) => {
|
||||
if (d.included_in_stat === device.stat_consumption) {
|
||||
return acc + devicesTotals[d.stat_consumption];
|
||||
}
|
||||
return acc;
|
||||
}, 0);
|
||||
value -= Math.min(value, childSum);
|
||||
} else if (this._compoundStats.includes(device.stat_consumption)) {
|
||||
return;
|
||||
}
|
||||
const color = getGraphColorByIndex(idx, computedStyle);
|
||||
|
||||
chartData.push({
|
||||
id,
|
||||
value: [value, device.stat_consumption],
|
||||
id: device.stat_consumption,
|
||||
value: [value, device.stat_consumption] as any,
|
||||
name: device.stat_consumption,
|
||||
itemStyle: {
|
||||
color: color + "7F",
|
||||
borderColor: color,
|
||||
@@ -221,16 +316,24 @@ export class HuiEnergyDevicesGraphCard
|
||||
});
|
||||
|
||||
if (compareData) {
|
||||
const compareValue =
|
||||
let compareValue =
|
||||
device.stat_consumption in compareData
|
||||
? calculateStatisticSumGrowth(
|
||||
compareData[device.stat_consumption]
|
||||
) || 0
|
||||
: 0;
|
||||
const compareChildSum = devices.reduce((acc, d) => {
|
||||
if (d.included_in_stat === device.stat_consumption) {
|
||||
return acc + devicesTotalsCompare[d.stat_consumption];
|
||||
}
|
||||
return acc;
|
||||
}, 0);
|
||||
compareValue -= Math.min(compareValue, compareChildSum);
|
||||
|
||||
chartDataCompare.push({
|
||||
id,
|
||||
value: [compareValue, device.stat_consumption],
|
||||
id: device.stat_consumption,
|
||||
value: [compareValue, device.stat_consumption] as any,
|
||||
name: device.stat_consumption,
|
||||
itemStyle: {
|
||||
color: color + "32",
|
||||
borderColor: color + "7F",
|
||||
@@ -240,11 +343,62 @@ export class HuiEnergyDevicesGraphCard
|
||||
});
|
||||
|
||||
chartData.sort((a: any, b: any) => b.value[0] - a.value[0]);
|
||||
if (compareData) {
|
||||
datasets[1].data = chartData.map((d) =>
|
||||
chartDataCompare.find((d2) => (d2 as any).id === d.id)
|
||||
) as typeof chartDataCompare;
|
||||
}
|
||||
|
||||
chartData.length = Math.min(
|
||||
this._config?.max_devices || Infinity,
|
||||
chartData.length
|
||||
);
|
||||
datasets.forEach((dataset) => {
|
||||
dataset.data!.length = Math.min(
|
||||
this._config?.max_devices || Infinity,
|
||||
dataset.data!.length
|
||||
);
|
||||
});
|
||||
|
||||
if (this._chartType === "pie") {
|
||||
const { summedData } = getSummedData(energyData);
|
||||
const { consumption } = computeConsumptionData(summedData);
|
||||
const totalUsed = consumption.total.used_total;
|
||||
const showUntracked =
|
||||
"from_grid" in summedData ||
|
||||
"solar" in summedData ||
|
||||
"from_battery" in summedData;
|
||||
const untracked = showUntracked
|
||||
? totalUsed -
|
||||
chartData.reduce((acc: number, d: any) => acc + d.value[0], 0)
|
||||
: 0;
|
||||
datasets.push({
|
||||
type: "pie",
|
||||
radius: ["0%", compareData ? "30%" : "40%"],
|
||||
name: this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_devices_graph.total_energy_usage"
|
||||
),
|
||||
data: [totalUsed],
|
||||
label: {
|
||||
show: true,
|
||||
position: "center",
|
||||
color: computedStyle.getPropertyValue("--secondary-text-color"),
|
||||
fontSize: computedStyle.getPropertyValue("--ha-font-size-l"),
|
||||
lineHeight: 24,
|
||||
fontWeight: "bold",
|
||||
formatter: `{a}\n${formatNumber(totalUsed, this.hass.locale)} kWh`,
|
||||
},
|
||||
cursor: "default",
|
||||
itemStyle: {
|
||||
color: "rgba(0, 0, 0, 0)",
|
||||
},
|
||||
tooltip: {
|
||||
formatter: () =>
|
||||
untracked > 0
|
||||
? this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_devices_graph.includes_untracked",
|
||||
{ num: formatNumber(untracked, this.hass.locale) }
|
||||
)
|
||||
: "",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this._chartData = datasets;
|
||||
await this.updateComplete;
|
||||
@@ -259,11 +413,26 @@ export class HuiEnergyDevicesGraphCard
|
||||
fireEvent(this, "hass-more-info", {
|
||||
entityId: e.detail.value as string,
|
||||
});
|
||||
} else if (
|
||||
e.detail.seriesType === "pie" &&
|
||||
e.detail.event?.target?.type === "tspan" // label
|
||||
) {
|
||||
fireEvent(this, "hass-more-info", {
|
||||
entityId: (e.detail.data as any).id as string,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _handleChartTypeChange(): void {
|
||||
this._chartType = this._chartType === "pie" ? "bar" : "pie";
|
||||
this._getStatistics(this._data!);
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.content {
|
||||
@@ -275,6 +444,11 @@ export class HuiEnergyDevicesGraphCard
|
||||
ha-chart-base {
|
||||
--chart-max-height: none;
|
||||
}
|
||||
ha-icon-button {
|
||||
transform: rotate(90deg);
|
||||
color: var(--secondary-text-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -176,6 +176,7 @@ export interface EnergyDevicesGraphCardConfig extends EnergyCardBaseConfig {
|
||||
type: "energy-devices-graph";
|
||||
title?: string;
|
||||
max_devices?: number;
|
||||
hide_compound_stats?: boolean;
|
||||
}
|
||||
|
||||
export interface EnergyDevicesDetailGraphCardConfig
|
||||
@@ -282,7 +283,7 @@ export interface GlanceConfigEntity extends ConfigEntity {
|
||||
image?: string;
|
||||
show_state?: boolean;
|
||||
state_color?: boolean;
|
||||
format: TimestampRenderingFormat;
|
||||
format?: TimestampRenderingFormat;
|
||||
}
|
||||
|
||||
export interface GlanceCardConfig extends LovelaceCardConfig {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { mdiDrag } from "@mdi/js";
|
||||
import { mdiClose, mdiDrag, mdiPencil } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
@@ -12,6 +12,7 @@ import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-sortable";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { EntityConfig } from "../entity-rows/types";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
|
||||
@customElement("hui-entity-editor")
|
||||
export class HuiEntityEditor extends LitElement {
|
||||
@@ -24,6 +25,8 @@ export class HuiEntityEditor extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ attribute: "can-edit", type: Boolean }) public canEdit?;
|
||||
|
||||
private _entityKeys = new WeakMap<EntityConfig, string>();
|
||||
|
||||
private _getKey(action: EntityConfig) {
|
||||
@@ -34,6 +37,70 @@ export class HuiEntityEditor extends LitElement {
|
||||
return this._entityKeys.get(action)!;
|
||||
}
|
||||
|
||||
private _renderItem(item: EntityConfig, index: number) {
|
||||
const stateObj = this.hass!.states[item.entity];
|
||||
|
||||
const entityName =
|
||||
stateObj && this.hass!.formatEntityName(stateObj, "entity");
|
||||
const deviceName =
|
||||
stateObj && this.hass!.formatEntityName(stateObj, "device");
|
||||
const areaName = stateObj && this.hass!.formatEntityName(stateObj, "area");
|
||||
|
||||
const isRTL = computeRTL(this.hass!);
|
||||
|
||||
const primary = item.name || entityName || deviceName || item.entity;
|
||||
const secondary = [areaName, entityName ? deviceName : undefined]
|
||||
.filter(Boolean)
|
||||
.join(isRTL ? " ◂ " : " ▸ ");
|
||||
|
||||
return html`
|
||||
<ha-md-list-item class="item">
|
||||
<ha-svg-icon class="handle" .path=${mdiDrag} slot="start"></ha-svg-icon>
|
||||
|
||||
<div slot="headline" class="label">${primary}</div>
|
||||
${secondary
|
||||
? html`<div slot="supporting-text" class="description">
|
||||
${secondary}
|
||||
</div>`
|
||||
: nothing}
|
||||
<ha-icon-button
|
||||
slot="end"
|
||||
.item=${item}
|
||||
.index=${index}
|
||||
.label=${this.hass!.localize("ui.common.edit")}
|
||||
.path=${mdiPencil}
|
||||
@click=${this._editItem}
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
slot="end"
|
||||
.index=${index}
|
||||
.label=${this.hass!.localize("ui.common.delete")}
|
||||
.path=${mdiClose}
|
||||
@click=${this._deleteItem}
|
||||
></ha-icon-button>
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
private _editItem(ev) {
|
||||
const index = (ev.currentTarget as any).index;
|
||||
fireEvent(this, "edit-detail-element", {
|
||||
subElementConfig: {
|
||||
index,
|
||||
type: "row",
|
||||
elementConfig: this.entities![index],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _deleteItem(ev) {
|
||||
const index = ev.target.index;
|
||||
const newConfigEntities = this.entities!.slice(0, index).concat(
|
||||
this.entities!.slice(index + 1)
|
||||
);
|
||||
fireEvent(this, "entities-changed", { entities: newConfigEntities });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.entities) {
|
||||
return nothing;
|
||||
@@ -47,29 +114,48 @@ export class HuiEntityEditor extends LitElement {
|
||||
this.hass!.localize("ui.panel.lovelace.editor.card.config.required") +
|
||||
")"}
|
||||
</h3>
|
||||
<ha-sortable handle-selector=".handle" @item-moved=${this._entityMoved}>
|
||||
<div class="entities">
|
||||
${repeat(
|
||||
this.entities,
|
||||
(entityConf) => this._getKey(entityConf),
|
||||
(entityConf, index) => html`
|
||||
<div class="entity" data-entity-id=${entityConf.entity}>
|
||||
<div class="handle">
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
</div>
|
||||
<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
.value=${entityConf.entity}
|
||||
.index=${index}
|
||||
.entityFilter=${this.entityFilter}
|
||||
@value-changed=${this._valueChanged}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</ha-sortable>
|
||||
${this.canEdit
|
||||
? html`
|
||||
<div class="items-container">
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
draggable-selector=".item"
|
||||
@item-moved=${this._entityMoved}
|
||||
>
|
||||
<ha-md-list>
|
||||
${this.entities.map((item, index) =>
|
||||
this._renderItem(item, index)
|
||||
)}
|
||||
</ha-md-list>
|
||||
</ha-sortable>
|
||||
</div>
|
||||
`
|
||||
: html` <ha-sortable
|
||||
handle-selector=".handle"
|
||||
@item-moved=${this._entityMoved}
|
||||
>
|
||||
<div class="entities">
|
||||
${repeat(
|
||||
this.entities,
|
||||
(entityConf) => this._getKey(entityConf),
|
||||
(entityConf, index) => html`
|
||||
<div class="entity" data-entity-id=${entityConf.entity}>
|
||||
<div class="handle">
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
</div>
|
||||
<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
.value=${entityConf.entity}
|
||||
.index=${index}
|
||||
.entityFilter=${this.entityFilter}
|
||||
@value-changed=${this._valueChanged}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</ha-sortable>`}
|
||||
<ha-entity-picker
|
||||
class="add-entity"
|
||||
.hass=${this.hass}
|
||||
@@ -148,6 +234,35 @@ export class HuiEntityEditor extends LitElement {
|
||||
.entity ha-entity-picker {
|
||||
flex-grow: 1;
|
||||
}
|
||||
ha-md-list {
|
||||
gap: 8px;
|
||||
}
|
||||
ha-md-list-item {
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 8px;
|
||||
--ha-md-list-item-gap: 0;
|
||||
--md-list-item-top-space: 0;
|
||||
--md-list-item-bottom-space: 0;
|
||||
--md-list-item-leading-space: 12px;
|
||||
--md-list-item-trailing-space: 4px;
|
||||
--md-list-item-two-line-container-height: 48px;
|
||||
--md-list-item-one-line-container-height: 48px;
|
||||
}
|
||||
.handle {
|
||||
cursor: move;
|
||||
padding: 8px;
|
||||
margin-inline-start: -8px;
|
||||
}
|
||||
label {
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
ha-md-list-item .label,
|
||||
ha-md-list-item .description {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -31,6 +31,8 @@ export class HuiGenericEntityRowEditor
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public schema?;
|
||||
|
||||
@state() private _config?: EntitiesCardEntityConfig;
|
||||
|
||||
public setConfig(config: EntitiesCardEntityConfig): void {
|
||||
@@ -87,7 +89,8 @@ export class HuiGenericEntityRowEditor
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const schema = this._schema(this._config.entity, this.hass.localize);
|
||||
const schema =
|
||||
this.schema || this._schema(this._config.entity, this.hass.localize);
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
|
@@ -13,6 +13,9 @@ import {
|
||||
} from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import "../hui-sub-element-editor";
|
||||
import type { EditDetailElementEvent, SubElementEditorConfig } from "../types";
|
||||
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { ConfigEntity, GlanceCardConfig } from "../../cards/types";
|
||||
@@ -21,6 +24,7 @@ import type { LovelaceCardEditor } from "../../types";
|
||||
import { processEditorEntities } from "../process-editor-entities";
|
||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||
import { entitiesConfigStruct } from "../structs/entities-struct";
|
||||
import type { EntityConfig } from "../../entity-rows/types";
|
||||
|
||||
const cardConfigStruct = assign(
|
||||
baseLovelaceCardConfig,
|
||||
@@ -36,6 +40,49 @@ const cardConfigStruct = assign(
|
||||
})
|
||||
);
|
||||
|
||||
const SUB_SCHEMA = [
|
||||
{ name: "entity", selector: { entity: {} }, required: true },
|
||||
{
|
||||
type: "grid",
|
||||
name: "",
|
||||
schema: [
|
||||
{ name: "name", selector: { text: {} } },
|
||||
{
|
||||
name: "icon",
|
||||
selector: {
|
||||
icon: {},
|
||||
},
|
||||
context: {
|
||||
icon_entity: "entity",
|
||||
},
|
||||
},
|
||||
{ name: "show_last_changed", selector: { boolean: {} } },
|
||||
{ name: "show_state", selector: { boolean: {} }, default: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "more-info",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "optional_actions",
|
||||
flatten: true,
|
||||
schema: (["hold_action", "double_tap_action"] as const).map((action) => ({
|
||||
name: action,
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "none" as const,
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
] as const;
|
||||
|
||||
const SCHEMA = [
|
||||
{ name: "title", selector: { text: {} } },
|
||||
{
|
||||
@@ -68,6 +115,8 @@ export class HuiGlanceCardEditor
|
||||
|
||||
@state() private _config?: GlanceCardConfig;
|
||||
|
||||
@state() private _subElementEditorConfig?: SubElementEditorConfig;
|
||||
|
||||
@state() private _configEntities?: ConfigEntity[];
|
||||
|
||||
public setConfig(config: GlanceCardConfig): void {
|
||||
@@ -81,6 +130,19 @@ export class HuiGlanceCardEditor
|
||||
return nothing;
|
||||
}
|
||||
|
||||
if (this._subElementEditorConfig) {
|
||||
return html`
|
||||
<hui-sub-element-editor
|
||||
.hass=${this.hass}
|
||||
.config=${this._subElementEditorConfig}
|
||||
.schema=${SUB_SCHEMA}
|
||||
@go-back=${this._goBack}
|
||||
@config-changed=${this._handleSubEntityChanged}
|
||||
>
|
||||
</hui-sub-element-editor>
|
||||
`;
|
||||
}
|
||||
|
||||
const data = {
|
||||
show_name: true,
|
||||
show_icon: true,
|
||||
@@ -98,12 +160,42 @@ export class HuiGlanceCardEditor
|
||||
></ha-form>
|
||||
<hui-entity-editor
|
||||
.hass=${this.hass}
|
||||
can-edit
|
||||
.entities=${this._configEntities}
|
||||
@entities-changed=${this._entitiesChanged}
|
||||
@edit-detail-element=${this._editDetailElement}
|
||||
></hui-entity-editor>
|
||||
`;
|
||||
}
|
||||
|
||||
private _goBack(): void {
|
||||
this._subElementEditorConfig = undefined;
|
||||
}
|
||||
|
||||
private _editDetailElement(ev: HASSDomEvent<EditDetailElementEvent>): void {
|
||||
this._subElementEditorConfig = ev.detail.subElementConfig;
|
||||
}
|
||||
|
||||
private _handleSubEntityChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
|
||||
const index = this._subElementEditorConfig!.index!;
|
||||
|
||||
const newEntities = this._configEntities!.concat();
|
||||
const newConfig = ev.detail.config as EntityConfig;
|
||||
this._subElementEditorConfig = {
|
||||
...this._subElementEditorConfig!,
|
||||
elementConfig: newConfig,
|
||||
};
|
||||
newEntities[index] = newConfig;
|
||||
let config = this._config!;
|
||||
config = { ...config, entities: newEntities };
|
||||
this._config = config;
|
||||
this._configEntities = processEditorEntities(config.entities);
|
||||
|
||||
fireEvent(this, "config-changed", { config });
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
const config = ev.detail.value;
|
||||
fireEvent(this, "config-changed", { config });
|
||||
|
@@ -18,6 +18,9 @@ import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { HistoryGraphCardConfig } from "../../cards/types";
|
||||
import "../../components/hui-entity-editor";
|
||||
import "../hui-sub-element-editor";
|
||||
import type { EditDetailElementEvent, SubElementEditorConfig } from "../types";
|
||||
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||
import type { EntityConfig } from "../../entity-rows/types";
|
||||
import type { LovelaceCardEditor } from "../../types";
|
||||
import { processEditorEntities } from "../process-editor-entities";
|
||||
@@ -40,6 +43,11 @@ const cardConfigStruct = assign(
|
||||
})
|
||||
);
|
||||
|
||||
const SUB_SCHEMA = [
|
||||
{ name: "entity", selector: { entity: {} }, required: true },
|
||||
{ name: "name", selector: { text: {} } },
|
||||
] as const;
|
||||
|
||||
@customElement("hui-history-graph-card-editor")
|
||||
export class HuiHistoryGraphCardEditor
|
||||
extends LitElement
|
||||
@@ -49,6 +57,8 @@ export class HuiHistoryGraphCardEditor
|
||||
|
||||
@state() private _config?: HistoryGraphCardConfig;
|
||||
|
||||
@state() private _subElementEditorConfig?: SubElementEditorConfig;
|
||||
|
||||
@state() private _configEntities?: EntityConfig[];
|
||||
|
||||
public setConfig(config: HistoryGraphCardConfig): void {
|
||||
@@ -110,6 +120,19 @@ export class HuiHistoryGraphCardEditor
|
||||
return nothing;
|
||||
}
|
||||
|
||||
if (this._subElementEditorConfig) {
|
||||
return html`
|
||||
<hui-sub-element-editor
|
||||
.hass=${this.hass}
|
||||
.config=${this._subElementEditorConfig}
|
||||
.schema=${SUB_SCHEMA}
|
||||
@go-back=${this._goBack}
|
||||
@config-changed=${this._handleSubEntityChanged}
|
||||
>
|
||||
</hui-sub-element-editor>
|
||||
`;
|
||||
}
|
||||
|
||||
const schema = this._schema(
|
||||
this._config!.min_y_axis !== undefined ||
|
||||
this._config!.max_y_axis !== undefined
|
||||
@@ -126,11 +149,41 @@ export class HuiHistoryGraphCardEditor
|
||||
<hui-entity-editor
|
||||
.hass=${this.hass}
|
||||
.entities=${this._configEntities}
|
||||
can-edit
|
||||
@entities-changed=${this._entitiesChanged}
|
||||
@edit-detail-element=${this._editDetailElement}
|
||||
></hui-entity-editor>
|
||||
`;
|
||||
}
|
||||
|
||||
private _goBack(): void {
|
||||
this._subElementEditorConfig = undefined;
|
||||
}
|
||||
|
||||
private _editDetailElement(ev: HASSDomEvent<EditDetailElementEvent>): void {
|
||||
this._subElementEditorConfig = ev.detail.subElementConfig;
|
||||
}
|
||||
|
||||
private _handleSubEntityChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
|
||||
const index = this._subElementEditorConfig!.index!;
|
||||
|
||||
const newEntities = this._configEntities!.concat();
|
||||
const newConfig = ev.detail.config as EntityConfig;
|
||||
this._subElementEditorConfig = {
|
||||
...this._subElementEditorConfig!,
|
||||
elementConfig: newConfig,
|
||||
};
|
||||
newEntities[index] = newConfig;
|
||||
let config = this._config!;
|
||||
config = { ...config, entities: newEntities };
|
||||
this._config = config;
|
||||
this._configEntities = processEditorEntities(config.entities);
|
||||
|
||||
fireEvent(this, "config-changed", { config });
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||
}
|
||||
|
@@ -57,6 +57,8 @@ export abstract class HuiElementEditor<
|
||||
|
||||
@property({ attribute: false }) public context?: C;
|
||||
|
||||
@property({ attribute: false }) public schema?;
|
||||
|
||||
@state() private _config?: T;
|
||||
|
||||
@state() private _configElement?: LovelaceGenericElementEditor;
|
||||
@@ -312,6 +314,9 @@ export abstract class HuiElementEditor<
|
||||
if (this._configElement && changedProperties.has("context")) {
|
||||
this._configElement.context = this.context;
|
||||
}
|
||||
if (this._configElement && changedProperties.has("schema")) {
|
||||
this._configElement.schema = this.schema;
|
||||
}
|
||||
}
|
||||
|
||||
private _handleUIConfigChanged(ev: UIConfigChangedEvent<T>) {
|
||||
@@ -399,6 +404,7 @@ export abstract class HuiElementEditor<
|
||||
configElement.lovelace = this.lovelace;
|
||||
}
|
||||
configElement.context = this.context;
|
||||
configElement.schema = this.schema;
|
||||
configElement.addEventListener("config-changed", (ev) =>
|
||||
this._handleUIConfigChanged(ev as UIConfigChangedEvent<T>)
|
||||
);
|
||||
|
@@ -27,6 +27,8 @@ export class HuiSubElementEditor extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public config!: SubElementEditorConfig;
|
||||
|
||||
@property({ attribute: false }) public schema?;
|
||||
|
||||
@state() private _guiModeAvailable = true;
|
||||
|
||||
@state() private _guiMode = true;
|
||||
@@ -89,6 +91,7 @@ export class HuiSubElementEditor extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.value=${this.config.elementConfig}
|
||||
.context=${this.config.context}
|
||||
.schema=${this.schema}
|
||||
@config-changed=${this._handleConfigChanged}
|
||||
@GUImode-changed=${this._handleGUIModeChanged}
|
||||
></hui-row-element-editor>
|
||||
|
@@ -18,6 +18,8 @@ export const entitiesConfigStruct = union([
|
||||
hold_action: optional(actionConfigStruct),
|
||||
double_tap_action: optional(actionConfigStruct),
|
||||
confirmation: optional(actionConfigStructConfirmation),
|
||||
show_last_changed: optional(boolean()),
|
||||
show_state: optional(boolean()),
|
||||
}),
|
||||
string(),
|
||||
]);
|
||||
|
@@ -72,7 +72,7 @@ import {
|
||||
} from "../../dialogs/quick-bar/show-dialog-quick-bar";
|
||||
import { showShortcutsDialog } from "../../dialogs/shortcuts/show-shortcuts-dialog";
|
||||
import { showVoiceCommandDialog } from "../../dialogs/voice-command-dialog/show-ha-voice-command-dialog";
|
||||
import { haStyle, haStyleAnimations } from "../../resources/styles";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant, PanelInfo } from "../../types";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import { showToast } from "../../util/toast";
|
||||
@@ -1205,7 +1205,6 @@ class HUIRoot extends LitElement {
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
haStyleAnimations,
|
||||
css`
|
||||
:host {
|
||||
-ms-user-select: none;
|
||||
@@ -1260,8 +1259,6 @@ class HUIRoot extends LitElement {
|
||||
padding: 0px 12px;
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
box-sizing: border-box;
|
||||
animation: fadeIn var(--ha-animation-duration) ease-out both;
|
||||
animation-delay: var(--ha-animation-delay-base);
|
||||
}
|
||||
.narrow .toolbar {
|
||||
padding: 0 4px;
|
||||
@@ -1410,8 +1407,6 @@ class HUIRoot extends LitElement {
|
||||
hui-view-container > * {
|
||||
flex: 1 1 100%;
|
||||
max-width: 100%;
|
||||
animation: fadeInSlideDown var(--ha-animation-duration) ease-out both;
|
||||
animation-delay: var(--ha-animation-delay-base);
|
||||
}
|
||||
/**
|
||||
* In edit mode we have the tab bar on a new line *
|
||||
|
@@ -169,6 +169,7 @@ export interface LovelaceGenericElementEditor<C = any> extends HTMLElement {
|
||||
hass?: HomeAssistant;
|
||||
lovelace?: LovelaceConfig;
|
||||
context?: C;
|
||||
schema?: any;
|
||||
setConfig(config: any): void;
|
||||
focusYamlEditor?: () => void;
|
||||
}
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import type { ActionDetail } from "@material/mwc-list";
|
||||
import {
|
||||
mdiAndroid,
|
||||
mdiApple,
|
||||
@@ -15,7 +14,8 @@ import memoizeOne from "memoize-one";
|
||||
import { relativeTime } from "../../common/datetime/relative_time";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-button";
|
||||
import "../../components/ha-button-menu";
|
||||
import "../../components/ha-md-button-menu";
|
||||
import "../../components/ha-md-menu-item";
|
||||
import "../../components/ha-card";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-label";
|
||||
@@ -146,20 +146,19 @@ class HaRefreshTokens extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_END"
|
||||
menu-corner="END"
|
||||
@action=${this._handleAction}
|
||||
.token=${token}
|
||||
>
|
||||
<ha-md-button-menu positioning="popover">
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-list-item graphic="icon">
|
||||
<ha-md-menu-item
|
||||
graphic="icon"
|
||||
@click=${this._toggleTokenExpiration}
|
||||
.token=${token}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="start"
|
||||
.path=${token.expire_at
|
||||
? mdiClockRemoveOutline
|
||||
: mdiClockCheckOutline}
|
||||
@@ -171,20 +170,24 @@ class HaRefreshTokens extends LitElement {
|
||||
: this.hass.localize(
|
||||
"ui.panel.profile.refresh_tokens.enable_token_expiration"
|
||||
)}
|
||||
</ha-list-item>
|
||||
<ha-list-item
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
graphic="icon"
|
||||
class="warning"
|
||||
.disabled=${token.is_current}
|
||||
@click=${this._deleteToken}
|
||||
.token=${token}
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="graphic"
|
||||
slot="start"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize("ui.common.delete")}
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
<div slot="headline">
|
||||
${this.hass.localize("ui.common.delete")}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
</div>
|
||||
</ha-settings-row>
|
||||
`
|
||||
@@ -207,19 +210,8 @@ class HaRefreshTokens extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private async _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||
const token = (ev.currentTarget as any).token;
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._toggleTokenExpiration(token);
|
||||
break;
|
||||
case 1:
|
||||
this._deleteToken(token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async _toggleTokenExpiration(token: RefreshToken): Promise<void> {
|
||||
private async _toggleTokenExpiration(ev): Promise<void> {
|
||||
const token = (ev.currentTarget as any).token as RefreshToken;
|
||||
const enable = !token.expire_at;
|
||||
if (!enable) {
|
||||
if (
|
||||
@@ -260,7 +252,8 @@ class HaRefreshTokens extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _deleteToken(token: RefreshToken): Promise<void> {
|
||||
private async _deleteToken(ev): Promise<void> {
|
||||
const token = (ev.currentTarget as any).token as RefreshToken;
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
@@ -332,8 +325,8 @@ class HaRefreshTokens extends LitElement {
|
||||
ha-icon-button {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
ha-list-item[disabled],
|
||||
ha-list-item[disabled] ha-svg-icon {
|
||||
ha-md-list-item[disabled],
|
||||
ha-md-list-item[disabled] ha-svg-icon {
|
||||
color: var(--disabled-text-color) !important;
|
||||
}
|
||||
ha-settings-row .current-session {
|
||||
|
@@ -195,36 +195,3 @@ export const baseEntrypointStyles = css`
|
||||
width: 100vw;
|
||||
}
|
||||
`;
|
||||
|
||||
export const haStyleAnimations = css`
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInSlideUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInSlideDown {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@@ -42,10 +42,6 @@ export const coreStyles = css`
|
||||
--ha-space-18: 72px;
|
||||
--ha-space-19: 76px;
|
||||
--ha-space-20: 80px;
|
||||
|
||||
/* Animation timing */
|
||||
--ha-animation-duration: 400ms;
|
||||
--ha-animation-delay-base: 50ms;
|
||||
}
|
||||
`;
|
||||
|
||||
|
@@ -7025,7 +7025,11 @@
|
||||
},
|
||||
"energy_devices_graph": {
|
||||
"energy_usage": "Energy usage",
|
||||
"previous_energy_usage": "Previous energy usage"
|
||||
"previous_energy_usage": "Previous energy usage",
|
||||
"total_energy_usage": "Total energy usage",
|
||||
"change_chart_type": "Change chart type",
|
||||
"untracked": "untracked",
|
||||
"includes_untracked": "Includes {num} kWh of untracked energy"
|
||||
},
|
||||
"energy_devices_detail_graph": {
|
||||
"untracked_consumption": "Untracked consumption",
|
||||
@@ -7720,6 +7724,7 @@
|
||||
"show_icon": "Show icon",
|
||||
"show_name": "Show name",
|
||||
"show_state": "Show state",
|
||||
"show_last_changed": "Show last changed",
|
||||
"tap_action": "Tap behavior",
|
||||
"interactions": "Interactions",
|
||||
"title": "Title",
|
||||
@@ -8440,6 +8445,7 @@
|
||||
"system": "Use system locale",
|
||||
"comma_decimal": "1,234,567.89",
|
||||
"decimal_comma": "1.234.567,89",
|
||||
"quote_decimal": "1'234'567.89",
|
||||
"space_comma": "1 234 567,89",
|
||||
"none": "None"
|
||||
}
|
||||
|
20
yarn.lock
20
yarn.lock
@@ -1284,15 +1284,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@codemirror/view@npm:6.38.3, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0":
|
||||
version: 6.38.3
|
||||
resolution: "@codemirror/view@npm:6.38.3"
|
||||
"@codemirror/view@npm:6.38.4, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0":
|
||||
version: 6.38.4
|
||||
resolution: "@codemirror/view@npm:6.38.4"
|
||||
dependencies:
|
||||
"@codemirror/state": "npm:^6.5.0"
|
||||
crelt: "npm:^1.0.6"
|
||||
style-mod: "npm:^4.1.0"
|
||||
w3c-keyname: "npm:^2.2.4"
|
||||
checksum: 10/2df41450399cbac0eaf06dba822418dd6926e48344b9255902248075ef040c957dfe97fe842a755e284a2fd4a66dc17b9638385f46ad74e926baac2e797335a2
|
||||
checksum: 10/86b3894e9e7c2113aabb1db8684d0520378339c194fa56a688fc26cd7d40336bb9df1f5f19f68309d95f14b80ecf0b70c0ffe5e43f2ec11c4bab18f2d5ee4494
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9187,7 +9187,7 @@ __metadata:
|
||||
"@codemirror/legacy-modes": "npm:6.5.1"
|
||||
"@codemirror/search": "npm:6.5.11"
|
||||
"@codemirror/state": "npm:6.5.2"
|
||||
"@codemirror/view": "npm:6.38.3"
|
||||
"@codemirror/view": "npm:6.38.4"
|
||||
"@date-fns/tz": "npm:1.4.1"
|
||||
"@egjs/hammerjs": "npm:2.0.17"
|
||||
"@formatjs/intl-datetimeformat": "npm:6.18.0"
|
||||
@@ -9322,7 +9322,7 @@ __metadata:
|
||||
leaflet: "npm:1.9.4"
|
||||
leaflet-draw: "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||
leaflet.markercluster: "npm:1.5.3"
|
||||
lint-staged: "npm:16.2.1"
|
||||
lint-staged: "npm:16.2.3"
|
||||
lit: "npm:3.3.1"
|
||||
lit-analyzer: "npm:2.0.3"
|
||||
lit-html: "npm:3.3.1"
|
||||
@@ -10657,9 +10657,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lint-staged@npm:16.2.1":
|
||||
version: 16.2.1
|
||||
resolution: "lint-staged@npm:16.2.1"
|
||||
"lint-staged@npm:16.2.3":
|
||||
version: 16.2.3
|
||||
resolution: "lint-staged@npm:16.2.3"
|
||||
dependencies:
|
||||
commander: "npm:^14.0.1"
|
||||
listr2: "npm:^9.0.4"
|
||||
@@ -10670,7 +10670,7 @@ __metadata:
|
||||
yaml: "npm:^2.8.1"
|
||||
bin:
|
||||
lint-staged: bin/lint-staged.js
|
||||
checksum: 10/b604de3ca27a067e45c5f0e0780ca46f5617e9f6ac3895297dee087d62742bbcd9f9e910300c15c599e1f06900666469b73e036e3fe3153ccedef314ce791dd5
|
||||
checksum: 10/7c83cb478aa8004eecc8c91d633abe2865ffc037957ae9ee2669e49b76b76fe3512ba431277efc29cec7a38641e7d8a62f3378a41b624c88bde6fbef5524e2cb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Reference in New Issue
Block a user