mirror of
https://github.com/home-assistant/frontend.git
synced 2026-06-24 09:11:36 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d4b1fe0c7f | |||
| 8ecd350e6f | |||
| a26de31a2d | |||
| 77110afc59 | |||
| 7b6e9ba738 |
@@ -0,0 +1,18 @@
|
||||
diff --git a/lib/cook-raw-quasi.js b/lib/cook-raw-quasi.js
|
||||
index 3ea8fa7be8e357c1066d7417caeeecd841415208..6bf04ab0bed8897b5ff2898ca835867aec5cee6a 100644
|
||||
--- a/lib/cook-raw-quasi.js
|
||||
+++ b/lib/cook-raw-quasi.js
|
||||
@@ -1,10 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
-function cookRawQuasi({transform}, raw) {
|
||||
+function cookRawQuasi({transformSync}, raw) {
|
||||
// This nasty hack is needed until https://github.com/babel/babel/issues/9242 is resolved.
|
||||
const args = {raw};
|
||||
|
||||
- transform('cooked`' + args.raw + '`', {
|
||||
+ // Babel 8 removed synchronous `transform`; use `transformSync` instead.
|
||||
+ transformSync('cooked`' + args.raw + '`', {
|
||||
babelrc: false,
|
||||
configFile: false,
|
||||
plugins: [
|
||||
@@ -84,7 +84,12 @@ module.exports.swcOptions = () => ({
|
||||
},
|
||||
});
|
||||
|
||||
module.exports.babelOptions = ({ latestBuild, isTestBuild, sw }) => ({
|
||||
module.exports.babelOptions = ({
|
||||
latestBuild,
|
||||
isProdBuild,
|
||||
isTestBuild,
|
||||
sw,
|
||||
}) => ({
|
||||
babelrc: false,
|
||||
compact: false,
|
||||
assumptions: {
|
||||
@@ -120,6 +125,28 @@ module.exports.babelOptions = ({ latestBuild, isTestBuild, sw }) => ({
|
||||
ignoreModuleNotFound: true,
|
||||
},
|
||||
],
|
||||
// Minify template literals for production
|
||||
isProdBuild && [
|
||||
"template-html-minifier",
|
||||
{
|
||||
modules: {
|
||||
...Object.fromEntries(
|
||||
["lit", "lit-element", "lit-html"].map((m) => [
|
||||
m,
|
||||
[
|
||||
"html",
|
||||
{ name: "svg", encapsulation: "svg" },
|
||||
{ name: "css", encapsulation: "style" },
|
||||
],
|
||||
])
|
||||
),
|
||||
"@polymer/polymer/lib/utils/html-tag.js": ["html"],
|
||||
},
|
||||
strictCSS: true,
|
||||
htmlMinifier: module.exports.htmlMinifierOptions,
|
||||
failOnError: false, // we can turn this off in case of false positives
|
||||
},
|
||||
],
|
||||
// Import helpers and regenerator from runtime package.
|
||||
// `moduleName` is pinned so helpers resolve from `@babel/runtime`: the
|
||||
// corejs3 polyfill provider above otherwise redirects them to the
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
/* global module */
|
||||
// rspack/webpack loader that minifies the HTML, SVG, and CSS inside lit
|
||||
// tagged template literals using `minify-literals` (html-minifier-next +
|
||||
// lightningcss). Replaces the unmaintained babel-plugin-template-html-minifier.
|
||||
//
|
||||
// It runs between swc and babel: swc has already stripped TS types and
|
||||
// decorators (so minify-literals' acorn parser only sees plain ESM), but the
|
||||
// `html`/`css`/`svg` tagged templates are still intact at ES2021. Running after
|
||||
// babel instead would miss the legacy build, where babel lowers the templates
|
||||
// to `_taggedTemplateLiteral()` calls that no longer look like tagged templates.
|
||||
|
||||
// minify-literals is ESM-only, so load it via dynamic import from this CJS loader.
|
||||
let minifyPromise;
|
||||
const getMinifier = () => {
|
||||
if (!minifyPromise) {
|
||||
minifyPromise = import("minify-literals").then((m) => m.minifyHTMLLiterals);
|
||||
}
|
||||
return minifyPromise;
|
||||
};
|
||||
|
||||
// HTML options mirror the previous babel-plugin-template-html-minifier config
|
||||
// (html-minifier-next is option-compatible with html-minifier-terser). CSS in
|
||||
// css`` templates and inline <style> is handled by minify-literals' lightningcss
|
||||
// default.
|
||||
const htmlOptions = {
|
||||
caseSensitive: true,
|
||||
collapseWhitespace: true,
|
||||
conservativeCollapse: true,
|
||||
decodeEntities: true,
|
||||
removeComments: true,
|
||||
removeRedundantAttributes: true,
|
||||
};
|
||||
|
||||
module.exports = function minifyTemplateLiteralsLoader(source, map, meta) {
|
||||
const callback = this.async();
|
||||
getMinifier()
|
||||
.then((minifyHTMLLiterals) =>
|
||||
minifyHTMLLiterals(source, {
|
||||
fileName: this.resourcePath,
|
||||
html: htmlOptions,
|
||||
})
|
||||
)
|
||||
.then((result) => {
|
||||
if (!result) {
|
||||
// No tagged templates changed; pass through untouched.
|
||||
callback(null, source, map, meta);
|
||||
} else {
|
||||
callback(null, result.code, result.map ?? map, meta);
|
||||
}
|
||||
})
|
||||
.catch(callback);
|
||||
};
|
||||
+18
-29
@@ -67,36 +67,25 @@ const createRspackConfig = ({
|
||||
{
|
||||
test: /\.m?js$|\.ts$/,
|
||||
exclude: /node_modules[\\/]core-js/,
|
||||
use: (info) =>
|
||||
[
|
||||
{
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
...bundle.babelOptions({
|
||||
latestBuild,
|
||||
isTestBuild,
|
||||
sw: info.issuerLayer === "sw",
|
||||
}),
|
||||
cacheDirectory: !isProdBuild,
|
||||
cacheCompression: false,
|
||||
},
|
||||
use: (info) => [
|
||||
{
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
...bundle.babelOptions({
|
||||
latestBuild,
|
||||
isProdBuild,
|
||||
isTestBuild,
|
||||
sw: info.issuerLayer === "sw",
|
||||
}),
|
||||
cacheDirectory: !isProdBuild,
|
||||
cacheCompression: false,
|
||||
},
|
||||
// Minify lit html/svg/css tagged template literals for production.
|
||||
// Must run after swc (TS/decorators stripped, but templates kept at
|
||||
// ES2021) and before babel — otherwise the legacy build lowers
|
||||
// html`` to _taggedTemplateLiteral() calls that can no longer be
|
||||
// matched, leaving legacy templates unminified.
|
||||
isProdBuild && {
|
||||
loader: path.join(
|
||||
__dirname,
|
||||
"minify-template-literals-loader.cjs"
|
||||
),
|
||||
},
|
||||
{
|
||||
loader: "builtin:swc-loader",
|
||||
options: bundle.swcOptions(),
|
||||
},
|
||||
].filter(Boolean),
|
||||
},
|
||||
{
|
||||
loader: "builtin:swc-loader",
|
||||
options: bundle.swcOptions(),
|
||||
},
|
||||
],
|
||||
resolve: {
|
||||
fullySpecified: false,
|
||||
},
|
||||
|
||||
+3
-3
@@ -158,6 +158,7 @@
|
||||
"@vitest/coverage-v8": "4.1.9",
|
||||
"babel-loader": "10.1.1",
|
||||
"babel-plugin-polyfill-corejs3": "1.0.0",
|
||||
"babel-plugin-template-html-minifier": "patch:babel-plugin-template-html-minifier@npm%3A4.1.0#~/.yarn/patches/babel-plugin-template-html-minifier-npm-4.1.0-9a3c00055a.patch",
|
||||
"browserslist-useragent-regexp": "4.1.4",
|
||||
"del": "8.0.1",
|
||||
"eslint": "10.5.0",
|
||||
@@ -182,12 +183,11 @@
|
||||
"jsdom": "29.1.1",
|
||||
"jszip": "3.10.1",
|
||||
"license-checker-rseidelsohn": "5.0.1",
|
||||
"lint-staged": "17.0.7",
|
||||
"lint-staged": "17.0.8",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.18.1",
|
||||
"map-stream": "0.0.7",
|
||||
"minify-literals": "2.0.2",
|
||||
"pinst": "3.0.0",
|
||||
"prettier": "3.8.4",
|
||||
"rspack-manifest-plugin": "5.2.2",
|
||||
@@ -217,6 +217,6 @@
|
||||
},
|
||||
"packageManager": "yarn@4.17.0",
|
||||
"volta": {
|
||||
"node": "24.17.0"
|
||||
"node": "24.18.0"
|
||||
}
|
||||
}
|
||||
|
||||
+42
-7
@@ -42,6 +42,12 @@ import {
|
||||
|
||||
export const ENERGY_COLLECTION_KEY_PREFIX = "energy_";
|
||||
|
||||
// Collection key for the statistics-based energy dashboard views (Overview,
|
||||
// Electricity, Gas, Water).
|
||||
export const DEFAULT_ENERGY_COLLECTION_KEY = "energy_dashboard";
|
||||
// Collection key for the real-time "Now" view (live power + 5-minute stats).
|
||||
export const DEFAULT_POWER_COLLECTION_KEY = "energy_dashboard_now";
|
||||
|
||||
// All collection keys created this session
|
||||
const energyCollectionKeys = new Set<string | undefined>();
|
||||
|
||||
@@ -787,9 +793,30 @@ const findEnergyDataCollection = (
|
||||
return (hass.connection as any)[key];
|
||||
};
|
||||
|
||||
// When does the collection's day period need to roll over to the next day?
|
||||
// With `midnightRollover` (the real-time "Now" view) it rolls over right at
|
||||
// midnight. Otherwise it waits an hour, until the new day's first hourly
|
||||
// statistic exists — rolling over at midnight would show an empty graph.
|
||||
export const getNextEnergyPeriodStart = (
|
||||
midnightRollover: boolean,
|
||||
now: Date,
|
||||
locale: HomeAssistant["locale"],
|
||||
config: HomeAssistant["config"]
|
||||
): Date => {
|
||||
const dayEnd = calcDate(now, endOfDay, locale, config);
|
||||
return midnightRollover ? addMilliseconds(dayEnd, 1) : addHours(dayEnd, 1);
|
||||
};
|
||||
|
||||
export const getEnergyDataCollection = (
|
||||
hass: HomeAssistant,
|
||||
options: { prefs?: EnergyPreferences; key?: string } = {}
|
||||
options: {
|
||||
prefs?: EnergyPreferences;
|
||||
key?: string;
|
||||
// The real-time "Now" view opts in to rolling its day period over at
|
||||
// midnight rather than an hour later (it shows live data, so it always
|
||||
// tracks today and never falls back to yesterday in the first hour).
|
||||
midnightRollover?: boolean;
|
||||
} = {}
|
||||
): EnergyCollection => {
|
||||
const [key, collectionKey] = convertCollectionKeyToConnection(
|
||||
hass,
|
||||
@@ -799,6 +826,8 @@ export const getEnergyDataCollection = (
|
||||
return (hass.connection as any)[key];
|
||||
}
|
||||
|
||||
const midnightRollover = options.midnightRollover ?? false;
|
||||
|
||||
energyCollectionKeys.add(collectionKey);
|
||||
|
||||
const collection = getCollection<EnergyData>(
|
||||
@@ -857,12 +886,16 @@ export const getEnergyDataCollection = (
|
||||
|
||||
const now = new Date();
|
||||
const hour = formatTime24h(now, hass.locale, hass.config).split(":")[0];
|
||||
// Set start to start of today if we have data for today, otherwise yesterday
|
||||
// Set start to start of today if we have data for today, otherwise yesterday.
|
||||
// The real-time "Now" view always tracks today; it shows live data even
|
||||
// before today's first statistic exists, so it never falls back to yesterday.
|
||||
const preferredPeriod =
|
||||
(localStorage.getItem(`energy-default-period-${key}`) as DateRange) ||
|
||||
"today";
|
||||
const period =
|
||||
preferredPeriod === "today" && hour === "0" ? "yesterday" : preferredPeriod;
|
||||
preferredPeriod === "today" && hour === "0" && !midnightRollover
|
||||
? "yesterday"
|
||||
: preferredPeriod;
|
||||
|
||||
const [start, end] = calcDateRange(hass.locale, hass.config, period);
|
||||
collection.start = calcDate(start, startOfDay, hass.locale, hass.config);
|
||||
@@ -886,10 +919,12 @@ export const getEnergyDataCollection = (
|
||||
collection.refresh();
|
||||
scheduleUpdatePeriod();
|
||||
},
|
||||
addHours(
|
||||
calcDate(new Date(), endOfDay, hass.locale, hass.config),
|
||||
1
|
||||
).getTime() - Date.now() // Switch to next day an hour after the day changed
|
||||
getNextEnergyPeriodStart(
|
||||
midnightRollover,
|
||||
new Date(),
|
||||
hass.locale,
|
||||
hass.config
|
||||
).getTime() - Date.now()
|
||||
);
|
||||
};
|
||||
scheduleUpdatePeriod();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { LovelaceCardFeatureContext } from "../panels/lovelace/card-features/types";
|
||||
import type { LovelaceBadgeConfig } from "./lovelace/config/badge";
|
||||
import type { LovelaceCardConfig } from "./lovelace/config/card";
|
||||
|
||||
export interface CustomCardSuggestion<
|
||||
@@ -10,6 +11,13 @@ export interface CustomCardSuggestion<
|
||||
config: T;
|
||||
}
|
||||
|
||||
export interface CustomBadgeSuggestion<
|
||||
T extends LovelaceBadgeConfig = LovelaceBadgeConfig,
|
||||
> {
|
||||
label?: string;
|
||||
config: T;
|
||||
}
|
||||
|
||||
export interface CustomCardEntry {
|
||||
type: string;
|
||||
name?: string;
|
||||
@@ -28,6 +36,10 @@ export interface CustomBadgeEntry {
|
||||
description?: string;
|
||||
preview?: boolean;
|
||||
documentationURL?: string;
|
||||
getEntitySuggestion?: (
|
||||
hass: HomeAssistant,
|
||||
entityId: string
|
||||
) => CustomBadgeSuggestion | CustomBadgeSuggestion[] | null;
|
||||
}
|
||||
|
||||
export interface CustomCardFeatureEntry {
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../../../common/config/is_component_loaded";
|
||||
import { navigate } from "../../../../../common/navigate";
|
||||
import { animationStyles } from "../../../../../resources/theme/animations.globals";
|
||||
import "../../../../../components/ha-alert";
|
||||
import "../../../../../components/ha-button";
|
||||
@@ -21,6 +23,7 @@ import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-spinner";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import type { ConfigEntry } from "../../../../../data/config_entries";
|
||||
import { getConfigEntries } from "../../../../../data/config_entries";
|
||||
@@ -66,20 +69,46 @@ class ZHAConfigDashboard extends LitElement {
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues<this>) {
|
||||
super.firstUpdated(changedProperties);
|
||||
if (this.hass) {
|
||||
this.hass.loadBackendTranslation("config_panel", "zha", false);
|
||||
this._fetchConfigEntry();
|
||||
this._fetchConfiguration();
|
||||
this._fetchDevicesAndGroups();
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
if (!isComponentLoaded(this.hass.config, "zha")) {
|
||||
navigate("/config/integrations", { replace: true });
|
||||
return;
|
||||
}
|
||||
this.hass.loadBackendTranslation("config_panel", "zha", false);
|
||||
this._load();
|
||||
}
|
||||
|
||||
private async _load(): Promise<void> {
|
||||
await this._fetchConfigEntry();
|
||||
if (!this._configEntry) {
|
||||
return;
|
||||
}
|
||||
this._fetchConfiguration();
|
||||
this._fetchDevicesAndGroups();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const devices = this._configEntry
|
||||
? Object.values(this.hass.devices).filter((device) =>
|
||||
device.config_entries.includes(this._configEntry!.entry_id)
|
||||
)
|
||||
: [];
|
||||
if (!this._configEntry) {
|
||||
return html`
|
||||
<hass-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.header=${this.hass.localize("ui.panel.config.zha.network.caption")}
|
||||
back-path="/config"
|
||||
>
|
||||
<div class="loading">
|
||||
<ha-spinner></ha-spinner>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
const configEntry = this._configEntry;
|
||||
const devices = Object.values(this.hass.devices).filter((device) =>
|
||||
device.config_entries.includes(configEntry.entry_id)
|
||||
);
|
||||
const deviceCount = devices.length;
|
||||
|
||||
let entityCount = 0;
|
||||
@@ -463,6 +492,12 @@ class ZHAConfigDashboard extends LitElement {
|
||||
margin-top: var(--ha-space-6);
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: var(--ha-space-12);
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export const DEFAULT_ENERGY_COLLECTION_KEY = "energy_dashboard";
|
||||
export const DEFAULT_POWER_COLLECTION_KEY = "energy_dashboard_now";
|
||||
@@ -16,7 +16,7 @@ import "../lovelace/hui-root";
|
||||
import type { Lovelace } from "../lovelace/types";
|
||||
import "../lovelace/views/hui-view";
|
||||
import "../lovelace/views/hui-view-container";
|
||||
import { DEFAULT_POWER_COLLECTION_KEY } from "./constants";
|
||||
import { DEFAULT_POWER_COLLECTION_KEY } from "../../data/energy";
|
||||
|
||||
@customElement("ha-panel-energy")
|
||||
class PanelEnergy extends LitElement {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import {
|
||||
DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
DEFAULT_POWER_COLLECTION_KEY,
|
||||
EMPTY_PREFERENCES,
|
||||
getEnergyDataCollection,
|
||||
} from "../../../data/energy";
|
||||
@@ -11,10 +13,6 @@ import type { LovelaceStrategyViewConfig } from "../../../data/lovelace/config/v
|
||||
import type { LocalizeKeys } from "../../../common/translations/localize";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceStrategyDependency } from "../../lovelace/strategies/types";
|
||||
import {
|
||||
DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
DEFAULT_POWER_COLLECTION_KEY,
|
||||
} from "../constants";
|
||||
import type { EnergyViewPath } from "./energy-cards";
|
||||
import {
|
||||
hasDeviceConsumption,
|
||||
@@ -160,6 +158,9 @@ async function fetchEnergyPrefs(
|
||||
): Promise<EnergyPreferences> {
|
||||
const collection = getEnergyDataCollection(hass, {
|
||||
key: defaultCollection || DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
// When landing directly on the "Now" view this warms its real-time
|
||||
// collection, so it must be created with midnight rollover too.
|
||||
midnightRollover: defaultCollection === DEFAULT_POWER_COLLECTION_KEY,
|
||||
});
|
||||
|
||||
return await new Promise<EnergyPreferences>((resolve) => {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { getEnergyDataCollection } from "../../../data/energy";
|
||||
import {
|
||||
DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
getEnergyDataCollection,
|
||||
} from "../../../data/energy";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { LovelaceStrategyDependency } from "../../lovelace/strategies/types";
|
||||
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../constants";
|
||||
import type { EnergyViewStrategyConfig } from "./energy-cards";
|
||||
import { hasWaterSource, isEnergyCardVisible } from "./energy-cards";
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { getEnergyDataCollection } from "../../../data/energy";
|
||||
import {
|
||||
DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
getEnergyDataCollection,
|
||||
} from "../../../data/energy";
|
||||
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../constants";
|
||||
import type { EnergyViewStrategyConfig } from "./energy-cards";
|
||||
import { isEnergyCardVisible } from "./energy-cards";
|
||||
import { shouldShowFloorsAndAreas } from "./show-floors-and-areas";
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { getEnergyDataCollection } from "../../../data/energy";
|
||||
import {
|
||||
DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
getEnergyDataCollection,
|
||||
} from "../../../data/energy";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../constants";
|
||||
import type { EnergyViewStrategyConfig } from "./energy-cards";
|
||||
import { hasGasSource, isEnergyCardVisible } from "./energy-cards";
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { getEnergyDataCollection } from "../../../data/energy";
|
||||
import {
|
||||
DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
getEnergyDataCollection,
|
||||
} from "../../../data/energy";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../constants";
|
||||
import type { EnergyViewStrategyConfig } from "./energy-cards";
|
||||
import {
|
||||
hasGasRateSource,
|
||||
@@ -32,6 +34,8 @@ export class PowerViewStrategy extends ReactiveElement {
|
||||
|
||||
const energyCollection = getEnergyDataCollection(hass, {
|
||||
key: collectionKey,
|
||||
// The "Now" view is real-time; roll its day period over at midnight.
|
||||
midnightRollover: true,
|
||||
});
|
||||
if (!energyCollection.prefs) {
|
||||
await energyCollection.refresh();
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { getEnergyDataCollection } from "../../../data/energy";
|
||||
import {
|
||||
DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
getEnergyDataCollection,
|
||||
} from "../../../data/energy";
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceStrategyDependency } from "../../lovelace/strategies/types";
|
||||
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../constants";
|
||||
import type { EnergyViewStrategyConfig } from "./energy-cards";
|
||||
import {
|
||||
hasWaterDevices,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { EntityBadgeConfig } from "../badges/types";
|
||||
import type { BadgeSuggestion, BadgeSuggestionProvider } from "./types";
|
||||
|
||||
export const entityBadgeSuggestions: BadgeSuggestionProvider<EntityBadgeConfig> =
|
||||
{
|
||||
getEntitySuggestion(hass, entityId) {
|
||||
const suggestions: BadgeSuggestion<EntityBadgeConfig>[] = [
|
||||
{
|
||||
config: { type: "entity", entity: entityId },
|
||||
},
|
||||
{
|
||||
label: hass.localize(
|
||||
"ui.panel.lovelace.editor.badge_picker.with_name"
|
||||
),
|
||||
config: { type: "entity", entity: entityId, show_name: true },
|
||||
},
|
||||
];
|
||||
return suggestions;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { ensureArray } from "../../../common/array/ensure-array";
|
||||
import { customBadges } from "../../../data/lovelace_custom_cards";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { BADGE_SUGGESTION_PROVIDERS } from "./registry";
|
||||
import type { BadgeSuggestion } from "./types";
|
||||
|
||||
export type { BadgeSuggestion, BadgeSuggestionProvider } from "./types";
|
||||
export { BADGE_SUGGESTION_PROVIDERS } from "./registry";
|
||||
|
||||
export interface BadgeSuggestions {
|
||||
core: BadgeSuggestion[];
|
||||
custom: BadgeSuggestion[];
|
||||
}
|
||||
|
||||
export const generateBadgeSuggestions = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string | undefined
|
||||
): BadgeSuggestions => {
|
||||
if (!entityId || hass.states[entityId] === undefined) {
|
||||
return { core: [], custom: [] };
|
||||
}
|
||||
const core = Object.values(BADGE_SUGGESTION_PROVIDERS).flatMap((provider) => {
|
||||
try {
|
||||
return ensureArray(provider.getEntitySuggestion(hass, entityId)) ?? [];
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Badge suggestion provider threw:", err);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
const custom = customBadges.flatMap((badge) => {
|
||||
if (!badge.getEntitySuggestion) return [];
|
||||
try {
|
||||
return ensureArray(badge.getEntitySuggestion(hass, entityId)) ?? [];
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
`Custom badge "${badge.type}" getEntitySuggestion threw:`,
|
||||
err
|
||||
);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
return { core, custom };
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
import { entityBadgeSuggestions } from "./hui-entity-badge-suggestions";
|
||||
import type { BadgeSuggestionProvider } from "./types";
|
||||
|
||||
export const BADGE_SUGGESTION_PROVIDERS: Record<
|
||||
string,
|
||||
BadgeSuggestionProvider
|
||||
> = {
|
||||
entity: entityBadgeSuggestions,
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
export interface BadgeSuggestion<
|
||||
T extends LovelaceBadgeConfig = LovelaceBadgeConfig,
|
||||
> {
|
||||
label?: string;
|
||||
config: T;
|
||||
}
|
||||
|
||||
export interface BadgeSuggestionProvider<
|
||||
T extends LovelaceBadgeConfig = LovelaceBadgeConfig,
|
||||
> {
|
||||
getEntitySuggestion(
|
||||
hass: HomeAssistant,
|
||||
entityId: string
|
||||
): BadgeSuggestion<T> | BadgeSuggestion<T>[] | null;
|
||||
}
|
||||
@@ -13,14 +13,12 @@ import type {
|
||||
GridSourceTypeEnergyPreference,
|
||||
} from "../../../data/energy";
|
||||
import { domainToName } from "../../../data/integration";
|
||||
import type { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge";
|
||||
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import { computeUserInitials } from "../../../data/user";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { HELPER_DOMAINS } from "../../config/helpers/const";
|
||||
import type { EntityBadgeConfig } from "../badges/types";
|
||||
import type {
|
||||
AlarmPanelCardConfig,
|
||||
EntitiesCardConfig,
|
||||
@@ -315,23 +313,6 @@ export const computeCards = (
|
||||
];
|
||||
};
|
||||
|
||||
export const computeBadges = (
|
||||
_states: HassEntities,
|
||||
entityIds: string[]
|
||||
): LovelaceBadgeConfig[] => {
|
||||
const badges: LovelaceBadgeConfig[] = [];
|
||||
|
||||
for (const entityId of entityIds) {
|
||||
const config: EntityBadgeConfig = {
|
||||
type: "entity",
|
||||
entity: entityId,
|
||||
};
|
||||
|
||||
badges.push(config);
|
||||
}
|
||||
return badges;
|
||||
};
|
||||
|
||||
const computeDefaultViewStates = (
|
||||
entities: HassEntities,
|
||||
entityEntries: HomeAssistant["entities"]
|
||||
|
||||
@@ -0,0 +1,375 @@
|
||||
import { mdiClose, mdiViewGridPlus } from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { computeEntityPickerDisplay } from "../../../../common/entity/compute_entity_name_display";
|
||||
import "../../../../components/entity/state-badge";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-combo-box-item";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-ripple";
|
||||
import "../../../../components/ha-section-title";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import type { LovelaceBadgeConfig } from "../../../../data/lovelace/config/badge";
|
||||
import { haStyleScrollbar } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
generateBadgeSuggestions,
|
||||
type BadgeSuggestions,
|
||||
} from "../../badge-suggestions";
|
||||
import type { BadgeSuggestion } from "../../badge-suggestions/types";
|
||||
import "../card-editor/hui-suggestion-entity-tree";
|
||||
import type { HuiSuggestionEntityTree } from "../card-editor/hui-suggestion-entity-tree";
|
||||
import "./hui-suggestion-badge";
|
||||
|
||||
@customElement("hui-badge-suggestion-picker")
|
||||
export class HuiBadgeSuggestionPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Array, attribute: false })
|
||||
public prioritizedBadgeTypes?: string[];
|
||||
|
||||
@state() private _entityId?: string;
|
||||
|
||||
@state() private _narrow = false;
|
||||
|
||||
private _narrowMql?: MediaQueryList;
|
||||
|
||||
@query("hui-suggestion-entity-tree")
|
||||
private _entityTree?: HuiSuggestionEntityTree;
|
||||
|
||||
public async focus(): Promise<void> {
|
||||
await this.updateComplete;
|
||||
await this._entityTree?.focus();
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this._narrowMql = matchMedia("(max-width: 600px)");
|
||||
this._narrow = this._narrowMql.matches;
|
||||
this._narrowMql.addEventListener("change", this._handleNarrowChange);
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._narrowMql?.removeEventListener("change", this._handleNarrowChange);
|
||||
this._narrowMql = undefined;
|
||||
}
|
||||
|
||||
private _handleNarrowChange = (ev: MediaQueryListEvent) => {
|
||||
this._narrow = ev.matches;
|
||||
};
|
||||
|
||||
// Memoize on scalars so the result stays stable when only hass changes.
|
||||
// Keeps hui-badge previews from re-rendering on every state tick.
|
||||
private _computeSuggestions = memoizeOne(
|
||||
(
|
||||
entityId: string | undefined,
|
||||
priorityTypesKey: string
|
||||
): BadgeSuggestions => {
|
||||
const { core, custom } = generateBadgeSuggestions(this.hass, entityId);
|
||||
const priorityTypes = priorityTypesKey
|
||||
? priorityTypesKey.split("|")
|
||||
: undefined;
|
||||
if (!priorityTypes?.length) return { core, custom };
|
||||
const isPrioritized = (s: BadgeSuggestion) =>
|
||||
priorityTypes.includes(s.config.type);
|
||||
return {
|
||||
core: [
|
||||
...core.filter(isPrioritized),
|
||||
...core.filter((s) => !isPrioritized(s)),
|
||||
],
|
||||
custom,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
protected render() {
|
||||
const hasEntity = !!this._entityId;
|
||||
// Tree is rendered unconditionally so its state (filter, expanded
|
||||
// branches, fuse index) survives the desktop/mobile and tree/suggestions
|
||||
// switches.
|
||||
const showTree = !this._narrow || !hasEntity;
|
||||
const showMain = !this._narrow || hasEntity;
|
||||
return html`
|
||||
<div class=${classMap({ sidebar: true, hidden: !showTree })}>
|
||||
<hui-suggestion-entity-tree
|
||||
class="tree"
|
||||
.hass=${this.hass}
|
||||
.selectedEntityId=${this._entityId}
|
||||
@entity-picked=${this._handleEntityPicked}
|
||||
></hui-suggestion-entity-tree>
|
||||
</div>
|
||||
<div class=${classMap({ main: true, hidden: !showMain })}>
|
||||
<div class="content ha-scrollbar">
|
||||
${this._renderMainContent(hasEntity)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderMainContent(
|
||||
hasEntity: boolean
|
||||
): TemplateResult | typeof nothing {
|
||||
if (!hasEntity) return this._renderEmptyState();
|
||||
const { core, custom } = this._suggestions();
|
||||
return html`
|
||||
${this._narrow ? this._renderSelectedEntity() : nothing}
|
||||
<ha-section-title>
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.badge_picker.suggestions_title"
|
||||
)}
|
||||
</ha-section-title>
|
||||
${this._renderSuggestionsGrid(core)}
|
||||
${custom.length
|
||||
? html`
|
||||
<ha-section-title>
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.badge_picker.community_title"
|
||||
)}
|
||||
</ha-section-title>
|
||||
${this._renderSuggestionsGrid(custom)}
|
||||
`
|
||||
: nothing}
|
||||
${this._renderBrowseBadge()}
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderBrowseBadge(): TemplateResult {
|
||||
return html`
|
||||
<div class="browse-badge">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.badge_picker.not_found"
|
||||
)}
|
||||
</p>
|
||||
<ha-button appearance="plain" @click=${this._browseBadges}>
|
||||
<ha-svg-icon slot="start" .path=${mdiViewGridPlus}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.badge_picker.browse_badges"
|
||||
)}
|
||||
</ha-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderSelectedEntity(): TemplateResult {
|
||||
const stateObj = this.hass.states[this._entityId!];
|
||||
const { primary, secondary } = stateObj
|
||||
? computeEntityPickerDisplay(this.hass, stateObj)
|
||||
: { primary: this._entityId!, secondary: undefined };
|
||||
return html`
|
||||
<ha-section-title>
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.badge_picker.selected_entity"
|
||||
)}
|
||||
</ha-section-title>
|
||||
<ha-combo-box-item compact class="selected-entity">
|
||||
${stateObj
|
||||
? html`<state-badge
|
||||
slot="start"
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
></state-badge>`
|
||||
: nothing}
|
||||
<span slot="headline">${primary}</span>
|
||||
${secondary
|
||||
? html`<span slot="supporting-text">${secondary}</span>`
|
||||
: nothing}
|
||||
<ha-icon-button
|
||||
slot="end"
|
||||
.label=${this.hass.localize("ui.common.clear")}
|
||||
.path=${mdiClose}
|
||||
@click=${this._clearEntity}
|
||||
></ha-icon-button>
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderEmptyState(): TemplateResult {
|
||||
return html`
|
||||
<div class="content-empty">
|
||||
<h2>
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.badge_picker.content_empty_title"
|
||||
)}
|
||||
</h2>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.badge_picker.content_empty_description"
|
||||
)}
|
||||
</p>
|
||||
<ha-button appearance="plain" @click=${this._browseBadges}>
|
||||
<ha-svg-icon slot="start" .path=${mdiViewGridPlus}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.badge_picker.browse_badges"
|
||||
)}
|
||||
</ha-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _suggestionKeys = new WeakMap<BadgeSuggestion, string>();
|
||||
|
||||
private _suggestionKey = (s: BadgeSuggestion): string => {
|
||||
let key = this._suggestionKeys.get(s);
|
||||
if (key === undefined) {
|
||||
key = JSON.stringify(s.config);
|
||||
this._suggestionKeys.set(s, key);
|
||||
}
|
||||
return key;
|
||||
};
|
||||
|
||||
private _renderSuggestionsGrid(
|
||||
suggestions: BadgeSuggestion[]
|
||||
): TemplateResult {
|
||||
return html`
|
||||
<div class="suggestions" @pick-badge-suggestion=${this._pickSuggestion}>
|
||||
${repeat(
|
||||
suggestions,
|
||||
this._suggestionKey,
|
||||
(s: BadgeSuggestion) => html`
|
||||
<hui-suggestion-badge
|
||||
.hass=${this.hass}
|
||||
.suggestion=${s}
|
||||
></hui-suggestion-badge>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _suggestions(): BadgeSuggestions {
|
||||
return this._computeSuggestions(
|
||||
this._entityId,
|
||||
(this.prioritizedBadgeTypes ?? []).join("|")
|
||||
);
|
||||
}
|
||||
|
||||
private _browseBadges(): void {
|
||||
fireEvent(this, "browse-badges", undefined);
|
||||
}
|
||||
|
||||
private _handleEntityPicked(ev: CustomEvent<{ entityId: string }>): void {
|
||||
this._entityId = ev.detail.entityId;
|
||||
}
|
||||
|
||||
private _clearEntity(): void {
|
||||
this._entityId = undefined;
|
||||
}
|
||||
|
||||
private _pickSuggestion(
|
||||
ev: CustomEvent<{ suggestion: BadgeSuggestion }>
|
||||
): void {
|
||||
fireEvent(this, "badge-suggestion-picked", {
|
||||
config: ev.detail.suggestion.config,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
flex: 0 0 320px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-inline-end: var(--ha-border-width-sm) solid
|
||||
var(--divider-color);
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.tree {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
.main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
.content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
.suggestions {
|
||||
display: grid;
|
||||
gap: var(--ha-space-3);
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
padding: var(--ha-space-3);
|
||||
}
|
||||
.content-empty {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--ha-space-3);
|
||||
padding: var(--ha-space-8) var(--ha-space-4);
|
||||
text-align: center;
|
||||
}
|
||||
.content-empty h2 {
|
||||
margin: 0;
|
||||
font-size: var(--ha-font-size-xl);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
}
|
||||
.content-empty p {
|
||||
margin: 0;
|
||||
max-width: 480px;
|
||||
color: var(--ha-color-text-secondary);
|
||||
line-height: var(--ha-line-height-expanded);
|
||||
}
|
||||
.browse-badge {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--ha-space-2);
|
||||
padding: var(--ha-space-6) var(--ha-space-4);
|
||||
}
|
||||
.browse-badge p {
|
||||
margin: 0;
|
||||
color: var(--ha-color-text-secondary);
|
||||
font-size: var(--ha-font-size-s);
|
||||
}
|
||||
|
||||
/* Mobile master/detail: sidebar OR main is visible, never both. */
|
||||
@media (max-width: 600px) {
|
||||
:host {
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.sidebar {
|
||||
flex: 1;
|
||||
border-inline-end: none;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-badge-suggestion-picker": HuiBadgeSuggestionPicker;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
"browse-badges": undefined;
|
||||
"badge-suggestion-picked": { config: LovelaceBadgeConfig };
|
||||
}
|
||||
}
|
||||
@@ -3,35 +3,24 @@ import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { cache } from "lit/directives/cache";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-dialog-header";
|
||||
import "../../../../components/ha-dialog";
|
||||
import "../../../../components/ha-dialog-footer";
|
||||
import "../../../../components/ha-dialog-header";
|
||||
import "../../../../components/ha-tab-group";
|
||||
import "../../../../components/ha-tab-group-tab";
|
||||
import "../../../../components/ha-dialog";
|
||||
import type { LovelaceBadgeConfig } from "../../../../data/lovelace/config/badge";
|
||||
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
|
||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { computeBadges } from "../../common/generate-lovelace-config";
|
||||
import "../card-editor/hui-entity-picker-table";
|
||||
import { addBadge } from "../config-util";
|
||||
import { findLovelaceContainer } from "../lovelace-path";
|
||||
import "./hui-badge-picker";
|
||||
import "./hui-badge-suggestion-picker";
|
||||
import type { CreateBadgeDialogParams } from "./show-create-badge-dialog";
|
||||
import { showEditBadgeDialog } from "./show-edit-badge-dialog";
|
||||
import { showSuggestBadgeDialog } from "./show-suggest-badge-dialog";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"selected-changed": SelectedChangedEvent;
|
||||
}
|
||||
}
|
||||
|
||||
interface SelectedChangedEvent {
|
||||
selectedEntities: string[];
|
||||
}
|
||||
|
||||
@customElement("hui-dialog-create-badge")
|
||||
export class HuiCreateDialogBadge
|
||||
@@ -46,13 +35,17 @@ export class HuiCreateDialogBadge
|
||||
|
||||
@state() private _containerConfig!: LovelaceViewConfig;
|
||||
|
||||
@state() private _selectedEntities: string[] = [];
|
||||
@state() private _currTab: "badge" | "entity" = "entity";
|
||||
|
||||
@state() private _currTab: "badge" | "entity" = "badge";
|
||||
@state() private _narrow = false;
|
||||
|
||||
public async showDialog(params: CreateBadgeDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
|
||||
this._narrow = matchMedia(
|
||||
"all and (max-width: 450px), all and (max-height: 500px)"
|
||||
).matches;
|
||||
|
||||
const containerConfig = findLovelaceContainer(
|
||||
params.lovelaceConfig,
|
||||
params.path
|
||||
@@ -74,8 +67,7 @@ export class HuiCreateDialogBadge
|
||||
private _dialogClosed(): void {
|
||||
this._open = false;
|
||||
this._params = undefined;
|
||||
this._currTab = "badge";
|
||||
this._selectedEntities = [];
|
||||
this._currTab = "entity";
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
@@ -98,7 +90,6 @@ export class HuiCreateDialogBadge
|
||||
width="large"
|
||||
@keydown=${this._ignoreKeydown}
|
||||
@closed=${this._dialogClosed}
|
||||
class=${classMap({ table: this._currTab === "entity" })}
|
||||
>
|
||||
<ha-dialog-header show-border slot="header">
|
||||
<ha-icon-button
|
||||
@@ -109,6 +100,15 @@ export class HuiCreateDialogBadge
|
||||
></ha-icon-button>
|
||||
<span slot="title">${title}</span>
|
||||
<ha-tab-group @wa-tab-show=${this._handleTabChanged}>
|
||||
<ha-tab-group-tab
|
||||
slot="nav"
|
||||
.active=${this._currTab === "entity"}
|
||||
panel="entity"
|
||||
?autofocus=${this._narrow}
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.badge_picker.by_entity"
|
||||
)}</ha-tab-group-tab
|
||||
>
|
||||
<ha-tab-group-tab
|
||||
slot="nav"
|
||||
.active=${this._currTab === "badge"}
|
||||
@@ -118,35 +118,31 @@ export class HuiCreateDialogBadge
|
||||
"ui.panel.lovelace.editor.badge_picker.by_badge"
|
||||
)}
|
||||
</ha-tab-group-tab>
|
||||
<ha-tab-group-tab
|
||||
slot="nav"
|
||||
.active=${this._currTab === "entity"}
|
||||
panel="entity"
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.badge_picker.by_entity"
|
||||
)}</ha-tab-group-tab
|
||||
>
|
||||
</ha-tab-group>
|
||||
</ha-dialog-header>
|
||||
${cache(
|
||||
this._currTab === "badge"
|
||||
? html`
|
||||
<hui-badge-picker
|
||||
autofocus
|
||||
.suggestedBadges=${this._params.suggestedBadges}
|
||||
.lovelace=${this._params.lovelaceConfig}
|
||||
.hass=${this.hass}
|
||||
@config-changed=${this._handleBadgePicked}
|
||||
></hui-badge-picker>
|
||||
`
|
||||
: html`
|
||||
<hui-entity-picker-table
|
||||
.hass=${this.hass}
|
||||
.narrow=${true}
|
||||
@selected-changed=${this._handleSelectedChanged}
|
||||
></hui-entity-picker-table>
|
||||
`
|
||||
)}
|
||||
<div class="body">
|
||||
${cache(
|
||||
this._currTab === "entity"
|
||||
? html`
|
||||
<hui-badge-suggestion-picker
|
||||
?autofocus=${!this._narrow}
|
||||
.hass=${this.hass}
|
||||
.prioritizedBadgeTypes=${this._params.suggestedBadges}
|
||||
@badge-suggestion-picked=${this._handleSuggestionPicked}
|
||||
@browse-badges=${this._handleBrowseBadges}
|
||||
></hui-badge-suggestion-picker>
|
||||
`
|
||||
: html`
|
||||
<hui-badge-picker
|
||||
?autofocus=${!this._narrow}
|
||||
.suggestedBadges=${this._params.suggestedBadges}
|
||||
.lovelace=${this._params.lovelaceConfig}
|
||||
.hass=${this.hass}
|
||||
@config-changed=${this._handleBadgePicked}
|
||||
></hui-badge-picker>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
@@ -156,13 +152,6 @@ export class HuiCreateDialogBadge
|
||||
>
|
||||
${this.hass!.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
${this._selectedEntities.length
|
||||
? html`
|
||||
<ha-button slot="primaryAction" @click=${this._suggestBadges}>
|
||||
${this.hass!.localize("ui.common.continue")}
|
||||
</ha-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-dialog-footer>
|
||||
</ha-dialog>
|
||||
`;
|
||||
@@ -181,13 +170,19 @@ export class HuiCreateDialogBadge
|
||||
--dialog-z-index: 6;
|
||||
}
|
||||
|
||||
ha-dialog.table {
|
||||
--dialog-content-padding: 0;
|
||||
@media (min-width: 451px) and (min-height: 501px) {
|
||||
ha-dialog {
|
||||
--ha-dialog-min-height: min(900px, 80vh);
|
||||
--ha-dialog-max-height: var(--ha-dialog-min-height);
|
||||
}
|
||||
}
|
||||
|
||||
ha-dialog::part(body) {
|
||||
overflow: hidden;
|
||||
}
|
||||
ha-dialog-footer {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
ha-tab-group-tab {
|
||||
flex: 1;
|
||||
@@ -196,34 +191,41 @@ export class HuiCreateDialogBadge
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
.body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
hui-badge-picker,
|
||||
hui-badge-suggestion-picker {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
hui-badge-picker {
|
||||
--badge-picker-search-shape: 0;
|
||||
--badge-picker-search-margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
hui-badge-picker,
|
||||
hui-entity-picker-table {
|
||||
height: calc(100vh - 198px);
|
||||
}
|
||||
|
||||
hui-entity-picker-table {
|
||||
display: block;
|
||||
--mdc-shape-small: 0;
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
hui-badge-picker,
|
||||
hui-entity-picker-table {
|
||||
height: calc(100vh - 158px);
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
private _handleBrowseBadges(): void {
|
||||
this._currTab = "badge";
|
||||
}
|
||||
|
||||
private async _handleSuggestionPicked(
|
||||
ev: CustomEvent<{ config: LovelaceBadgeConfig }>
|
||||
): Promise<void> {
|
||||
const config = ev.detail.config;
|
||||
const lovelaceConfig = this._params!.lovelaceConfig;
|
||||
const containerPath = this._params!.path;
|
||||
const saveConfig = this._params!.saveConfig;
|
||||
const newConfig = addBadge(lovelaceConfig, containerPath, config);
|
||||
await saveConfig(newConfig);
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
private _handleBadgePicked(ev) {
|
||||
const config = ev.detail.config;
|
||||
if (this._params!.entities && this._params!.entities.length) {
|
||||
@@ -249,13 +251,7 @@ export class HuiCreateDialogBadge
|
||||
if (newTab === this._currTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._currTab = newTab;
|
||||
this._selectedEntities = [];
|
||||
}
|
||||
|
||||
private _handleSelectedChanged(ev: CustomEvent): void {
|
||||
this._selectedEntities = ev.detail.selectedEntities;
|
||||
}
|
||||
|
||||
private _cancel(ev?: Event) {
|
||||
@@ -264,20 +260,6 @@ export class HuiCreateDialogBadge
|
||||
}
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
private _suggestBadges(): void {
|
||||
const badgeConfig = computeBadges(this.hass.states, this._selectedEntities);
|
||||
|
||||
showSuggestBadgeDialog(this, {
|
||||
lovelaceConfig: this._params!.lovelaceConfig,
|
||||
saveConfig: this._params!.saveConfig,
|
||||
path: this._params!.path as [number],
|
||||
entities: this._selectedEntities,
|
||||
badgeConfig,
|
||||
});
|
||||
|
||||
this.closeDialog();
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
import deepFreeze from "deep-freeze";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-yaml-editor";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-dialog-footer";
|
||||
import "../../../../components/ha-dialog";
|
||||
|
||||
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
||||
import type { LovelaceBadgeConfig } from "../../../../data/lovelace/config/badge";
|
||||
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { showSaveSuccessToast } from "../../../../util/toast-saved-success";
|
||||
import "../../badges/hui-badge";
|
||||
import { addBadges } from "../config-util";
|
||||
import type { LovelaceContainerPath } from "../lovelace-path";
|
||||
import { parseLovelaceContainerPath } from "../lovelace-path";
|
||||
import type { SuggestBadgeDialogParams } from "./show-suggest-badge-dialog";
|
||||
|
||||
@customElement("hui-dialog-suggest-badge")
|
||||
export class HuiDialogSuggestBadge extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: SuggestBadgeDialogParams;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
@state() private _badgeConfig?: LovelaceBadgeConfig[];
|
||||
|
||||
@state() private _saving = false;
|
||||
|
||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
public showDialog(params: SuggestBadgeDialogParams): void {
|
||||
this._params = params;
|
||||
this._badgeConfig = params.badgeConfig;
|
||||
this._open = true;
|
||||
if (!Object.isFrozen(this._badgeConfig)) {
|
||||
this._badgeConfig = deepFreeze(this._badgeConfig);
|
||||
}
|
||||
if (this._yamlEditor) {
|
||||
this._yamlEditor.setValue(this._badgeConfig);
|
||||
}
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this._open = false;
|
||||
this._params = undefined;
|
||||
this._badgeConfig = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
private _renderPreview() {
|
||||
if (this._badgeConfig) {
|
||||
return html`
|
||||
<div class="element-preview">
|
||||
${this._badgeConfig.map(
|
||||
(badgeConfig) => html`
|
||||
<hui-badge
|
||||
.hass=${this.hass}
|
||||
.config=${badgeConfig}
|
||||
preview
|
||||
></hui-badge>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
header-title=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.suggest_badge.header"
|
||||
)}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<div>
|
||||
${this._renderPreview()}
|
||||
${this._params.yaml && this._badgeConfig
|
||||
? html`
|
||||
<div class="editor">
|
||||
<ha-yaml-editor
|
||||
.defaultValue=${this._badgeConfig}
|
||||
in-dialog
|
||||
></ha-yaml-editor>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
slot="secondaryAction"
|
||||
appearance="plain"
|
||||
@click=${this.closeDialog}
|
||||
autofocus
|
||||
>
|
||||
${this._params.yaml
|
||||
? this.hass!.localize("ui.common.close")
|
||||
: this.hass!.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
${!this._params.yaml
|
||||
? html`
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._save}
|
||||
.loading=${this._saving}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.suggest_badge.add"
|
||||
)}
|
||||
</ha-button>
|
||||
`
|
||||
: nothing}
|
||||
</ha-dialog-footer>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog {
|
||||
--dialog-z-index: 6;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.element-preview {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: var(--ha-space-2);
|
||||
margin: 0;
|
||||
}
|
||||
.editor {
|
||||
padding-top: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
private _computeNewConfig(
|
||||
config: LovelaceConfig,
|
||||
path: LovelaceContainerPath
|
||||
): LovelaceConfig {
|
||||
const { viewIndex } = parseLovelaceContainerPath(path);
|
||||
|
||||
const newBadges = this._badgeConfig!;
|
||||
return addBadges(config, [viewIndex], newBadges);
|
||||
}
|
||||
|
||||
private async _save(): Promise<void> {
|
||||
if (
|
||||
!this._params?.lovelaceConfig ||
|
||||
!this._params?.path ||
|
||||
!this._params?.saveConfig ||
|
||||
!this._badgeConfig
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this._saving = true;
|
||||
|
||||
const newConfig = this._computeNewConfig(
|
||||
this._params.lovelaceConfig,
|
||||
this._params.path
|
||||
);
|
||||
await this._params!.saveConfig(newConfig);
|
||||
this._saving = false;
|
||||
showSaveSuccessToast(this, this.hass);
|
||||
this.closeDialog();
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-dialog-suggest-badge": HuiDialogSuggestBadge;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-ripple";
|
||||
import {
|
||||
getCustomBadgeEntry,
|
||||
isCustomType,
|
||||
stripCustomPrefix,
|
||||
} from "../../../../data/lovelace_custom_cards";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { BadgeSuggestion } from "../../badge-suggestions/types";
|
||||
import "../../badges/hui-badge";
|
||||
|
||||
@customElement("hui-suggestion-badge")
|
||||
export class HuiSuggestionBadge extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public suggestion!: BadgeSuggestion;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const { suggestion } = this;
|
||||
const type = suggestion.config.type;
|
||||
let badgeName: string;
|
||||
if (isCustomType(type)) {
|
||||
const customType = stripCustomPrefix(type);
|
||||
badgeName = getCustomBadgeEntry(customType)?.name ?? customType;
|
||||
} else {
|
||||
badgeName =
|
||||
this.hass.localize(
|
||||
`ui.panel.lovelace.editor.badge.${type}.name` as any
|
||||
) || type;
|
||||
}
|
||||
const label = suggestion.label
|
||||
? `${badgeName} - ${suggestion.label}`
|
||||
: badgeName;
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="badge"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
aria-label=${label}
|
||||
@keydown=${this._handleKeyDown}
|
||||
>
|
||||
<div class="overlay" @click=${this._handleClick}></div>
|
||||
<div class="badge-header">${label}</div>
|
||||
<div class="preview">
|
||||
<hui-badge
|
||||
.hass=${this.hass}
|
||||
.config=${suggestion.config}
|
||||
preview
|
||||
></hui-badge>
|
||||
</div>
|
||||
<ha-ripple></ha-ripple>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleClick(): void {
|
||||
fireEvent(this, "pick-badge-suggestion", { suggestion: this.suggestion });
|
||||
}
|
||||
|
||||
private _handleKeyDown(ev: KeyboardEvent): void {
|
||||
if (ev.key === "Enter" || ev.key === " ") {
|
||||
ev.preventDefault();
|
||||
this._handleClick();
|
||||
}
|
||||
}
|
||||
|
||||
static readonly styles: CSSResultGroup = css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
.badge {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
||||
background: var(--primary-background-color);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: var(--ha-card-border-width, var(--ha-border-width-sm)) solid
|
||||
var(--ha-card-border-color, var(--divider-color));
|
||||
}
|
||||
.badge:focus-visible {
|
||||
outline: 2px solid var(--primary-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
border-radius: inherit;
|
||||
}
|
||||
.badge-header {
|
||||
color: var(--ha-card-header-color, var(--primary-text-color));
|
||||
font-family: var(--ha-card-header-font-family, inherit);
|
||||
font-size: var(--ha-font-size-m);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
padding: var(--ha-space-3) var(--ha-space-4);
|
||||
text-align: center;
|
||||
}
|
||||
.preview {
|
||||
pointer-events: none;
|
||||
margin: var(--ha-space-4);
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-suggestion-badge": HuiSuggestionBadge;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
"pick-badge-suggestion": { suggestion: BadgeSuggestion };
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LovelaceBadgeConfig } from "../../../../data/lovelace/config/badge";
|
||||
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
|
||||
import type { LovelaceContainerPath } from "../lovelace-path";
|
||||
|
||||
export interface SuggestBadgeDialogParams {
|
||||
lovelaceConfig?: LovelaceConfig;
|
||||
yaml?: boolean;
|
||||
saveConfig?: (config: LovelaceConfig) => void;
|
||||
path?: LovelaceContainerPath;
|
||||
entities?: string[]; // We pass this to create dialog when user chooses "Pick own"
|
||||
badgeConfig: LovelaceBadgeConfig[]; // We can pass a suggested config
|
||||
}
|
||||
|
||||
const importSuggestBadgeDialog = () => import("./hui-dialog-suggest-badge");
|
||||
|
||||
export const showSuggestBadgeDialog = (
|
||||
element: HTMLElement,
|
||||
suggestBadgeDialogParams: SuggestBadgeDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "hui-dialog-suggest-badge",
|
||||
dialogImport: importSuggestBadgeDialog,
|
||||
dialogParams: suggestBadgeDialogParams,
|
||||
});
|
||||
};
|
||||
@@ -260,4 +260,7 @@ declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-entity-picker-table": HuiEntityPickerTable;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
"selected-changed": { selectedEntities: string[] };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10489,7 +10489,15 @@
|
||||
"domain": "Domain",
|
||||
"entity": "Entity",
|
||||
"by_entity": "By entity",
|
||||
"by_badge": "By badge"
|
||||
"by_badge": "By badge",
|
||||
"with_name": "With name",
|
||||
"suggestions_title": "[%key:ui::panel::lovelace::editor::cardpicker::suggestions_title%]",
|
||||
"community_title": "[%key:ui::panel::lovelace::editor::cardpicker::community_title%]",
|
||||
"selected_entity": "[%key:ui::panel::lovelace::editor::cardpicker::selected_entity%]",
|
||||
"content_empty_title": "[%key:ui::panel::lovelace::editor::cardpicker::content_empty_title%]",
|
||||
"content_empty_description": "Or browse all badge types.",
|
||||
"browse_badges": "Browse all badges",
|
||||
"not_found": "Can't find the badge you want?"
|
||||
},
|
||||
"header-footer": {
|
||||
"header": "Header",
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { startOfDay } from "date-fns";
|
||||
import type { HassConfig } from "home-assistant-js-websocket";
|
||||
import { assert, describe, it } from "vitest";
|
||||
|
||||
import { calcDate } from "../../src/common/datetime/calc_date";
|
||||
import {
|
||||
type FrontendLocaleData,
|
||||
NumberFormat,
|
||||
@@ -13,6 +16,7 @@ import {
|
||||
formatConsumptionShort,
|
||||
calculateSolarConsumedGauge,
|
||||
formatPowerShort,
|
||||
getNextEnergyPeriodStart,
|
||||
} from "../../src/data/energy";
|
||||
import type { HomeAssistant } from "../../src/types";
|
||||
|
||||
@@ -854,3 +858,54 @@ describe("Self-consumed solar gauge tests", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getNextEnergyPeriodStart", () => {
|
||||
const locale: FrontendLocaleData = {
|
||||
language: "en",
|
||||
number_format: NumberFormat.language,
|
||||
time_format: TimeFormat.language,
|
||||
date_format: DateFormat.language,
|
||||
time_zone: TimeZone.server,
|
||||
first_weekday: FirstWeekday.language,
|
||||
};
|
||||
// Pin the time zone (via TimeZone.server) so the test does not depend on the
|
||||
// machine's local zone.
|
||||
const config = { time_zone: "America/New_York" } as HassConfig;
|
||||
|
||||
const isMidnight = (date: Date) =>
|
||||
calcDate(date, startOfDay, locale, config).getTime() === date.getTime();
|
||||
|
||||
it("rolls the real-time view over at midnight, statistics an hour later", () => {
|
||||
const now = new Date("2026-06-19T15:30:00-04:00");
|
||||
|
||||
const realTime = getNextEnergyPeriodStart(true, now, locale, config);
|
||||
const statistics = getNextEnergyPeriodStart(false, now, locale, config);
|
||||
|
||||
// Real-time rolls over exactly at the next midnight.
|
||||
assert.isTrue(isMidnight(realTime));
|
||||
assert.equal(
|
||||
realTime.getTime(),
|
||||
new Date("2026-06-20T00:00:00-04:00").getTime()
|
||||
);
|
||||
|
||||
// Statistics roll over an hour after midnight, on the same day boundary.
|
||||
assert.equal(statistics.getTime() - realTime.getTime(), 60 * 60 * 1000 - 1);
|
||||
assert.equal(
|
||||
calcDate(statistics, startOfDay, locale, config).getTime(),
|
||||
realTime.getTime()
|
||||
);
|
||||
});
|
||||
|
||||
it("advances the real-time view to the next midnight when called after midnight", () => {
|
||||
const now = new Date("2026-06-20T00:30:00-04:00");
|
||||
|
||||
const realTime = getNextEnergyPeriodStart(true, now, locale, config);
|
||||
|
||||
assert.isTrue(isMidnight(realTime));
|
||||
// Next midnight is June 21, not the already-passed June 20 midnight.
|
||||
assert.equal(
|
||||
realTime.getTime(),
|
||||
new Date("2026-06-21T00:00:00-04:00").getTime()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5108,15 +5108,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sveltejs/acorn-typescript@npm:^1.0.10":
|
||||
version: 1.0.10
|
||||
resolution: "@sveltejs/acorn-typescript@npm:1.0.10"
|
||||
peerDependencies:
|
||||
acorn: ^8.9.0
|
||||
checksum: 10/5770f9bdcfdac2c5454318fffb8ba0a7ddbdd002221016d2e32855588bb5727d09cafc48923ac55404f5e4680f7a844599b91987931445fec608428137b38462
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/helpers@npm:0.5.23":
|
||||
version: 0.5.23
|
||||
resolution: "@swc/helpers@npm:0.5.23"
|
||||
@@ -6300,7 +6291,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"acorn@npm:^8.10.0, acorn@npm:^8.11.0, acorn@npm:^8.15.0, acorn@npm:^8.16.0":
|
||||
"acorn@npm:^8.10.0, acorn@npm:^8.11.0, acorn@npm:^8.15.0, acorn@npm:^8.16.0, acorn@npm:^8.5.0":
|
||||
version: 8.17.0
|
||||
resolution: "acorn@npm:8.17.0"
|
||||
bin:
|
||||
@@ -6778,6 +6769,28 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"babel-plugin-template-html-minifier@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "babel-plugin-template-html-minifier@npm:4.1.0"
|
||||
dependencies:
|
||||
clean-css: "npm:^4.2.1"
|
||||
html-minifier-terser: "npm:^5.0.0"
|
||||
is-builtin-module: "npm:^3.0.0"
|
||||
checksum: 10/d7582da510cbb947cdc06accfcc03b8da89e7634e0890902ccc8ba55467eb43a312bd5833362e40f3c6aa1b22bafba84d51f6d6d84232c17f10c59f017ac52c6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"babel-plugin-template-html-minifier@patch:babel-plugin-template-html-minifier@npm%3A4.1.0#~/.yarn/patches/babel-plugin-template-html-minifier-npm-4.1.0-9a3c00055a.patch":
|
||||
version: 4.1.0
|
||||
resolution: "babel-plugin-template-html-minifier@patch:babel-plugin-template-html-minifier@npm%3A4.1.0#~/.yarn/patches/babel-plugin-template-html-minifier-npm-4.1.0-9a3c00055a.patch::version=4.1.0&hash=4a6de3"
|
||||
dependencies:
|
||||
clean-css: "npm:^4.2.1"
|
||||
html-minifier-terser: "npm:^5.0.0"
|
||||
is-builtin-module: "npm:^3.0.0"
|
||||
checksum: 10/cd119f593f1228f13c9beb934ec2a8479d2403b12b7215b1df839a4e831fc627711ca124d1c9300b9d971f18e0b2f48ffe59310033f469e4a176a7c290dcc10b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bach@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "bach@npm:2.0.1"
|
||||
@@ -6912,13 +6925,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"boolbase@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "boolbase@npm:1.0.0"
|
||||
checksum: 10/3e25c80ef626c3a3487c73dbfc70ac322ec830666c9ad915d11b701142fab25ec1e63eff2c450c74347acfd2de854ccde865cd79ef4db1683f7c7b046ea43bb0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bottleneck@npm:^2.15.3":
|
||||
version: 2.19.5
|
||||
resolution: "bottleneck@npm:2.19.5"
|
||||
@@ -7057,6 +7063,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"builtin-modules@npm:^3.3.0":
|
||||
version: 3.3.0
|
||||
resolution: "builtin-modules@npm:3.3.0"
|
||||
checksum: 10/62e063ab40c0c1efccbfa9ffa31873e4f9d57408cb396a2649981a0ecbce56aabc93c28feaccbc5658c95aab2703ad1d11980e62ec2e5e72637404e1eb60f39e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bytes@npm:3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "bytes@npm:3.0.0"
|
||||
@@ -7137,7 +7150,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"camel-case@npm:^4.1.2":
|
||||
"camel-case@npm:^4.1.1, camel-case@npm:^4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "camel-case@npm:4.1.2"
|
||||
dependencies:
|
||||
@@ -7448,13 +7461,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"commander@npm:^11.1.0":
|
||||
version: 11.1.0
|
||||
resolution: "commander@npm:11.1.0"
|
||||
checksum: 10/66bd2d8a0547f6cb1d34022efb25f348e433b0e04ad76a65279b1b09da108f59a4d3001ca539c60a7a46ea38bcf399fc17d91adad76a8cf43845d8dcbaf5cda1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"commander@npm:^14.0.2":
|
||||
version: 14.0.3
|
||||
resolution: "commander@npm:14.0.3"
|
||||
@@ -7462,13 +7468,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"commander@npm:^15.0.0":
|
||||
version: 15.0.0
|
||||
resolution: "commander@npm:15.0.0"
|
||||
checksum: 10/a5dab1f5c3f1bf2ea19e8089f650f7fb65c3ed88e13ddde62e490d34b20057bcb2cd3de5b6ddc8aae93286083f8256ca0b812df842f574cbebe0e40f5b2f98f7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"commander@npm:^2.20.0, commander@npm:^2.20.3":
|
||||
version: 2.20.3
|
||||
resolution: "commander@npm:2.20.3"
|
||||
@@ -7476,6 +7475,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"commander@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "commander@npm:4.1.1"
|
||||
checksum: 10/3b2dc4125f387dab73b3294dbcb0ab2a862f9c0ad748ee2b27e3544d25325b7a8cdfbcc228d103a98a716960b14478114a5206b5415bd48cdafa38797891562c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"comment-parser@npm:^1.4.1":
|
||||
version: 1.4.7
|
||||
resolution: "comment-parser@npm:1.4.7"
|
||||
@@ -7664,20 +7670,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"css-select@npm:^5.1.0":
|
||||
version: 5.2.2
|
||||
resolution: "css-select@npm:5.2.2"
|
||||
dependencies:
|
||||
boolbase: "npm:^1.0.0"
|
||||
css-what: "npm:^6.1.0"
|
||||
domhandler: "npm:^5.0.2"
|
||||
domutils: "npm:^3.0.1"
|
||||
nth-check: "npm:^2.0.1"
|
||||
checksum: 10/ebb6a88446433312d1a16301afd1c5f75090805b730dbbdccb0338b0d6ca7922410375f16dde06673ef7da086e2cf3b9ad91afe9a8e0d2ee3625795cb5e0170d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"css-tree@npm:^3.0.0, css-tree@npm:^3.0.1, css-tree@npm:^3.1.0, css-tree@npm:^3.2.1":
|
||||
"css-tree@npm:^3.0.0, css-tree@npm:^3.1.0, css-tree@npm:^3.2.1":
|
||||
version: 3.2.1
|
||||
resolution: "css-tree@npm:3.2.1"
|
||||
dependencies:
|
||||
@@ -7687,23 +7680,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"css-tree@npm:~2.2.0":
|
||||
version: 2.2.1
|
||||
resolution: "css-tree@npm:2.2.1"
|
||||
dependencies:
|
||||
mdn-data: "npm:2.0.28"
|
||||
source-map-js: "npm:^1.0.1"
|
||||
checksum: 10/1959c4b0e268bf8db1b3a1776a5ba9ae3a464ccd1226bfa62799cb0a3d0039006e21fb95cec4dec9d687a9a9b90f692dff2d230b631527ece700f4bfb419aaf3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"css-what@npm:^6.1.0":
|
||||
version: 6.2.2
|
||||
resolution: "css-what@npm:6.2.2"
|
||||
checksum: 10/3c5a53be94728089bd1716f915f7f96adde5dd8bf374610eb03982266f3d860bf1ebaf108cda30509d02ef748fe33eaa59aa75911e2c49ee05a85ef1f9fb5223
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cssesc@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "cssesc@npm:3.0.0"
|
||||
@@ -7720,15 +7696,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"csso@npm:^5.0.5":
|
||||
version: 5.0.5
|
||||
resolution: "csso@npm:5.0.5"
|
||||
dependencies:
|
||||
css-tree: "npm:~2.2.0"
|
||||
checksum: 10/4036fb2b9f8ed6b948349136b39e0b19ffb5edee934893a37b55e9a116186c4ae2a9d3ba66fbdbc07fa44a853fb478cd2d8733e4743473dcd364e7f21444ff34
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"culori@npm:4.0.2":
|
||||
version: 4.0.2
|
||||
resolution: "culori@npm:4.0.2"
|
||||
@@ -8039,7 +8006,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"domutils@npm:^3.0.1, domutils@npm:^3.2.1":
|
||||
"domutils@npm:^3.2.1":
|
||||
version: 3.2.2
|
||||
resolution: "domutils@npm:3.2.2"
|
||||
dependencies:
|
||||
@@ -9637,6 +9604,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"he@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "he@npm:1.2.0"
|
||||
bin:
|
||||
he: bin/he
|
||||
checksum: 10/d09b2243da4e23f53336e8de3093e5c43d2c39f8d0d18817abfa32ce3e9355391b2edb4bb5edc376aea5d4b0b59d6a0482aab4c52bc02ef95751e4b818e847f1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"hls.js@npm:1.6.16":
|
||||
version: 1.6.16
|
||||
resolution: "hls.js@npm:1.6.16"
|
||||
@@ -9728,6 +9704,7 @@ __metadata:
|
||||
"@webcomponents/webcomponentsjs": "npm:2.8.0"
|
||||
babel-loader: "npm:10.1.1"
|
||||
babel-plugin-polyfill-corejs3: "npm:1.0.0"
|
||||
babel-plugin-template-html-minifier: "patch:babel-plugin-template-html-minifier@npm%3A4.1.0#~/.yarn/patches/babel-plugin-template-html-minifier-npm-4.1.0-9a3c00055a.patch"
|
||||
barcode-detector: "npm:3.2.0"
|
||||
browserslist-useragent-regexp: "npm:4.1.4"
|
||||
cally: "npm:0.9.2"
|
||||
@@ -9776,7 +9753,7 @@ __metadata:
|
||||
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"
|
||||
license-checker-rseidelsohn: "npm:5.0.1"
|
||||
lint-staged: "npm:17.0.7"
|
||||
lint-staged: "npm:17.0.8"
|
||||
lit: "npm:3.3.3"
|
||||
lit-analyzer: "npm:2.0.3"
|
||||
lit-html: "npm:3.3.3"
|
||||
@@ -9786,7 +9763,6 @@ __metadata:
|
||||
map-stream: "npm:0.0.7"
|
||||
marked: "npm:18.0.5"
|
||||
memoize-one: "npm:6.0.0"
|
||||
minify-literals: "npm:2.0.2"
|
||||
node-vibrant: "npm:4.0.4"
|
||||
object-hash: "npm:3.0.0"
|
||||
pinst: "npm:3.0.0"
|
||||
@@ -9865,27 +9841,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"html-minifier-next@npm:^6.2.8":
|
||||
version: 6.2.11
|
||||
resolution: "html-minifier-next@npm:6.2.11"
|
||||
dependencies:
|
||||
commander: "npm:^15.0.0"
|
||||
entities: "npm:^8.0.0"
|
||||
lightningcss: "npm:^1.32.0"
|
||||
svgo: "npm:^4.0.1"
|
||||
terser: "npm:^5.47.1"
|
||||
peerDependencies:
|
||||
"@swc/core": ^1.15.7
|
||||
peerDependenciesMeta:
|
||||
"@swc/core":
|
||||
optional: true
|
||||
bin:
|
||||
hmn: cli.js
|
||||
html-minifier-next: cli.js
|
||||
checksum: 10/d2b3923f77fff918ab09a9cfdfc3819b89dd928e1e64c60dcb39a2b08ddf6bbd3c696d90e8feb830fed11cd5d5757b271de65cb9d1713ddc171ee9668708eda6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"html-minifier-terser@npm:7.2.0":
|
||||
version: 7.2.0
|
||||
resolution: "html-minifier-terser@npm:7.2.0"
|
||||
@@ -9903,6 +9858,23 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"html-minifier-terser@npm:^5.0.0":
|
||||
version: 5.1.1
|
||||
resolution: "html-minifier-terser@npm:5.1.1"
|
||||
dependencies:
|
||||
camel-case: "npm:^4.1.1"
|
||||
clean-css: "npm:^4.2.3"
|
||||
commander: "npm:^4.1.1"
|
||||
he: "npm:^1.2.0"
|
||||
param-case: "npm:^3.0.3"
|
||||
relateurl: "npm:^0.2.7"
|
||||
terser: "npm:^4.6.3"
|
||||
bin:
|
||||
html-minifier-terser: cli.js
|
||||
checksum: 10/97d45614e8f07ba66ea66015cfa80759e3e270b475430f8a5d67586876deaad535db97be3247dee3dd2ed51aeafcd1d6bfaf02276fec12e56cf5e4141e52ae28
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"html-standard@npm:^0.0.13":
|
||||
version: 0.0.13
|
||||
resolution: "html-standard@npm:0.0.13"
|
||||
@@ -10202,6 +10174,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-builtin-module@npm:^3.0.0":
|
||||
version: 3.2.1
|
||||
resolution: "is-builtin-module@npm:3.2.1"
|
||||
dependencies:
|
||||
builtin-modules: "npm:^3.3.0"
|
||||
checksum: 10/e8f0ffc19a98240bda9c7ada84d846486365af88d14616e737d280d378695c8c448a621dcafc8332dbf0fcd0a17b0763b845400709963fa9151ddffece90ae88
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-callable@npm:^1.2.7":
|
||||
version: 1.2.7
|
||||
resolution: "is-callable@npm:1.2.7"
|
||||
@@ -11167,9 +11148,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lint-staged@npm:17.0.7":
|
||||
version: 17.0.7
|
||||
resolution: "lint-staged@npm:17.0.7"
|
||||
"lint-staged@npm:17.0.8":
|
||||
version: 17.0.8
|
||||
resolution: "lint-staged@npm:17.0.8"
|
||||
dependencies:
|
||||
listr2: "npm:^10.2.1"
|
||||
picomatch: "npm:^4.0.4"
|
||||
@@ -11181,7 +11162,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
lint-staged: bin/lint-staged.js
|
||||
checksum: 10/4ed3cd01caa78ff5cc5da7ec69f77f091c43a0d5cbb1e084f7ffd3872a9e599675fb8b5f11fd5911faee0d330952889dd0e14378a26620d8f529eae401ce49b4
|
||||
checksum: 10/2b574a3107c030e27ff1c34166ef49f2189c256bb423b0deabef0becdf13ed4cfdcc6fb6815a1285ce0daa92fc6c545d8d0245c9d47a8eb3fbccbc4cf3754587
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -11477,13 +11458,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mdn-data@npm:2.0.28":
|
||||
version: 2.0.28
|
||||
resolution: "mdn-data@npm:2.0.28"
|
||||
checksum: 10/aec475e0c078af00498ce2f9434d96a1fdebba9814d14b8f72cd6d5475293f4b3972d0538af2d5c5053d35e1b964af08b7d162b98e9846e9343990b75e4baef1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mdn-data@npm:2.27.1":
|
||||
version: 2.27.1
|
||||
resolution: "mdn-data@npm:2.27.1"
|
||||
@@ -11582,19 +11556,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minify-literals@npm:2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "minify-literals@npm:2.0.2"
|
||||
dependencies:
|
||||
"@sveltejs/acorn-typescript": "npm:^1.0.10"
|
||||
acorn: "npm:^8.16.0"
|
||||
html-minifier-next: "npm:^6.2.8"
|
||||
lightningcss: "npm:^1.32.0"
|
||||
magic-string: "npm:^0.30.21"
|
||||
checksum: 10/51de4b6affcebe082f00709241b8d1587aff06164cf9fd5797ffc45b322c14d0f77e9085171bd78cc35c5a9b0a22c5211f36d177a50fe5d0f8c57e6c10735838
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:3.1.5":
|
||||
version: 3.1.5
|
||||
resolution: "minimatch@npm:3.1.5"
|
||||
@@ -12020,15 +11981,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nth-check@npm:^2.0.1":
|
||||
version: 2.1.1
|
||||
resolution: "nth-check@npm:2.1.1"
|
||||
dependencies:
|
||||
boolbase: "npm:^1.0.0"
|
||||
checksum: 10/5afc3dafcd1573b08877ca8e6148c52abd565f1d06b1eb08caf982e3fa289a82f2cae697ffb55b5021e146d60443f1590a5d6b944844e944714a5b549675bcd3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"object-assign@npm:^4":
|
||||
version: 4.1.1
|
||||
resolution: "object-assign@npm:4.1.1"
|
||||
@@ -12285,7 +12237,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"param-case@npm:^3.0.4":
|
||||
"param-case@npm:^3.0.3, param-case@npm:^3.0.4":
|
||||
version: 3.0.4
|
||||
resolution: "param-case@npm:3.0.4"
|
||||
dependencies:
|
||||
@@ -13395,13 +13347,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sax@npm:^1.5.0":
|
||||
version: 1.6.0
|
||||
resolution: "sax@npm:1.6.0"
|
||||
checksum: 10/0909cedcd9f011ceeac80c0240a92d64ef712cf6c04e0f6ee236a8d812f86a59f61bee6bb5da28d75306db050b99e0593051ea77351795822253b984af6cf044
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"saxes@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "saxes@npm:6.0.0"
|
||||
@@ -13782,14 +13727,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"source-map-js@npm:^1.0.1, source-map-js@npm:^1.2.1":
|
||||
"source-map-js@npm:^1.2.1":
|
||||
version: 1.2.1
|
||||
resolution: "source-map-js@npm:1.2.1"
|
||||
checksum: 10/ff9d8c8bf096d534a5b7707e0382ef827b4dd360a577d3f34d2b9f48e12c9d230b5747974ee7c607f0df65113732711bb701fe9ece3c7edbd43cb2294d707df3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"source-map-support@npm:~0.5.20":
|
||||
"source-map-support@npm:~0.5.12, source-map-support@npm:~0.5.20":
|
||||
version: 0.5.21
|
||||
resolution: "source-map-support@npm:0.5.21"
|
||||
dependencies:
|
||||
@@ -13806,7 +13751,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"source-map@npm:^0.6.0, source-map@npm:~0.6.0":
|
||||
"source-map@npm:^0.6.0, source-map@npm:~0.6.0, source-map@npm:~0.6.1":
|
||||
version: 0.6.1
|
||||
resolution: "source-map@npm:0.6.1"
|
||||
checksum: 10/59ef7462f1c29d502b3057e822cdbdae0b0e565302c4dd1a95e11e793d8d9d62006cdc10e0fd99163ca33ff2071360cf50ee13f90440806e7ed57d81cba2f7ff
|
||||
@@ -14277,23 +14222,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"svgo@npm:^4.0.1":
|
||||
version: 4.0.1
|
||||
resolution: "svgo@npm:4.0.1"
|
||||
dependencies:
|
||||
commander: "npm:^11.1.0"
|
||||
css-select: "npm:^5.1.0"
|
||||
css-tree: "npm:^3.0.1"
|
||||
css-what: "npm:^6.1.0"
|
||||
csso: "npm:^5.0.5"
|
||||
picocolors: "npm:^1.1.1"
|
||||
sax: "npm:^1.5.0"
|
||||
bin:
|
||||
svgo: ./bin/svgo.js
|
||||
checksum: 10/8791aa12f3d1a5b3da12a67c2f880917512eaf32dad40563ae474deefff0630a4ce2259e06730f02150756ac77cc8b06598d30fb3ed3f02f085e6cbfbd344fb6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"symbol-tree@npm:^3.2.4":
|
||||
version: 3.2.4
|
||||
resolution: "symbol-tree@npm:3.2.4"
|
||||
@@ -14402,7 +14330,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"terser@npm:^5.15.1, terser@npm:^5.17.4, terser@npm:^5.31.1, terser@npm:^5.47.1":
|
||||
"terser@npm:^4.6.3":
|
||||
version: 4.8.1
|
||||
resolution: "terser@npm:4.8.1"
|
||||
dependencies:
|
||||
commander: "npm:^2.20.0"
|
||||
source-map: "npm:~0.6.1"
|
||||
source-map-support: "npm:~0.5.12"
|
||||
bin:
|
||||
terser: bin/terser
|
||||
checksum: 10/f58024a8bbf08d6421aea69b14f95da2a6e85a6d9a8b93895379084bd39ea70755d82f8676e9a56fde35ebaefbcb7b5d7920af537ffa1b87f638d39608941ea9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"terser@npm:^5.15.1, terser@npm:^5.17.4, terser@npm:^5.31.1":
|
||||
version: 5.48.0
|
||||
resolution: "terser@npm:5.48.0"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user