mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-27 19:57:24 +00:00
Compare commits
21 Commits
fix-safe-a
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c9d274b5c | ||
|
|
d6e6bc0e80 | ||
|
|
530a70b168 | ||
|
|
23137500f8 | ||
|
|
63e7ed21a4 | ||
|
|
92611f46f4 | ||
|
|
9bc896241d | ||
|
|
2baafe620c | ||
|
|
ce52bbaf8c | ||
|
|
0b4b8d9082 | ||
|
|
bddbb773b7 | ||
|
|
d52e1e8835 | ||
|
|
0a9dccfd19 | ||
|
|
bfd78670cc | ||
|
|
11276af1a0 | ||
|
|
d7be46c00b | ||
|
|
94f32ce242 | ||
|
|
ef3e8186bc | ||
|
|
50fcf622aa | ||
|
|
77c2444be8 | ||
|
|
e5cb26cd3d |
@@ -217,7 +217,7 @@
|
||||
"terser-webpack-plugin": "5.3.14",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.47.0",
|
||||
"typescript-eslint": "8.48.0",
|
||||
"vite-tsconfig-paths": "5.1.4",
|
||||
"vitest": "4.0.13",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { AuthData } from "home-assistant-js-websocket";
|
||||
import { extractSearchParam } from "../url/search-params";
|
||||
import { hassUrl } from "../../data/auth";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -30,7 +31,11 @@ export function askWrite() {
|
||||
export function saveTokens(tokens: AuthData | null) {
|
||||
tokenCache.tokens = tokens;
|
||||
|
||||
if (!tokenCache.writeEnabled && extractSearchParam("storeToken") === "true") {
|
||||
if (
|
||||
!tokenCache.writeEnabled &&
|
||||
(extractSearchParam("storeToken") === "true" ||
|
||||
hassUrl !== `${location.protocol}//${location.host}`)
|
||||
) {
|
||||
tokenCache.writeEnabled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -279,6 +279,7 @@ export class HaSankeyChart extends LitElement {
|
||||
:host {
|
||||
display: block;
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
background: var(--ha-card-background, var(--card-background-color));
|
||||
}
|
||||
ha-chart-base {
|
||||
|
||||
@@ -202,6 +202,7 @@ export class HaControlSelect extends LitElement {
|
||||
color: var(--primary-text-color);
|
||||
user-select: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
border-radius: var(--control-select-border-radius);
|
||||
}
|
||||
:host([vertical]) {
|
||||
width: var(--control-select-thickness);
|
||||
@@ -211,7 +212,6 @@ export class HaControlSelect extends LitElement {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: var(--control-select-border-radius);
|
||||
transform: translateZ(0);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -186,6 +186,7 @@ export class HaIcon extends LitElement {
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
fill: currentcolor;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "@home-assistant/webawesome/dist/components/dialog/dialog";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import {
|
||||
customElement,
|
||||
@@ -7,8 +9,6 @@ import {
|
||||
state,
|
||||
} from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import "@home-assistant/webawesome/dist/components/dialog/dialog";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
@@ -172,7 +172,9 @@ export class HaWaDialog extends LitElement {
|
||||
|
||||
await this.updateComplete;
|
||||
|
||||
(this.querySelector("[autofocus]") as HTMLElement | null)?.focus();
|
||||
requestAnimationFrame(() => {
|
||||
(this.querySelector("[autofocus]") as HTMLElement | null)?.focus();
|
||||
});
|
||||
};
|
||||
|
||||
private _handleAfterShow = () => {
|
||||
|
||||
@@ -75,17 +75,11 @@ export const reorderAreaRegistryEntries = (
|
||||
});
|
||||
|
||||
export const getAreaEntityLookup = (
|
||||
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[],
|
||||
filterHidden = false
|
||||
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[]
|
||||
): AreaEntityLookup => {
|
||||
const areaEntityLookup: AreaEntityLookup = {};
|
||||
for (const entity of entities) {
|
||||
if (
|
||||
!entity.area_id ||
|
||||
(filterHidden &&
|
||||
((entity as EntityRegistryDisplayEntry).hidden ||
|
||||
(entity as EntityRegistryEntry).hidden_by))
|
||||
) {
|
||||
if (!entity.area_id) {
|
||||
continue;
|
||||
}
|
||||
if (!(entity.area_id in areaEntityLookup)) {
|
||||
|
||||
@@ -111,17 +111,11 @@ export const sortDeviceRegistryByName = (
|
||||
);
|
||||
|
||||
export const getDeviceEntityLookup = (
|
||||
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[],
|
||||
filterHidden = false
|
||||
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[]
|
||||
): DeviceEntityLookup => {
|
||||
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||
for (const entity of entities) {
|
||||
if (
|
||||
!entity.device_id ||
|
||||
(filterHidden &&
|
||||
((entity as EntityRegistryDisplayEntry).hidden ||
|
||||
(entity as EntityRegistryEntry).hidden_by))
|
||||
) {
|
||||
if (!entity.device_id) {
|
||||
continue;
|
||||
}
|
||||
if (!(entity.device_id in deviceEntityLookup)) {
|
||||
|
||||
@@ -84,6 +84,8 @@ export class HaConfigAreasDashboard extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
private _searchParms = new URLSearchParams(window.location.search);
|
||||
|
||||
@state() private _hierarchy?: AreasFloorHierarchy;
|
||||
|
||||
private _blockHierarchyUpdate = false;
|
||||
@@ -167,7 +169,9 @@ export class HaConfigAreasDashboard extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.isWide=${this.isWide}
|
||||
back-path="/config"
|
||||
.backPath=${this._searchParms.has("historyBack")
|
||||
? undefined
|
||||
: "/config"}
|
||||
.tabs=${configSections.areas}
|
||||
.route=${this.route}
|
||||
has-fab
|
||||
|
||||
@@ -294,10 +294,7 @@ class DialogAddAutomationElement
|
||||
feature.domain === "automation" &&
|
||||
feature.preview_feature === "new_triggers_conditions"
|
||||
)?.enabled ?? false;
|
||||
this._tab =
|
||||
this._newTriggersAndConditions && this._params?.type !== "condition"
|
||||
? "targets"
|
||||
: "groups";
|
||||
this._tab = this._newTriggersAndConditions ? "targets" : "groups";
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1353,6 +1350,61 @@ class DialogAddAutomationElement
|
||||
this._labelRegistry?.find(({ label_id }) => label_id === labelId)
|
||||
);
|
||||
|
||||
private _getDomainType(domain: string) {
|
||||
return ENTITY_DOMAINS_MAIN.has(domain) ||
|
||||
(this._manifests?.[domain].integration_type === "entity" &&
|
||||
!ENTITY_DOMAINS_OTHER.has(domain))
|
||||
? "dynamicGroups"
|
||||
: this._manifests?.[domain].integration_type === "helper"
|
||||
? "helpers"
|
||||
: "other";
|
||||
}
|
||||
|
||||
private _sortDomainsByCollection(
|
||||
type: AddAutomationElementDialogParams["type"],
|
||||
entries: [
|
||||
string,
|
||||
{ title: string; items: AddAutomationElementListItem[] },
|
||||
][]
|
||||
): { title: string; items: AddAutomationElementListItem[] }[] {
|
||||
const order: string[] = [];
|
||||
|
||||
TYPES[type].collections.forEach((collection) => {
|
||||
order.push(...Object.keys(collection.groups));
|
||||
});
|
||||
|
||||
return entries
|
||||
.sort((a, b) => {
|
||||
const domainA = a[0];
|
||||
const domainB = b[0];
|
||||
|
||||
if (order.includes(domainA) && order.includes(domainB)) {
|
||||
return order.indexOf(domainA) - order.indexOf(domainB);
|
||||
}
|
||||
|
||||
let typeA = domainA;
|
||||
let typeB = domainB;
|
||||
|
||||
if (!order.includes(domainA)) {
|
||||
typeA = this._getDomainType(domainA);
|
||||
}
|
||||
|
||||
if (!order.includes(domainB)) {
|
||||
typeB = this._getDomainType(domainB);
|
||||
}
|
||||
|
||||
if (typeA === typeB) {
|
||||
return stringCompare(
|
||||
a[1].title,
|
||||
b[1].title,
|
||||
this.hass.locale.language
|
||||
);
|
||||
}
|
||||
return order.indexOf(typeA) - order.indexOf(typeB);
|
||||
})
|
||||
.map((entry) => entry[1]);
|
||||
}
|
||||
|
||||
// #endregion data
|
||||
|
||||
// #region data memoize
|
||||
@@ -1368,12 +1420,12 @@ class DialogAddAutomationElement
|
||||
|
||||
private _getAreaEntityLookupMemoized = memoizeOne(
|
||||
(entities: HomeAssistant["entities"]) =>
|
||||
getAreaEntityLookup(Object.values(entities), true)
|
||||
getAreaEntityLookup(Object.values(entities))
|
||||
);
|
||||
|
||||
private _getDeviceEntityLookupMemoized = memoizeOne(
|
||||
(entities: HomeAssistant["entities"]) =>
|
||||
getDeviceEntityLookup(Object.values(entities), true)
|
||||
getDeviceEntityLookup(Object.values(entities))
|
||||
);
|
||||
|
||||
private _extractTypeAndIdFromTarget = memoizeOne(
|
||||
@@ -1438,8 +1490,9 @@ class DialogAddAutomationElement
|
||||
);
|
||||
});
|
||||
|
||||
return Object.values(items).sort((a, b) =>
|
||||
stringCompare(a.title, b.title, this.hass.locale.language)
|
||||
return this._sortDomainsByCollection(
|
||||
this._params!.type,
|
||||
Object.entries(items)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1548,8 +1601,9 @@ class DialogAddAutomationElement
|
||||
);
|
||||
});
|
||||
|
||||
return Object.values(items).sort((a, b) =>
|
||||
stringCompare(a.title, b.title, this.hass.locale.language)
|
||||
return this._sortDomainsByCollection(
|
||||
this._params!.type,
|
||||
Object.entries(items)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1580,8 +1634,9 @@ class DialogAddAutomationElement
|
||||
);
|
||||
});
|
||||
|
||||
return Object.values(items).sort((a, b) =>
|
||||
stringCompare(a.title, b.title, this.hass.locale.language)
|
||||
return this._sortDomainsByCollection(
|
||||
this._params!.type,
|
||||
Object.entries(items)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1678,14 +1733,19 @@ class DialogAddAutomationElement
|
||||
}
|
||||
|
||||
if (this._params!.type === "action") {
|
||||
const items = await getServicesForTarget(
|
||||
const items: string[] = await getServicesForTarget(
|
||||
this.hass.callWS,
|
||||
this._selectedTarget
|
||||
);
|
||||
|
||||
const filteredItems = items.filter(
|
||||
// homeassistant services are too generic to be applied on the selected target
|
||||
(service) => !service.startsWith("homeassistant.")
|
||||
);
|
||||
|
||||
this._targetItems = this._getDomainGroupedActionListItems(
|
||||
this.hass.localize,
|
||||
items
|
||||
filteredItems
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -1913,7 +1973,7 @@ class DialogAddAutomationElement
|
||||
ha-wa-dialog {
|
||||
--dialog-content-padding: var(--ha-space-0);
|
||||
--ha-dialog-min-height: min(
|
||||
648px,
|
||||
800px,
|
||||
calc(
|
||||
100vh - max(
|
||||
var(--safe-area-inset-bottom),
|
||||
@@ -1922,7 +1982,7 @@ class DialogAddAutomationElement
|
||||
)
|
||||
);
|
||||
--ha-dialog-min-height: min(
|
||||
648px,
|
||||
800px,
|
||||
calc(
|
||||
100dvh - max(
|
||||
var(--safe-area-inset-bottom),
|
||||
|
||||
@@ -708,7 +708,11 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
this.floors
|
||||
);
|
||||
|
||||
const label = entityName || deviceName || entityId;
|
||||
let label = entityName || deviceName || entityId;
|
||||
|
||||
if (this.entities[entityId]?.hidden) {
|
||||
label += ` (${this.localize("ui.panel.config.automation.editor.entity_hidden")})`;
|
||||
}
|
||||
|
||||
return [entityId, label, stateObj] as [string, string, HassEntity];
|
||||
})
|
||||
@@ -837,12 +841,12 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
|
||||
private _getAreaEntityLookupMemoized = memoizeOne(
|
||||
(entities: HomeAssistant["entities"]) =>
|
||||
getAreaEntityLookup(Object.values(entities), true)
|
||||
getAreaEntityLookup(Object.values(entities))
|
||||
);
|
||||
|
||||
private _getDeviceEntityLookupMemoized = memoizeOne(
|
||||
(entities: HomeAssistant["entities"]) =>
|
||||
getDeviceEntityLookup(Object.values(entities), true)
|
||||
getDeviceEntityLookup(Object.values(entities))
|
||||
);
|
||||
|
||||
private _getSelectedTargetId = memoizeOne(
|
||||
|
||||
@@ -273,7 +273,7 @@ export class HaAutomationAddItems extends LitElement {
|
||||
align-items: center;
|
||||
color: var(--ha-color-text-secondary);
|
||||
padding: var(--ha-space-0);
|
||||
margin: var(--ha-space-3) var(--ha-space-4)
|
||||
margin: var(--ha-space-0) var(--ha-space-4)
|
||||
max(var(--safe-area-inset-bottom), var(--ha-space-3));
|
||||
line-height: var(--ha-line-height-expanded);
|
||||
justify-content: center;
|
||||
@@ -306,7 +306,7 @@ export class HaAutomationAddItems extends LitElement {
|
||||
.items .item-headline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--ha-space-1);
|
||||
gap: var(--ha-space-2);
|
||||
min-height: var(--ha-space-9);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@@ -366,12 +366,16 @@ export class HaAutomationAddItems extends LitElement {
|
||||
}
|
||||
|
||||
.selected-target state-badge {
|
||||
--mdc-icon-size: 20px;
|
||||
--mdc-icon-size: 24px;
|
||||
}
|
||||
.selected-target state-badge,
|
||||
.selected-target ha-domain-icon {
|
||||
.selected-target ha-floor-icon {
|
||||
display: flex;
|
||||
height: 32px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
align-items: center;
|
||||
}
|
||||
.selected-target ha-domain-icon {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -393,6 +393,10 @@ export class HaPlatformCondition extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: 0px calc(-1 * var(--ha-space-4));
|
||||
}
|
||||
ha-settings-row {
|
||||
padding: 0 var(--ha-space-4);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import "../../../components/ha-tooltip";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import {
|
||||
hasRejectedItems,
|
||||
@@ -327,14 +329,19 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
||||
const date = new Date(automation.last_triggered);
|
||||
const now = new Date();
|
||||
const dayDifference = differenceInDays(now, date);
|
||||
const formattedTime = formatShortDateTimeWithConditionalYear(
|
||||
date,
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
);
|
||||
const elementId = "last-triggered-" + slugify(automation.entity_id);
|
||||
return html`
|
||||
${dayDifference > 3
|
||||
? formatShortDateTimeWithConditionalYear(
|
||||
date,
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
)
|
||||
: relativeTime(date, locale)}
|
||||
? formattedTime
|
||||
: html`
|
||||
<ha-tooltip for=${elementId}>${formattedTime}</ha-tooltip>
|
||||
<span id=${elementId}>${relativeTime(date, locale)}</span>
|
||||
`}
|
||||
`;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -429,6 +429,10 @@ export class HaPlatformTrigger extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: 0px calc(-1 * var(--ha-space-4));
|
||||
}
|
||||
ha-settings-row {
|
||||
padding: 0 var(--ha-space-4);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ class HaConfigLabs extends SubscribeMixin(LitElement) {
|
||||
<hass-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
back-path="/config"
|
||||
back-path="/config/system"
|
||||
.header=${this.hass.localize("ui.panel.config.labs.caption")}
|
||||
>
|
||||
${sortedFeatures.length
|
||||
|
||||
@@ -24,7 +24,7 @@ import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeCssColor } from "../../../common/color/compute-color";
|
||||
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
|
||||
import { formatShortDateTimeWithConditionalYear } from "../../../common/datetime/format_date_time";
|
||||
import { relativeTime } from "../../../common/datetime/relative_time";
|
||||
import { storage } from "../../../common/decorators/storage";
|
||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
@@ -301,10 +301,21 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
||||
const date = new Date(scene.state);
|
||||
const now = new Date();
|
||||
const dayDifference = differenceInDays(now, date);
|
||||
const formattedTime = formatShortDateTimeWithConditionalYear(
|
||||
date,
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
);
|
||||
const elementId = "last-activated-" + slugify(scene.entity_id);
|
||||
return html`
|
||||
${dayDifference > 3
|
||||
? formatShortDateTime(date, this.hass.locale, this.hass.config)
|
||||
: relativeTime(date, this.hass.locale)}
|
||||
? formattedTime
|
||||
: html`
|
||||
<ha-tooltip for=${elementId}>${formattedTime}</ha-tooltip>
|
||||
<span id=${elementId}
|
||||
>${relativeTime(date, this.hass.locale)}</span
|
||||
>
|
||||
`}
|
||||
`;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -33,6 +33,8 @@ import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import "../../../components/ha-tooltip";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import {
|
||||
hasRejectedItems,
|
||||
@@ -302,19 +304,27 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
||||
sortable: true,
|
||||
title: localize("ui.card.automation.last_triggered"),
|
||||
template: (script) => {
|
||||
if (!script.last_triggered) {
|
||||
return this.hass.localize("ui.components.relative_time.never");
|
||||
}
|
||||
const date = new Date(script.last_triggered);
|
||||
const now = new Date();
|
||||
const dayDifference = differenceInDays(now, date);
|
||||
const formattedTime = formatShortDateTimeWithConditionalYear(
|
||||
date,
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
);
|
||||
const elementId = "last-triggered-" + slugify(script.entity_id);
|
||||
return html`
|
||||
${script.last_triggered
|
||||
? dayDifference > 3
|
||||
? formatShortDateTimeWithConditionalYear(
|
||||
date,
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
)
|
||||
: relativeTime(date, this.hass.locale)
|
||||
: this.hass.localize("ui.components.relative_time.never")}
|
||||
${dayDifference > 3
|
||||
? formattedTime
|
||||
: html`
|
||||
<ha-tooltip for=${elementId}>${formattedTime}</ha-tooltip>
|
||||
<span id=${elementId}
|
||||
>${relativeTime(date, this.hass.locale)}</span
|
||||
>
|
||||
`}
|
||||
`;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -26,16 +26,24 @@ import type { LovelaceConfig } from "../../data/lovelace/config/types";
|
||||
import type { LovelaceViewConfig } from "../../data/lovelace/config/view";
|
||||
import type { StatisticValue } from "../../data/recorder";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { HomeAssistant, PanelInfo } from "../../types";
|
||||
import { fileDownload } from "../../util/file_download";
|
||||
import "../lovelace/components/hui-energy-period-selector";
|
||||
import "../lovelace/hui-root";
|
||||
import type { Lovelace } from "../lovelace/types";
|
||||
import "../lovelace/views/hui-view";
|
||||
import "../lovelace/views/hui-view-container";
|
||||
|
||||
export const DEFAULT_ENERGY_COLLECTION_KEY = "energy_dashboard";
|
||||
|
||||
const EMPTY_PREFERENCES: EnergyPreferences = {
|
||||
energy_sources: [],
|
||||
device_consumption: [],
|
||||
device_consumption_water: [],
|
||||
};
|
||||
|
||||
const OVERVIEW_VIEW = {
|
||||
path: "overview",
|
||||
strategy: {
|
||||
type: "energy-overview",
|
||||
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
@@ -43,8 +51,8 @@ const OVERVIEW_VIEW = {
|
||||
} as LovelaceViewConfig;
|
||||
|
||||
const ELECTRICITY_VIEW = {
|
||||
back_path: "/energy",
|
||||
path: "electricity",
|
||||
back_path: "/energy",
|
||||
strategy: {
|
||||
type: "energy-electricity",
|
||||
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
@@ -72,54 +80,96 @@ class PanelEnergy extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
|
||||
@property({ attribute: false }) public panel?: PanelInfo;
|
||||
|
||||
@state() private _lovelace?: Lovelace;
|
||||
|
||||
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@property({ attribute: false }) public route?: {
|
||||
path: string;
|
||||
prefix: string;
|
||||
};
|
||||
|
||||
@state()
|
||||
private _config?: LovelaceConfig;
|
||||
private _prefs?: EnergyPreferences;
|
||||
|
||||
get _viewPath(): string | undefined {
|
||||
const viewPath: string | undefined = this.route!.path.split("/")[1];
|
||||
return viewPath ? decodeURI(viewPath) : undefined;
|
||||
}
|
||||
@state()
|
||||
private _error?: string;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._loadLovelaceConfig();
|
||||
}
|
||||
|
||||
public async willUpdate(changedProps: PropertyValues) {
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
super.willUpdate(changedProps);
|
||||
// Initial setup
|
||||
if (!this.hasUpdated) {
|
||||
this.hass.loadFragmentTranslation("lovelace");
|
||||
this._loadConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!changedProps.has("hass")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as this["hass"];
|
||||
if (oldHass?.locale !== this.hass.locale) {
|
||||
if (oldHass && oldHass.localize !== this.hass.localize) {
|
||||
this._setLovelace();
|
||||
} else if (oldHass && oldHass.localize !== this.hass.localize) {
|
||||
this._reloadView();
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadLovelaceConfig() {
|
||||
private _fetchEnergyPrefs = async (): Promise<
|
||||
EnergyPreferences | undefined
|
||||
> => {
|
||||
const collection = getEnergyDataCollection(this.hass, {
|
||||
key: DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
});
|
||||
try {
|
||||
this._config = undefined;
|
||||
this._config = await this._generateLovelaceConfig();
|
||||
} catch (err) {
|
||||
this._error = (err as Error).message;
|
||||
await collection.refresh();
|
||||
} catch (err: any) {
|
||||
if (err.code === "not_found") {
|
||||
return undefined;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
return collection.prefs;
|
||||
};
|
||||
|
||||
this._setLovelace();
|
||||
private async _loadConfig() {
|
||||
try {
|
||||
this._error = undefined;
|
||||
const prefs = await this._fetchEnergyPrefs();
|
||||
this._prefs = prefs || EMPTY_PREFERENCES;
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to load prefs:", err);
|
||||
this._prefs = EMPTY_PREFERENCES;
|
||||
this._error = (err as Error).message || "Unknown error";
|
||||
}
|
||||
await this._setLovelace();
|
||||
|
||||
// Navigate to first view if not there yet
|
||||
const firstPath = this._lovelace!.config?.views?.[0]?.path;
|
||||
const viewPath: string | undefined = this.route!.path.split("/")[1];
|
||||
if (viewPath !== firstPath) {
|
||||
navigate(`${this.route!.prefix}/${firstPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async _setLovelace() {
|
||||
const config = await this._generateLovelaceConfig();
|
||||
|
||||
this._lovelace = {
|
||||
config: config,
|
||||
rawConfig: config,
|
||||
editMode: false,
|
||||
urlPath: "energy",
|
||||
mode: "generated",
|
||||
locale: this.hass.locale,
|
||||
enableFullEditMode: () => undefined,
|
||||
saveConfig: async () => undefined,
|
||||
deleteConfig: async () => undefined,
|
||||
setEditMode: () => undefined,
|
||||
showToast: () => undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private _back(ev) {
|
||||
@@ -128,7 +178,17 @@ class PanelEnergy extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._config && !this._error) {
|
||||
if (this._error) {
|
||||
return html`
|
||||
<div class="centered">
|
||||
<ha-alert alert-type="error">
|
||||
An error occurred loading energy preferences: ${this._error}
|
||||
</ha-alert>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (!this._prefs) {
|
||||
// Still loading
|
||||
return html`
|
||||
<div class="centered">
|
||||
@@ -136,20 +196,31 @@ class PanelEnergy extends LitElement {
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
const isSingleView = this._config?.views.length === 1;
|
||||
const viewPath = this._viewPath;
|
||||
const viewIndex = this._config
|
||||
? Math.max(
|
||||
this._config.views.findIndex((view) => view.path === viewPath),
|
||||
0
|
||||
)
|
||||
: 0;
|
||||
const showBack =
|
||||
this._searchParms.has("historyBack") || (!isSingleView && viewIndex > 0);
|
||||
|
||||
if (!this._lovelace) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const viewPath: string | undefined = this.route!.path.split("/")[1];
|
||||
|
||||
const views = this._lovelace.config?.views || [];
|
||||
const viewIndex = Math.max(
|
||||
views.findIndex((view) => view.path === viewPath),
|
||||
0
|
||||
);
|
||||
|
||||
const showBack = this._searchParms.has("historyBack") || viewIndex > 0;
|
||||
|
||||
return html`
|
||||
<div class="header">
|
||||
<div class="toolbar">
|
||||
<hui-root
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.lovelace=${this._lovelace}
|
||||
.route=${this.route}
|
||||
.panel=${this.panel}
|
||||
@reload-energy-panel=${this._reloadConfig}
|
||||
>
|
||||
<div class="toolbar" slot="toolbar">
|
||||
${showBack
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
@@ -175,14 +246,17 @@ class PanelEnergy extends LitElement {
|
||||
.collectionKey=${DEFAULT_ENERGY_COLLECTION_KEY}
|
||||
>
|
||||
${this.hass.user?.is_admin
|
||||
? html` <ha-list-item
|
||||
slot="overflow-menu"
|
||||
graphic="icon"
|
||||
@request-selected=${this._navigateConfig}
|
||||
>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiPencil}> </ha-svg-icon>
|
||||
${this.hass!.localize("ui.panel.energy.configure")}
|
||||
</ha-list-item>`
|
||||
? html`
|
||||
<ha-list-item
|
||||
slot="overflow-menu"
|
||||
graphic="icon"
|
||||
@request-selected=${this._navigateConfig}
|
||||
>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiPencil}>
|
||||
</ha-svg-icon>
|
||||
${this.hass!.localize("ui.panel.energy.configure")}
|
||||
</ha-list-item>
|
||||
`
|
||||
: nothing}
|
||||
<ha-list-item
|
||||
slot="overflow-menu"
|
||||
@@ -194,54 +268,15 @@ class PanelEnergy extends LitElement {
|
||||
</ha-list-item>
|
||||
</hui-energy-period-selector>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hui-view-container
|
||||
.hass=${this.hass}
|
||||
@reload-energy-panel=${this._reloadView}
|
||||
>
|
||||
${this._error
|
||||
? html`<div class="centered">
|
||||
<ha-alert alert-type="error">
|
||||
An error occurred while fetching your energy preferences:
|
||||
${this._error}
|
||||
</ha-alert>
|
||||
</div>`
|
||||
: this._lovelace
|
||||
? html`<hui-view
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.lovelace=${this._lovelace}
|
||||
.index=${viewIndex}
|
||||
></hui-view>`
|
||||
: nothing}
|
||||
</hui-view-container>
|
||||
</hui-root>
|
||||
`;
|
||||
}
|
||||
|
||||
private _fetchEnergyPrefs = async (): Promise<
|
||||
EnergyPreferences | undefined
|
||||
> => {
|
||||
const collection = getEnergyDataCollection(this.hass, {
|
||||
key: DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
});
|
||||
try {
|
||||
await collection.refresh();
|
||||
} catch (err: any) {
|
||||
if (err.code === "not_found") {
|
||||
return undefined;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
return collection.prefs;
|
||||
};
|
||||
|
||||
private async _generateLovelaceConfig(): Promise<LovelaceConfig> {
|
||||
const prefs = await this._fetchEnergyPrefs();
|
||||
if (
|
||||
!prefs ||
|
||||
(prefs.device_consumption.length === 0 &&
|
||||
prefs.energy_sources.length === 0)
|
||||
!this._prefs ||
|
||||
(this._prefs.device_consumption.length === 0 &&
|
||||
this._prefs.energy_sources.length === 0)
|
||||
) {
|
||||
await import("./cards/energy-setup-wizard-card");
|
||||
return {
|
||||
@@ -249,7 +284,7 @@ class PanelEnergy extends LitElement {
|
||||
};
|
||||
}
|
||||
|
||||
const isElectricityOnly = prefs.energy_sources.every((source) =>
|
||||
const isElectricityOnly = this._prefs.energy_sources.every((source) =>
|
||||
["grid", "solar", "battery"].includes(source.type)
|
||||
);
|
||||
if (isElectricityOnly) {
|
||||
@@ -259,8 +294,8 @@ class PanelEnergy extends LitElement {
|
||||
}
|
||||
|
||||
const hasWater =
|
||||
prefs.energy_sources.some((source) => source.type === "water") ||
|
||||
prefs.device_consumption_water?.length > 0;
|
||||
this._prefs.energy_sources.some((source) => source.type === "water") ||
|
||||
this._prefs.device_consumption_water?.length > 0;
|
||||
|
||||
const views: LovelaceViewConfig[] = [OVERVIEW_VIEW, ELECTRICITY_VIEW];
|
||||
if (hasWater) {
|
||||
@@ -269,25 +304,6 @@ class PanelEnergy extends LitElement {
|
||||
return { views };
|
||||
}
|
||||
|
||||
private _setLovelace() {
|
||||
if (!this._config) {
|
||||
return;
|
||||
}
|
||||
this._lovelace = {
|
||||
config: this._config,
|
||||
rawConfig: this._config,
|
||||
editMode: false,
|
||||
urlPath: "energy",
|
||||
mode: "generated",
|
||||
locale: this.hass.locale,
|
||||
enableFullEditMode: () => undefined,
|
||||
saveConfig: async () => undefined,
|
||||
deleteConfig: async () => undefined,
|
||||
setEditMode: () => undefined,
|
||||
showToast: () => undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private _navigateConfig(ev) {
|
||||
ev.stopPropagation();
|
||||
navigate("/config/energy?historyBack=1");
|
||||
@@ -593,8 +609,8 @@ class PanelEnergy extends LitElement {
|
||||
fileDownload(url, "energy.csv");
|
||||
}
|
||||
|
||||
private _reloadView() {
|
||||
this._loadLovelaceConfig();
|
||||
private _reloadConfig() {
|
||||
this._loadConfig();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
@@ -620,45 +636,6 @@ class PanelEnergy extends LitElement {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
.header {
|
||||
background-color: var(--app-header-background-color);
|
||||
color: var(--app-header-text-color, white);
|
||||
border-bottom: var(--app-header-border-bottom, none);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: calc(
|
||||
var(--mdc-top-app-bar-width, 100%) - var(
|
||||
--safe-area-inset-right,
|
||||
0px
|
||||
)
|
||||
);
|
||||
padding-top: var(--safe-area-inset-top);
|
||||
z-index: 4;
|
||||
transition: box-shadow 200ms linear;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
-webkit-backdrop-filter: var(--app-header-backdrop-filter, none);
|
||||
backdrop-filter: var(--app-header-backdrop-filter, none);
|
||||
padding-top: var(--safe-area-inset-top);
|
||||
padding-right: var(--safe-area-inset-right);
|
||||
}
|
||||
:host([narrow]) .header {
|
||||
width: calc(
|
||||
var(--mdc-top-app-bar-width, 100%) - var(
|
||||
--safe-area-inset-left,
|
||||
0px
|
||||
) - var(--safe-area-inset-right, 0px)
|
||||
);
|
||||
padding-left: var(--safe-area-inset-left);
|
||||
}
|
||||
:host([scrolled]) .header {
|
||||
box-shadow: var(
|
||||
--mdc-top-app-bar-fixed-box-shadow,
|
||||
0px 2px 4px -1px rgba(0, 0, 0, 0.2),
|
||||
0px 4px 5px 0px rgba(0, 0, 0, 0.14),
|
||||
0px 1px 10px 0px rgba(0, 0, 0, 0.12)
|
||||
);
|
||||
}
|
||||
.toolbar {
|
||||
height: var(--header-height);
|
||||
display: flex;
|
||||
@@ -677,24 +654,6 @@ class PanelEnergy extends LitElement {
|
||||
line-height: var(--ha-line-height-normal);
|
||||
flex-grow: 1;
|
||||
}
|
||||
hui-view-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
padding-top: calc(var(--header-height) + var(--safe-area-inset-top));
|
||||
padding-right: var(--safe-area-inset-right);
|
||||
padding-inline-end: var(--safe-area-inset-right);
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
}
|
||||
:host([narrow]) hui-view-container {
|
||||
padding-left: var(--safe-area-inset-left);
|
||||
padding-inline-start: var(--safe-area-inset-left);
|
||||
}
|
||||
hui-view {
|
||||
flex: 1 1 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
.centered {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -55,6 +55,9 @@ export class EnergyElectricityViewStrategy extends ReactiveElement {
|
||||
const hasPowerDevices = prefs.device_consumption.find(
|
||||
(device) => device.stat_rate
|
||||
);
|
||||
const showFloorsNAreas = !prefs.device_consumption.some(
|
||||
(d) => d.included_in_stat
|
||||
);
|
||||
|
||||
view.cards!.push({
|
||||
type: "energy-compare",
|
||||
@@ -67,6 +70,8 @@ export class EnergyElectricityViewStrategy extends ReactiveElement {
|
||||
title: hass.localize("ui.panel.energy.cards.power_sankey_title"),
|
||||
type: "power-sankey",
|
||||
collection_key: collectionKey,
|
||||
group_by_floor: showFloorsNAreas,
|
||||
group_by_area: showFloorsNAreas,
|
||||
grid_options: {
|
||||
columns: 24,
|
||||
},
|
||||
@@ -156,9 +161,6 @@ export class EnergyElectricityViewStrategy extends ReactiveElement {
|
||||
|
||||
// Only include if we have at least 1 device in the config.
|
||||
if (prefs.device_consumption.length) {
|
||||
const showFloorsNAreas = !prefs.device_consumption.some(
|
||||
(d) => d.included_in_stat
|
||||
);
|
||||
view.cards!.push({
|
||||
title: hass.localize("ui.panel.energy.cards.energy_sankey_title"),
|
||||
type: "energy-sankey",
|
||||
|
||||
@@ -81,10 +81,15 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
cards: [],
|
||||
};
|
||||
if (hasPowerSources && hasPowerDevices) {
|
||||
const showFloorsNAreas = !prefs.device_consumption.some(
|
||||
(d) => d.included_in_stat
|
||||
);
|
||||
overviewSection.cards!.push({
|
||||
title: hass.localize("ui.panel.energy.cards.power_sankey_title"),
|
||||
type: "power-sankey",
|
||||
collection_key: collectionKey,
|
||||
group_by_floor: showFloorsNAreas,
|
||||
group_by_area: showFloorsNAreas,
|
||||
grid_options: {
|
||||
columns: 24,
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/entity/ha-entities-picker";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-dialog-footer";
|
||||
import "../../../components/ha-wa-dialog";
|
||||
@@ -78,6 +79,16 @@ export class DialogEditHome
|
||||
@value-changed=${this._favoriteEntitiesChanged}
|
||||
></ha-entities-picker>
|
||||
|
||||
<ha-alert alert-type="info">
|
||||
${this.hass.localize("ui.panel.home.editor.areas_hint", {
|
||||
areas_page: html`<a
|
||||
href="/config/areas?historyBack=1"
|
||||
@click=${this.closeDialog}
|
||||
>${this.hass.localize("ui.panel.home.editor.areas_page")}</a
|
||||
>`,
|
||||
})}
|
||||
</ha-alert>
|
||||
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
@@ -140,6 +151,11 @@ export class DialogEditHome
|
||||
ha-entities-picker {
|
||||
display: block;
|
||||
}
|
||||
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-top: var(--ha-space-4);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -217,6 +217,9 @@ export class HuiEnergyDevicesGraphCard
|
||||
show: true,
|
||||
type: "value",
|
||||
name: "kWh",
|
||||
axisPointer: {
|
||||
show: false,
|
||||
},
|
||||
};
|
||||
options.yAxis = {
|
||||
show: true,
|
||||
|
||||
@@ -23,6 +23,9 @@ const DEFAULT_CONFIG: Partial<PowerSankeyCardConfig> = {
|
||||
group_by_area: true,
|
||||
};
|
||||
|
||||
// Minimum power threshold in kW to display a device node
|
||||
const MIN_POWER_THRESHOLD = 0.01;
|
||||
|
||||
interface PowerData {
|
||||
solar: number;
|
||||
from_grid: number;
|
||||
@@ -251,23 +254,75 @@ class HuiPowerSankeyCard
|
||||
let untrackedConsumption = homeNode.value;
|
||||
const deviceNodes: Node[] = [];
|
||||
const parentLinks: Record<string, string> = {};
|
||||
|
||||
// Build a map of device relationships for hierarchy resolution
|
||||
// Key: stat_consumption (energy), Value: { stat_rate, included_in_stat }
|
||||
const deviceMap = new Map<
|
||||
string,
|
||||
{ stat_rate?: string; included_in_stat?: string }
|
||||
>();
|
||||
prefs.device_consumption.forEach((device) => {
|
||||
deviceMap.set(device.stat_consumption, {
|
||||
stat_rate: device.stat_rate,
|
||||
included_in_stat: device.included_in_stat,
|
||||
});
|
||||
});
|
||||
|
||||
// Set of stat_rate entities that will be rendered as nodes
|
||||
const renderedStatRates = new Set<string>();
|
||||
prefs.device_consumption.forEach((device) => {
|
||||
if (device.stat_rate) {
|
||||
const value = this._getCurrentPower(device.stat_rate);
|
||||
if (value >= MIN_POWER_THRESHOLD) {
|
||||
renderedStatRates.add(device.stat_rate);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Find the effective parent for power hierarchy
|
||||
// Walks up the chain to find an ancestor with stat_rate that will be rendered
|
||||
const findEffectiveParent = (
|
||||
includedInStat: string | undefined
|
||||
): string | undefined => {
|
||||
let currentParent = includedInStat;
|
||||
while (currentParent) {
|
||||
const parentDevice = deviceMap.get(currentParent);
|
||||
if (!parentDevice) {
|
||||
return undefined;
|
||||
}
|
||||
// If this parent has a stat_rate and will be rendered, use it
|
||||
if (
|
||||
parentDevice.stat_rate &&
|
||||
renderedStatRates.has(parentDevice.stat_rate)
|
||||
) {
|
||||
return parentDevice.stat_rate;
|
||||
}
|
||||
// Otherwise, continue up the chain
|
||||
currentParent = parentDevice.included_in_stat;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
prefs.device_consumption.forEach((device, idx) => {
|
||||
if (!device.stat_rate) {
|
||||
return;
|
||||
}
|
||||
const value = this._getCurrentPower(device.stat_rate);
|
||||
|
||||
if (value < 0.01) {
|
||||
if (value < MIN_POWER_THRESHOLD) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the effective parent (may be different from direct parent if parent has no stat_rate)
|
||||
const effectiveParent = findEffectiveParent(device.included_in_stat);
|
||||
|
||||
const node = {
|
||||
id: device.stat_rate,
|
||||
label: device.name || this._getEntityLabel(device.stat_rate),
|
||||
value,
|
||||
color: getGraphColorByIndex(idx, computedStyle),
|
||||
index: 4,
|
||||
parent: device.included_in_stat,
|
||||
parent: effectiveParent,
|
||||
};
|
||||
if (node.parent) {
|
||||
parentLinks[node.id] = node.parent;
|
||||
|
||||
@@ -98,17 +98,32 @@ class HuiWaterSankeyCard
|
||||
const nodes: Node[] = [];
|
||||
const links: Link[] = [];
|
||||
|
||||
// Calculate total water consumption from all devices
|
||||
let totalWaterConsumption = 0;
|
||||
prefs.device_consumption_water.forEach((device) => {
|
||||
// Calculate total water consumption from all sources or devices
|
||||
const totalDownstreamConsumption = prefs.device_consumption_water.reduce(
|
||||
(total, device) => {
|
||||
const value =
|
||||
device.stat_consumption in this._data!.stats
|
||||
? calculateStatisticSumGrowth(
|
||||
this._data!.stats[device.stat_consumption]
|
||||
) || 0
|
||||
: 0;
|
||||
return total + value;
|
||||
},
|
||||
0
|
||||
);
|
||||
const totalSourceSupply = waterSources.reduce((total, source) => {
|
||||
const value =
|
||||
device.stat_consumption in this._data!.stats
|
||||
source.stat_energy_from in this._data!.stats
|
||||
? calculateStatisticSumGrowth(
|
||||
this._data!.stats[device.stat_consumption]
|
||||
this._data!.stats[source.stat_energy_from]
|
||||
) || 0
|
||||
: 0;
|
||||
totalWaterConsumption += value;
|
||||
});
|
||||
return total + value;
|
||||
}, 0);
|
||||
const totalWaterConsumption = Math.max(
|
||||
totalDownstreamConsumption,
|
||||
totalSourceSupply
|
||||
);
|
||||
|
||||
// Create home/consumption node
|
||||
const homeNode: Node = {
|
||||
|
||||
@@ -127,7 +127,7 @@ interface UndoStackItem {
|
||||
|
||||
@customElement("hui-root")
|
||||
class HUIRoot extends LitElement {
|
||||
@property({ attribute: false }) public panel?: PanelInfo<LovelacePanelConfig>;
|
||||
@property({ attribute: false }) public panel?: PanelInfo;
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -543,68 +543,72 @@ class HUIRoot extends LitElement {
|
||||
})}
|
||||
>
|
||||
<div class="header">
|
||||
<div class="toolbar">
|
||||
<slot name="toolbar">
|
||||
<div class="toolbar">
|
||||
${this._editMode
|
||||
? html`
|
||||
<div class="main-title">
|
||||
${dashboardTitle ||
|
||||
this.hass!.localize("ui.panel.lovelace.editor.header")}
|
||||
<ha-icon-button
|
||||
slot="actionItems"
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_lovelace.edit_title"
|
||||
)}
|
||||
.path=${mdiPencil}
|
||||
class="edit-icon"
|
||||
@click=${this._editDashboard}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
<div class="action-items">${this._renderActionItems()}</div>
|
||||
`
|
||||
: html`
|
||||
${isSubview
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
slot="navigationIcon"
|
||||
@click=${this._goBack}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`
|
||||
: html`
|
||||
<ha-menu-button
|
||||
slot="navigationIcon"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
`}
|
||||
${isSubview
|
||||
? html`
|
||||
<div class="main-title">${curViewConfig.title}</div>
|
||||
`
|
||||
: hasTabViews
|
||||
? tabs
|
||||
: html`
|
||||
<div class="main-title">
|
||||
${views[0]?.title ?? dashboardTitle}
|
||||
</div>
|
||||
`}
|
||||
<div class="action-items">${this._renderActionItems()}</div>
|
||||
`}
|
||||
</div>
|
||||
${this._editMode
|
||||
? html`
|
||||
<div class="main-title">
|
||||
${dashboardTitle ||
|
||||
this.hass!.localize("ui.panel.lovelace.editor.header")}
|
||||
<div class="tab-bar">
|
||||
${tabs}
|
||||
<ha-icon-button
|
||||
slot="actionItems"
|
||||
slot="nav"
|
||||
id="add-view"
|
||||
@click=${this._addView}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_lovelace.edit_title"
|
||||
"ui.panel.lovelace.editor.edit_view.add"
|
||||
)}
|
||||
.path=${mdiPencil}
|
||||
class="edit-icon"
|
||||
@click=${this._editDashboard}
|
||||
.path=${mdiPlus}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
<div class="action-items">${this._renderActionItems()}</div>
|
||||
`
|
||||
: html`
|
||||
${isSubview
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
slot="navigationIcon"
|
||||
@click=${this._goBack}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`
|
||||
: html`
|
||||
<ha-menu-button
|
||||
slot="navigationIcon"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
`}
|
||||
${isSubview
|
||||
? html`<div class="main-title">${curViewConfig.title}</div>`
|
||||
: hasTabViews
|
||||
? tabs
|
||||
: html`
|
||||
<div class="main-title">
|
||||
${views[0]?.title ?? dashboardTitle}
|
||||
</div>
|
||||
`}
|
||||
<div class="action-items">${this._renderActionItems()}</div>
|
||||
`}
|
||||
</div>
|
||||
${this._editMode
|
||||
? html`
|
||||
<div class="tab-bar">
|
||||
${tabs}
|
||||
<ha-icon-button
|
||||
slot="nav"
|
||||
id="add-view"
|
||||
@click=${this._addView}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_view.add"
|
||||
)}
|
||||
.path=${mdiPlus}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
: nothing}
|
||||
</slot>
|
||||
</div>
|
||||
<hui-view-container
|
||||
class=${this._editMode ? "has-tab-bar" : ""}
|
||||
|
||||
@@ -123,6 +123,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
"section-visibility-changed",
|
||||
this._sectionVisibilityChanged
|
||||
);
|
||||
this._showSidebar = Boolean(window.history.state?.sidebar);
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
@@ -428,6 +429,12 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
|
||||
this._showSidebar = !this._showSidebar;
|
||||
|
||||
// Add sidebar state to history
|
||||
window.history.replaceState(
|
||||
{ ...window.history.state, sidebar: this._showSidebar },
|
||||
""
|
||||
);
|
||||
|
||||
// Restore scroll position after view updates
|
||||
this.updateComplete.then(() => {
|
||||
const scrollY = this._showSidebar
|
||||
@@ -507,8 +514,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
.wrapper.narrow hui-view-sidebar {
|
||||
grid-column: 1 / -1;
|
||||
padding-bottom: calc(
|
||||
var(--ha-space-4) + 56px + var(--ha-space-4) +
|
||||
env(safe-area-inset-bottom)
|
||||
var(--ha-space-14) + var(--ha-space-3) + var(--safe-area-inset-bottom)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -518,25 +524,24 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
|
||||
.mobile-tabs {
|
||||
position: fixed;
|
||||
bottom: calc(var(--ha-space-4) + env(safe-area-inset-bottom));
|
||||
bottom: calc(var(--ha-space-3) + var(--safe-area-inset-bottom));
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 0;
|
||||
z-index: 1;
|
||||
filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.15))
|
||||
drop-shadow(0 4px 16px rgba(0, 0, 0, 0.1));
|
||||
}
|
||||
|
||||
.mobile-tabs ha-control-select {
|
||||
width: max-content;
|
||||
min-width: 280px;
|
||||
max-width: 90%;
|
||||
--control-select-thickness: 56px;
|
||||
--control-select-border-radius: var(--ha-border-radius-6xl);
|
||||
--control-select-thickness: var(--ha-space-14);
|
||||
--control-select-border-radius: var(--ha-border-radius-pill);
|
||||
--control-select-background: var(--card-background-color);
|
||||
--control-select-background-opacity: 1;
|
||||
--control-select-color: var(--primary-color);
|
||||
--control-select-padding: 6px;
|
||||
box-shadow: rgba(0, 0, 0, 0.3) 0px 4px 10px 0px;
|
||||
}
|
||||
|
||||
ha-sortable {
|
||||
@@ -560,8 +565,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
|
||||
|
||||
.wrapper.narrow.has-sidebar .content {
|
||||
padding-bottom: calc(
|
||||
var(--ha-space-4) + 56px + var(--ha-space-4) +
|
||||
env(safe-area-inset-bottom)
|
||||
var(--ha-space-14) + var(--ha-space-3) + var(--safe-area-inset-bottom)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { mdiViewDashboard } from "@mdi/js";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../components/ha-divider";
|
||||
import "../../components/ha-icon";
|
||||
import "../../components/ha-list-item";
|
||||
import "../../components/ha-select";
|
||||
import "../../components/ha-settings-row";
|
||||
import "../../components/ha-svg-icon";
|
||||
import { saveFrontendUserData } from "../../data/frontend";
|
||||
import type { LovelaceDashboard } from "../../data/lovelace/dashboard";
|
||||
import { fetchDashboards } from "../../data/lovelace/dashboard";
|
||||
import { getPanelTitle } from "../../data/panel";
|
||||
import { getPanelIcon, getPanelTitle } from "../../data/panel";
|
||||
import type { HomeAssistant, PanelInfo } from "../../types";
|
||||
import { PANEL_DASHBOARDS } from "../config/lovelace/dashboards/ha-config-lovelace-dashboards";
|
||||
|
||||
@@ -37,54 +40,57 @@ class HaPickDashboardRow extends LitElement {
|
||||
<span slot="description">
|
||||
${this.hass.localize("ui.panel.profile.dashboard.description")}
|
||||
</span>
|
||||
${this._dashboards
|
||||
? html`<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.profile.dashboard.dropdown_label"
|
||||
)}
|
||||
.disabled=${!this._dashboards?.length}
|
||||
.value=${value}
|
||||
@selected=${this._dashboardChanged}
|
||||
naturalMenuWidth
|
||||
>
|
||||
<ha-list-item .value=${USE_SYSTEM_VALUE}>
|
||||
${this.hass.localize("ui.panel.profile.dashboard.system")}
|
||||
<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.profile.dashboard.dropdown_label"
|
||||
)}
|
||||
.value=${value}
|
||||
@selected=${this._dashboardChanged}
|
||||
naturalMenuWidth
|
||||
>
|
||||
<ha-list-item .value=${USE_SYSTEM_VALUE}>
|
||||
${this.hass.localize("ui.panel.profile.dashboard.system")}
|
||||
</ha-list-item>
|
||||
<ha-divider></ha-divider>
|
||||
<ha-list-item value="lovelace" graphic="icon">
|
||||
<ha-svg-icon slot="graphic" .path=${mdiViewDashboard}></ha-svg-icon>
|
||||
${this.hass.localize("ui.panel.profile.dashboard.lovelace")}
|
||||
</ha-list-item>
|
||||
${PANEL_DASHBOARDS.map((panel) => {
|
||||
const panelInfo = this.hass.panels[panel] as PanelInfo | undefined;
|
||||
if (!panelInfo) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<ha-list-item value=${panelInfo.url_path} graphic="icon">
|
||||
<ha-icon
|
||||
slot="graphic"
|
||||
.icon=${getPanelIcon(panelInfo)}
|
||||
></ha-icon>
|
||||
${getPanelTitle(this.hass, panelInfo)}
|
||||
</ha-list-item>
|
||||
<ha-divider></ha-divider>
|
||||
<ha-list-item value="lovelace">
|
||||
${this.hass.localize("ui.panel.profile.dashboard.lovelace")}
|
||||
</ha-list-item>
|
||||
${PANEL_DASHBOARDS.map((panel) => {
|
||||
const panelInfo = this.hass.panels[panel] as
|
||||
| PanelInfo
|
||||
| undefined;
|
||||
if (!panelInfo) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<ha-list-item value=${panelInfo.url_path}>
|
||||
${getPanelTitle(this.hass, panelInfo)}
|
||||
</ha-list-item>
|
||||
`;
|
||||
})}
|
||||
<ha-divider></ha-divider>
|
||||
${this._dashboards.map((dashboard) => {
|
||||
if (!this.hass.user!.is_admin && dashboard.require_admin) {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
<ha-list-item .value=${dashboard.url_path}>
|
||||
${dashboard.title}
|
||||
</ha-list-item>
|
||||
`;
|
||||
})}
|
||||
</ha-select>`
|
||||
: html`<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.profile.dashboard.dropdown_label"
|
||||
)}
|
||||
disabled
|
||||
></ha-select>`}
|
||||
`;
|
||||
})}
|
||||
${this._dashboards?.length
|
||||
? html`
|
||||
<ha-divider></ha-divider>
|
||||
${this._dashboards.map((dashboard) => {
|
||||
if (!this.hass.user!.is_admin && dashboard.require_admin) {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
<ha-list-item .value=${dashboard.url_path} graphic="icon">
|
||||
<ha-icon
|
||||
slot="graphic"
|
||||
.icon=${dashboard.icon || "mdi:view-dashboard"}
|
||||
></ha-icon>
|
||||
${dashboard.title}
|
||||
</ha-list-item>
|
||||
`;
|
||||
})}
|
||||
`
|
||||
: nothing}
|
||||
</ha-select>
|
||||
</ha-settings-row>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -2227,7 +2227,9 @@
|
||||
"title": "Edit home page",
|
||||
"description": "Configure your home page display preferences.",
|
||||
"favorite_entities_helper": "Display your favorite entities. Home Assistant will still suggest based on commonly used up to 8 slots.",
|
||||
"save_failed": "Failed to save home page configuration"
|
||||
"save_failed": "Failed to save home page configuration",
|
||||
"areas_hint": "You can rearrange your floors and areas in the order that best represents your house on the {areas_page}.",
|
||||
"areas_page": "areas page"
|
||||
}
|
||||
},
|
||||
"my": {
|
||||
@@ -4045,6 +4047,7 @@
|
||||
"other_areas": "Other areas",
|
||||
"services": "Services",
|
||||
"helpers": "Helpers",
|
||||
"entity_hidden": "[%key:ui::panel::config::devices::entities::hidden%]",
|
||||
"triggers": {
|
||||
"name": "Triggers",
|
||||
"header": "When",
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
let askWrite;
|
||||
|
||||
const HASS_URL = `${location.protocol}//${location.host}`;
|
||||
|
||||
describe("token_storage.askWrite", () => {
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal("__HASS_URL__", HASS_URL);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
@@ -4,9 +4,12 @@ import { FallbackStorage } from "../../../test_helper/local-storage-fallback";
|
||||
|
||||
let saveTokens;
|
||||
|
||||
const HASS_URL = `${location.protocol}//${location.host}`;
|
||||
|
||||
describe("token_storage.saveTokens", () => {
|
||||
beforeEach(() => {
|
||||
window.localStorage = new FallbackStorage();
|
||||
vi.stubGlobal("__HASS_URL__", HASS_URL);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { describe, it, expect, test, vi, afterEach, beforeEach } from "vitest";
|
||||
import type { AuthData } from "home-assistant-js-websocket";
|
||||
import { FallbackStorage } from "../../../test_helper/local-storage-fallback";
|
||||
|
||||
const HASS_URL = `${location.protocol}//${location.host}`;
|
||||
|
||||
describe("token_storage", () => {
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal(
|
||||
@@ -11,6 +13,7 @@ describe("token_storage", () => {
|
||||
writeEnabled: undefined,
|
||||
})
|
||||
);
|
||||
vi.stubGlobal("__HASS_URL__", HASS_URL);
|
||||
window.localStorage = new FallbackStorage();
|
||||
});
|
||||
|
||||
|
||||
151
yarn.lock
151
yarn.lock
@@ -4945,140 +4945,139 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/eslint-plugin@npm:8.47.0":
|
||||
version: 8.47.0
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.47.0"
|
||||
"@typescript-eslint/eslint-plugin@npm:8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.48.0"
|
||||
dependencies:
|
||||
"@eslint-community/regexpp": "npm:^4.10.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.47.0"
|
||||
"@typescript-eslint/type-utils": "npm:8.47.0"
|
||||
"@typescript-eslint/utils": "npm:8.47.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.47.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.48.0"
|
||||
"@typescript-eslint/type-utils": "npm:8.48.0"
|
||||
"@typescript-eslint/utils": "npm:8.48.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.48.0"
|
||||
graphemer: "npm:^1.4.0"
|
||||
ignore: "npm:^7.0.0"
|
||||
natural-compare: "npm:^1.4.0"
|
||||
ts-api-utils: "npm:^2.1.0"
|
||||
peerDependencies:
|
||||
"@typescript-eslint/parser": ^8.47.0
|
||||
"@typescript-eslint/parser": ^8.48.0
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/53d86116a39429c0cdde5969f9ea2cf712f7c7cb2ed023088a876686a4771df131dbefda7645ba0724e2a5a0532e14bdffcb92c708060a46a8607dc5243083d1
|
||||
checksum: 10/c9cd87c72da7bb7f6175fdb53a4c08a26e61a3d9d1024960d193276217b37ca1e8e12328a57751ed9380475e11e198f9715e172126ea7d3b3da9948d225db92b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/parser@npm:8.47.0":
|
||||
version: 8.47.0
|
||||
resolution: "@typescript-eslint/parser@npm:8.47.0"
|
||||
"@typescript-eslint/parser@npm:8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/parser@npm:8.48.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager": "npm:8.47.0"
|
||||
"@typescript-eslint/types": "npm:8.47.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.47.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.47.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.48.0"
|
||||
"@typescript-eslint/types": "npm:8.48.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.48.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.48.0"
|
||||
debug: "npm:^4.3.4"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/e7213296a27b78511b8c9b2627ff6530d0eb31e6b076eef6f34f11ca7fbcb7e998a2fa079bfc1563a53f9d88326aa4af995241bcdf08a15c3e5be0d13fdff2d7
|
||||
checksum: 10/5919642345c79a43e57a85e0e69d1f56b5756b3fdb3586ec6371969604f589adc188338c8f12a787456edc3b38c70586d8209cffcf45e35e5a5ebd497c5f4257
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/project-service@npm:8.47.0":
|
||||
version: 8.47.0
|
||||
resolution: "@typescript-eslint/project-service@npm:8.47.0"
|
||||
"@typescript-eslint/project-service@npm:8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/project-service@npm:8.48.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.47.0"
|
||||
"@typescript-eslint/types": "npm:^8.47.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.48.0"
|
||||
"@typescript-eslint/types": "npm:^8.48.0"
|
||||
debug: "npm:^4.3.4"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/e2f935dae66ce27e6c0cce8b750da0e8fe84b6e0fa248bf8210b84eec3c4d2e2679a878185f445ce507d132215a676dcf8a21d47ab70c547da47ede000a128e1
|
||||
checksum: 10/5853a2f57bf8a26b70c1fe5a906c1890ad4f0fca127218a7805161fc9ad547af97f4a600f32f5acdf2f2312b156affca2bea84af9a433215cbcc2056b6a27c77
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/scope-manager@npm:8.47.0":
|
||||
version: 8.47.0
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.47.0"
|
||||
"@typescript-eslint/scope-manager@npm:8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.48.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.47.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.47.0"
|
||||
checksum: 10/e97ae0f746f6bb5706181a973bcc0c1268706ef7e8c18594b37168bb0b41b1673d3f0ba1a2575ee3bd121066500fdc75af313f6ad283198942a5cdb65ade7621
|
||||
"@typescript-eslint/types": "npm:8.48.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.48.0"
|
||||
checksum: 10/963af7af235e940467504969c565b359ca454a156eba0d5af2e4fd9cca4294947187e1a85107ff05801688ac85b5767d2566414cbef47a03c23f7b46527decca
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.47.0, @typescript-eslint/tsconfig-utils@npm:^8.47.0":
|
||||
version: 8.47.0
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.47.0"
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.48.0, @typescript-eslint/tsconfig-utils@npm:^8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.48.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/7f44441da3778928937419f8ebc62939538cf30087e56c0ca56f599ce98111b82f496902a9e15d713822b9cd14b17937d57b722468450a48748f8e50fd7161af
|
||||
checksum: 10/e480cd80498c4119a8c5bc413a22abf4bf365b3674ff95f5513292ede31e4fd8118f50d76a786de702696396a43c0c7a4d0c2ccd1c2c7db61bd941ba74495021
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/type-utils@npm:8.47.0":
|
||||
version: 8.47.0
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.47.0"
|
||||
"@typescript-eslint/type-utils@npm:8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.48.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.47.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.47.0"
|
||||
"@typescript-eslint/utils": "npm:8.47.0"
|
||||
"@typescript-eslint/types": "npm:8.48.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.48.0"
|
||||
"@typescript-eslint/utils": "npm:8.48.0"
|
||||
debug: "npm:^4.3.4"
|
||||
ts-api-utils: "npm:^2.1.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/07dcdd1ac071bbaf87b6b320d107129787a62cc403ce78e081cbe5e2ed0c576d660654e4117e6224c4c23d46919d7130b70801835d2fc41d9344c47ff946ce81
|
||||
checksum: 10/dfda42624d534f9fed270bd5c76c9c0bb879cccd3dfbfc2977c84489860fbc204f10bca5c69f3ac856cc4342c12f8947293e7449d3391af289620d7ec79ced0d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:8.47.0, @typescript-eslint/types@npm:^8.47.0":
|
||||
version: 8.47.0
|
||||
resolution: "@typescript-eslint/types@npm:8.47.0"
|
||||
checksum: 10/fc42416c01c512cfe1533bdf521925bca999adc68ffefa246e48552783f1fe9d22487d912611c5cb35fca481604aae3cab88279a53ce76c7cd7510b76775c078
|
||||
"@typescript-eslint/types@npm:8.48.0, @typescript-eslint/types@npm:^8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/types@npm:8.48.0"
|
||||
checksum: 10/cd14a7ecd1cb6af94e059a713357b9521ffab08b2793a7d33abda7006816e77f634d49d1ec6f1b99b47257a605347d691bd02b2b11477c9c328f2a27f52a664f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:8.47.0":
|
||||
version: 8.47.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.47.0"
|
||||
"@typescript-eslint/typescript-estree@npm:8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.48.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service": "npm:8.47.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.47.0"
|
||||
"@typescript-eslint/types": "npm:8.47.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.47.0"
|
||||
"@typescript-eslint/project-service": "npm:8.48.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.48.0"
|
||||
"@typescript-eslint/types": "npm:8.48.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.48.0"
|
||||
debug: "npm:^4.3.4"
|
||||
fast-glob: "npm:^3.3.2"
|
||||
is-glob: "npm:^4.0.3"
|
||||
minimatch: "npm:^9.0.4"
|
||||
semver: "npm:^7.6.0"
|
||||
tinyglobby: "npm:^0.2.15"
|
||||
ts-api-utils: "npm:^2.1.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/a480e83f1fca8a389642cbb18855ef25214c4765694b1d4a74051d2653a4fbbbf3a3cc4e544d1ecb79d49958fbf819246043c0d823d4384aa1c7b5ff79d02fcc
|
||||
checksum: 10/8ee6b9e98dd72d567b8842a695578b2098bd8cdcf5628d2819407a52b533a5a139ba9a5620976641bc4553144a1b971d75f2df218a7c281fe674df25835e9e22
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/utils@npm:8.47.0":
|
||||
version: 8.47.0
|
||||
resolution: "@typescript-eslint/utils@npm:8.47.0"
|
||||
"@typescript-eslint/utils@npm:8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/utils@npm:8.48.0"
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils": "npm:^4.7.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.47.0"
|
||||
"@typescript-eslint/types": "npm:8.47.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.47.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.48.0"
|
||||
"@typescript-eslint/types": "npm:8.48.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.48.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/e165bbcaaafb88761f12272bc4b3be1631d8a8ea319765c80cfe5bf7a5858f437486eeae177643baa213570a664f0254b41bf0541e9238b57080bb30d1a2c8ab
|
||||
checksum: 10/980b9faeaae0357bd7c002b15ab3bbcb7d5e4558be5df7980cf5221b41570a1a7b7d71ea2fcc8b1387f6c0db948d01468e6dcb31230d6757e28ac2ee5d8be4cf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:8.47.0":
|
||||
version: 8.47.0
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.47.0"
|
||||
"@typescript-eslint/visitor-keys@npm:8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.48.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.47.0"
|
||||
"@typescript-eslint/types": "npm:8.48.0"
|
||||
eslint-visitor-keys: "npm:^4.2.1"
|
||||
checksum: 10/1e184cdebc4ab15da8a46ae2624ba4543c6bea83ced80a1602da99b72c00b5f6ea913ae021823c555a35a65bb9a9df09d119713998c44b00eba25e1407844294
|
||||
checksum: 10/f9eaff8225b3b00e486e0221bd596b08a3ed463f31fab88221256908f6208c48f745281b7b92e6358d25e1dbdc37c6c2f4b42503403c24b071165bafd9a35d52
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8347,7 +8346,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.2, fast-glob@npm:^3.3.2, fast-glob@npm:^3.3.3":
|
||||
"fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.2, fast-glob@npm:^3.3.3":
|
||||
version: 3.3.3
|
||||
resolution: "fast-glob@npm:3.3.3"
|
||||
dependencies:
|
||||
@@ -9368,7 +9367,7 @@ __metadata:
|
||||
tinykeys: "npm:3.0.0"
|
||||
ts-lit-plugin: "npm:2.0.2"
|
||||
typescript: "npm:5.9.3"
|
||||
typescript-eslint: "npm:8.47.0"
|
||||
typescript-eslint: "npm:8.48.0"
|
||||
ua-parser-js: "npm:2.0.6"
|
||||
vite-tsconfig-paths: "npm:5.1.4"
|
||||
vitest: "npm:4.0.13"
|
||||
@@ -14281,18 +14280,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript-eslint@npm:8.47.0":
|
||||
version: 8.47.0
|
||||
resolution: "typescript-eslint@npm:8.47.0"
|
||||
"typescript-eslint@npm:8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "typescript-eslint@npm:8.48.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.47.0"
|
||||
"@typescript-eslint/parser": "npm:8.47.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.47.0"
|
||||
"@typescript-eslint/utils": "npm:8.47.0"
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.48.0"
|
||||
"@typescript-eslint/parser": "npm:8.48.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.48.0"
|
||||
"@typescript-eslint/utils": "npm:8.48.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/159dad98535dafd68c6228fae4aaf9e02d65d9ac3b02ddf0356b56ce72651dd9860e8bf9e0ee22532d77dd382d7f810e1bf1dd41c3fab381f627a08580c5117e
|
||||
checksum: 10/9be54df60faf3b5a6d255032b4478170b6f64e38b8396475a2049479d1e3c1f5a23a18bb4d2d6ff685ef92ff8f2af28215772fe33b48148a8cf83a724d0778d1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user