mirror of
https://github.com/home-assistant/frontend.git
synced 2026-06-05 07:51:42 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0725ce2053 |
@@ -8,7 +8,6 @@ You are an assistant helping with development of the Home Assistant frontend. Th
|
||||
|
||||
- [Quick Reference](#quick-reference)
|
||||
- [Core Architecture](#core-architecture)
|
||||
- [State Access: Contexts Instead of `hass`](#state-access-contexts-instead-of-hass)
|
||||
- [Development Standards](#development-standards)
|
||||
- [Component Library](#component-library)
|
||||
- [Common Patterns](#common-patterns)
|
||||
@@ -53,57 +52,6 @@ The Home Assistant frontend is a modern web application that:
|
||||
- Communicates with the backend via WebSocket API
|
||||
- Provides comprehensive theming and internationalization
|
||||
|
||||
## State Access: Contexts Instead of `hass`
|
||||
|
||||
Every component used to take the whole `hass: HomeAssistant` object — a god-object that re-renders on any unrelated `hass` change, forces tests to mock everything, and hides what a component actually reads. We're moving leaf components to **fine-grained [Lit context](https://lit.dev/docs/data/context/)**: consume only the slice you need and re-render only when it changes.
|
||||
|
||||
For new code, consume the matching context instead of adding a `hass` property. `hass` stays for container components that own it and feed the providers; the canonical migration is [`hui-button-card.ts`](src/panels/lovelace/cards/hui-button-card.ts). Infrastructure: contexts in [`src/data/context/index.ts`](src/data/context/index.ts), the `consume…` helpers in [`src/common/decorators/consume-context-entry.ts`](src/common/decorators/consume-context-entry.ts), and `@transform` in [`src/common/decorators/transform.ts`](src/common/decorators/transform.ts). Providers are wired automatically by `contextMixin` on `HassBaseEl` — you only consume.
|
||||
|
||||
### Contexts
|
||||
|
||||
Consume the narrowest context that covers your reads:
|
||||
|
||||
| Context | Replaces |
|
||||
| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
||||
| `statesContext` | `hass.states` |
|
||||
| `entitiesContext` / `devicesContext` / `areasContext` / `floorsContext` | `hass.entities` / `.devices` / `.areas` / `.floors` (or `registriesContext` for all four) |
|
||||
| `servicesContext` | `hass.services` |
|
||||
| `internationalizationContext` | `hass.localize`, `hass.locale`, `hass.language` |
|
||||
| `formattersContext` | `hass.formatEntityName`, `hass.formatEntityState`, `hass.formatEntityAttributeName`, … |
|
||||
| `configContext` | `hass.config`, `hass.user`, `hass.auth`, `hass.userData` |
|
||||
| `connectionContext` | `hass.connection`, `hass.connected`, `hass.hassUrl` |
|
||||
| `apiContext` | `hass.callService`, `hass.callApi`, `hass.callWS`, `hass.sendWS`, `hass.fetchWithAuth` |
|
||||
| `uiContext` | `hass.themes`, `hass.selectedTheme`, `hass.panels`, `hass.dockedSidebar`, … |
|
||||
| `narrowViewportContext` | narrow-layout boolean |
|
||||
|
||||
Lazy contexts (subscribe on first consumer, tear down after the last): `labelsContext`, `fullEntitiesContext`, `configEntriesContext`, `manifestsContext`. The single-field contexts (`localizeContext`, `themesContext`, `userContext`, …) are **deprecated** — use the grouped ones above.
|
||||
|
||||
### Consuming
|
||||
|
||||
Use the `consume…` helpers for entity-scoped and `localize` reads. `entityIdPath` is resolved against `this`, so these watch `this._config.entity`:
|
||||
|
||||
```ts
|
||||
@state() @consumeEntityState({ entityIdPath: ["_config", "entity"] })
|
||||
private _stateObj?: HassEntity; // consumeEntityStates(...) for a record of several
|
||||
|
||||
@state() @consumeEntityRegistryEntry({ entityIdPath: ["_config", "entity"] })
|
||||
private _entity?: EntityRegistryDisplayEntry;
|
||||
|
||||
@state() @consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
```
|
||||
|
||||
For any other single field, pair `@consume` with `@transform`:
|
||||
|
||||
```ts
|
||||
@state()
|
||||
@consume({ context: uiContext, subscribe: true })
|
||||
@transform<HomeAssistantUI, Themes>({ transformer: ({ themes }) => themes })
|
||||
private _themes!: Themes;
|
||||
```
|
||||
|
||||
`@transform`'s `watch` option re-runs the transformer when a host prop changes — needed when an entity id is computed, since `consumeEntityState` only watches the first path segment. To consume a whole group untransformed, drop `@transform` and type it `ContextType<typeof statesContext>`.
|
||||
|
||||
## Development Standards
|
||||
|
||||
### Code Quality Requirements
|
||||
@@ -188,7 +136,6 @@ export class HaMyComponent extends LitElement {
|
||||
### Data Management
|
||||
|
||||
- **Use WebSocket API**: All backend communication via home-assistant-js-websocket
|
||||
- **Prefer contexts over `hass`**: For state reads, consume the relevant Lit context instead of taking the whole `hass` object — see [State Access: Contexts Instead of `hass`](#state-access-contexts-instead-of-hass)
|
||||
- **Cache appropriately**: Use collections and caching for frequently accessed data
|
||||
- **Handle errors gracefully**: All API calls should have error handling
|
||||
- **Update real-time**: Subscribe to state changes for live updates
|
||||
@@ -329,7 +276,7 @@ fireEvent(this, "show-dialog", {
|
||||
|
||||
- **`variant`** (color): `"brand"` (default), `"neutral"`, `"danger"`, `"warning"`, `"success"`
|
||||
- **`appearance`** (fill style): `"accent"`, `"filled"`, `"outlined"`, `"plain"`
|
||||
- **`size`**: `"xs"` (extra small, 40px), `"s"` (small, 32px), `"m"` (medium, 40px - default), `"l"` (large, 48px), `"xl"` (extra large, 40px)
|
||||
- **`size`**: `"small"` (32px), `"medium"` (40px - default), `"large"` (48px)
|
||||
|
||||
Common patterns:
|
||||
|
||||
|
||||
@@ -77,22 +77,9 @@ jobs:
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
run: |
|
||||
# Sleep to give pypi time to populate the new version across mirrors
|
||||
sleep 240
|
||||
version=$(echo "$GITHUB_REF" | awk -F"/" '{print $NF}' )
|
||||
# Wait for the package to become available on PyPI
|
||||
echo "Waiting for home-assistant-frontend==$version to appear on PyPI..."
|
||||
for i in $(seq 1 30); do
|
||||
status=$(curl -s -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/home-assistant-frontend/$version/json")
|
||||
if [ "$status" = "200" ]; then
|
||||
echo "Package is available on PyPI!"
|
||||
break
|
||||
fi
|
||||
if [ "$i" = "30" ]; then
|
||||
echo "Timed out waiting for package to appear on PyPI"
|
||||
exit 1
|
||||
fi
|
||||
echo "Not available yet (HTTP $status), retrying in 30 seconds... ($i/30)"
|
||||
sleep 30
|
||||
done
|
||||
echo "home-assistant-frontend==$version" > ./requirements.txt
|
||||
|
||||
# home-assistant/wheels doesn't support SHA pinning
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
diff --git a/dist/tinykeys.cjs b/dist/tinykeys.cjs
|
||||
index 08c98b6eff3b8fb4b727fe8e6b096951d6ef6347..9c44f14862f582766ea1733b6dc0e97f962800d8 100644
|
||||
--- a/dist/tinykeys.cjs
|
||||
+++ b/dist/tinykeys.cjs
|
||||
@@ -61,6 +61,18 @@ function defaultKeybindingsHandlerIgnore(event) {
|
||||
function getModifierState(event, mod) {
|
||||
return typeof event.getModifierState === "function" ? event.getModifierState(mod) || ALT_GRAPH_ALIASES.includes(mod) && event.getModifierState("AltGraph") : false;
|
||||
}
|
||||
+function splitKeybindingPress(press) {
|
||||
+ let parts = [];
|
||||
+ let start = 0;
|
||||
+ for (let index = 0; index < press.length; index++) {
|
||||
+ if (press[index] === "+" && /[\w\]]/.test(press[index - 1] || "")) {
|
||||
+ parts.push(press.slice(start, index));
|
||||
+ start = index + 1;
|
||||
+ }
|
||||
+ }
|
||||
+ parts.push(press.slice(start));
|
||||
+ return parts;
|
||||
+}
|
||||
/**
|
||||
* Parses a keybinding string into its parts.
|
||||
*
|
||||
@@ -76,10 +88,10 @@ function getModifierState(event, mod) {
|
||||
*/
|
||||
function parseKeybinding(str) {
|
||||
return str.trim().split(" ").map((press) => {
|
||||
- let parts = press.split(/(?<=\w|\])\+/);
|
||||
+ let parts = splitKeybindingPress(press);
|
||||
let last = parts.pop();
|
||||
let regex = last.match(/^\((.+)\)$/);
|
||||
- let key = regex ? new RegExp(`^(?:${regex[1]})$`, "iv") : last;
|
||||
+ let key = regex ? new RegExp(`^(?:${regex[1]})$`, "i") : last;
|
||||
let requiredModifiers = [];
|
||||
let optionalModifiers = [];
|
||||
for (const part of parts) {
|
||||
@@ -201,5 +213,3 @@ exports.defaultKeybindingsHandlerIgnore = defaultKeybindingsHandlerIgnore;
|
||||
exports.matchKeybindingPress = matchKeybindingPress;
|
||||
exports.parseKeybinding = parseKeybinding;
|
||||
exports.tinykeys = tinykeys;
|
||||
-
|
||||
-//# sourceMappingURL=tinykeys.cjs.map
|
||||
\ No newline at end of file
|
||||
diff --git a/dist/tinykeys.mjs b/dist/tinykeys.mjs
|
||||
index c289972d2728e03d9b272268c38fd3392e8845bf..e22897b00aae6cdb0dbbb971445227c07be52918 100644
|
||||
--- a/dist/tinykeys.mjs
|
||||
+++ b/dist/tinykeys.mjs
|
||||
@@ -60,6 +60,18 @@ function defaultKeybindingsHandlerIgnore(event) {
|
||||
function getModifierState(event, mod) {
|
||||
return typeof event.getModifierState === "function" ? event.getModifierState(mod) || ALT_GRAPH_ALIASES.includes(mod) && event.getModifierState("AltGraph") : false;
|
||||
}
|
||||
+function splitKeybindingPress(press) {
|
||||
+ let parts = [];
|
||||
+ let start = 0;
|
||||
+ for (let index = 0; index < press.length; index++) {
|
||||
+ if (press[index] === "+" && /[\w\]]/.test(press[index - 1] || "")) {
|
||||
+ parts.push(press.slice(start, index));
|
||||
+ start = index + 1;
|
||||
+ }
|
||||
+ }
|
||||
+ parts.push(press.slice(start));
|
||||
+ return parts;
|
||||
+}
|
||||
/**
|
||||
* Parses a keybinding string into its parts.
|
||||
*
|
||||
@@ -75,10 +87,10 @@ function getModifierState(event, mod) {
|
||||
*/
|
||||
function parseKeybinding(str) {
|
||||
return str.trim().split(" ").map((press) => {
|
||||
- let parts = press.split(/(?<=\w|\])\+/);
|
||||
+ let parts = splitKeybindingPress(press);
|
||||
let last = parts.pop();
|
||||
let regex = last.match(/^\((.+)\)$/);
|
||||
- let key = regex ? new RegExp(`^(?:${regex[1]})$`, "iv") : last;
|
||||
+ let key = regex ? new RegExp(`^(?:${regex[1]})$`, "i") : last;
|
||||
let requiredModifiers = [];
|
||||
let optionalModifiers = [];
|
||||
for (const part of parts) {
|
||||
@@ -196,5 +208,3 @@ function tinykeys(target, keybindingMap, options = {}) {
|
||||
}
|
||||
//#endregion
|
||||
export { createKeybindingsHandler, defaultKeybindingsHandlerIgnore, matchKeybindingPress, parseKeybinding, tinykeys };
|
||||
-
|
||||
-//# sourceMappingURL=tinykeys.mjs.map
|
||||
\ No newline at end of file
|
||||
@@ -29,7 +29,7 @@ const LICENSE_OVERRIDES = [
|
||||
// type-fest ships two license files (MIT for code, CC0 for types).
|
||||
// We use the MIT license since that covers the bundled code.
|
||||
packageName: "type-fest",
|
||||
version: "5.7.0",
|
||||
version: "5.6.0",
|
||||
licensePath: path.resolve(
|
||||
paths.root_dir,
|
||||
"node_modules/type-fest/license-mit"
|
||||
|
||||
+7
-7
@@ -70,12 +70,12 @@
|
||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||
"@swc/helpers": "0.5.23",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@tsparticles/engine": "4.1.2",
|
||||
"@tsparticles/preset-links": "4.1.2",
|
||||
"@tsparticles/engine": "4.1.1",
|
||||
"@tsparticles/preset-links": "4.1.1",
|
||||
"@vibrant/color": "4.0.4",
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||
"barcode-detector": "3.2.0",
|
||||
"barcode-detector": "3.1.3",
|
||||
"cally": "0.9.2",
|
||||
"color-name": "2.1.0",
|
||||
"comlink": "4.4.2",
|
||||
@@ -88,7 +88,7 @@
|
||||
"dialog-polyfill": "0.5.6",
|
||||
"echarts": "6.1.0",
|
||||
"element-internals-polyfill": "3.0.2",
|
||||
"fuse.js": "7.4.1",
|
||||
"fuse.js": "7.4.0",
|
||||
"google-timezones-json": "1.2.0",
|
||||
"gulp-zopfli-green": "7.0.0",
|
||||
"hls.js": "1.6.16",
|
||||
@@ -114,7 +114,7 @@
|
||||
"sortablejs": "patch:sortablejs@npm%3A1.15.6#~/.yarn/patches/sortablejs-npm-1.15.6-3235a8f83b.patch",
|
||||
"stacktrace-js": "2.0.2",
|
||||
"superstruct": "2.0.2",
|
||||
"tinykeys": "patch:tinykeys@npm%3A4.0.0#~/.yarn/patches/tinykeys-npm-4.0.0-a6ca3fd771.patch",
|
||||
"tinykeys": "4.0.0",
|
||||
"weekstart": "2.0.0",
|
||||
"workbox-cacheable-response": "7.4.1",
|
||||
"workbox-core": "7.4.1",
|
||||
@@ -190,11 +190,11 @@
|
||||
"rspack-manifest-plugin": "5.2.1",
|
||||
"serve": "14.2.6",
|
||||
"sinon": "22.0.0",
|
||||
"tar": "7.5.16",
|
||||
"tar": "7.5.15",
|
||||
"terser-webpack-plugin": "5.6.1",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "8.60.1",
|
||||
"typescript-eslint": "8.60.0",
|
||||
"vite-tsconfig-paths": "6.1.1",
|
||||
"vitest": "4.1.8",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import type { HassEntities, HassEntity } from "home-assistant-js-websocket";
|
||||
import { computeStateDomain } from "./compute_state_domain";
|
||||
|
||||
export interface EntityLocation {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
gpsAccuracy?: number;
|
||||
}
|
||||
|
||||
const findFirstActiveZone = (
|
||||
inZones: readonly string[],
|
||||
states: HassEntities
|
||||
): HassEntity | undefined => {
|
||||
for (const zoneId of inZones) {
|
||||
const zone = states[zoneId];
|
||||
if (
|
||||
zone &&
|
||||
!zone.attributes.passive &&
|
||||
typeof zone.attributes.latitude === "number" &&
|
||||
typeof zone.attributes.longitude === "number"
|
||||
) {
|
||||
return zone;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const getEntityLocation = (
|
||||
stateObj: HassEntity,
|
||||
states: HassEntities
|
||||
): EntityLocation | undefined => {
|
||||
const {
|
||||
latitude,
|
||||
longitude,
|
||||
gps_accuracy: gpsAccuracy,
|
||||
} = stateObj.attributes;
|
||||
if (typeof latitude === "number" && typeof longitude === "number") {
|
||||
return { latitude, longitude, gpsAccuracy };
|
||||
}
|
||||
|
||||
if (computeStateDomain(stateObj) !== "person") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const inZones = stateObj.attributes.in_zones;
|
||||
if (!Array.isArray(inZones) || inZones.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const zone = findFirstActiveZone(inZones, states);
|
||||
if (!zone) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
latitude: zone.attributes.latitude,
|
||||
longitude: zone.attributes.longitude,
|
||||
};
|
||||
};
|
||||
@@ -2,17 +2,12 @@ import { mdiDragHorizontalVariant } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import {
|
||||
fireEvent,
|
||||
type HASSDomCurrentTargetEvent,
|
||||
type HASSDomEvent,
|
||||
} from "../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { isValidEntityId } from "../../common/entity/valid_entity_id";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity/entity";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../ha-sortable";
|
||||
import "./ha-entity-picker";
|
||||
import type { HaEntityPicker } from "./ha-entity-picker";
|
||||
|
||||
@customElement("ha-entities-picker")
|
||||
class HaEntitiesPicker extends LitElement {
|
||||
@@ -156,7 +151,7 @@ class HaEntitiesPicker extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _entityMoved(e: HASSDomEvent<HASSDomEvents["item-moved"]>) {
|
||||
private _entityMoved(e: CustomEvent) {
|
||||
e.stopPropagation();
|
||||
const { oldIndex, newIndex } = e.detail;
|
||||
const currentEntities = this._currentEntities;
|
||||
@@ -183,7 +178,7 @@ class HaEntitiesPicker extends LitElement {
|
||||
return this.value || [];
|
||||
}
|
||||
|
||||
private async _updateEntities(entities: string[]) {
|
||||
private async _updateEntities(entities) {
|
||||
this.value = entities;
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
@@ -191,12 +186,9 @@ class HaEntitiesPicker extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _entityChanged(
|
||||
event: ValueChangedEvent<string> &
|
||||
HASSDomCurrentTargetEvent<HaEntityPicker & { curValue: string }>
|
||||
) {
|
||||
private _entityChanged(event: ValueChangedEvent<string>) {
|
||||
event.stopPropagation();
|
||||
const curValue = event.currentTarget.curValue;
|
||||
const curValue = (event.currentTarget as any).curValue;
|
||||
const newValue = event.detail.value;
|
||||
if (
|
||||
newValue === curValue ||
|
||||
@@ -214,15 +206,13 @@ class HaEntitiesPicker extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private _addEntity(
|
||||
event: ValueChangedEvent<string> & HASSDomCurrentTargetEvent<HaEntityPicker>
|
||||
) {
|
||||
private async _addEntity(event: ValueChangedEvent<string>) {
|
||||
event.stopPropagation();
|
||||
const toAdd = event.detail.value;
|
||||
if (!toAdd) {
|
||||
return;
|
||||
}
|
||||
event.currentTarget.value = "";
|
||||
(event.currentTarget as any).value = "";
|
||||
if (!toAdd) {
|
||||
return;
|
||||
}
|
||||
@@ -249,7 +239,6 @@ class HaEntitiesPicker extends LitElement {
|
||||
}
|
||||
.entity ha-entity-picker {
|
||||
flex: 1;
|
||||
min-width: var(--ha-entities-picker-entity-min-width, auto);
|
||||
}
|
||||
.entity-handle {
|
||||
padding: 8px;
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import type { SelectedDetail } from "@material/mwc-list";
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import { mdiFilterVariantRemove } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { consumeLocalize } from "../common/decorators/consume-context-entry";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import { internationalizationContext, manifestsContext } from "../data/context";
|
||||
import type { IntegrationManifest } from "../data/integration";
|
||||
import { domainToName } from "../data/integration";
|
||||
import { domainToName, fetchIntegrationManifests } from "../data/integration";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-check-list-item";
|
||||
import "./ha-domain-icon";
|
||||
import "./ha-expansion-panel";
|
||||
@@ -23,26 +21,15 @@ import type { HaInputSearch } from "./input/ha-input-search";
|
||||
|
||||
@customElement("ha-filter-integrations")
|
||||
export class HaFilterIntegrations extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public value?: string[];
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public expanded = false;
|
||||
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n!: ContextType<typeof internationalizationContext>;
|
||||
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
|
||||
@consume({ context: manifestsContext, subscribe: true })
|
||||
@state()
|
||||
private _manifests?: ContextType<typeof manifestsContext>;
|
||||
|
||||
private _manifestList = memoizeOne(
|
||||
(manifests: ContextType<typeof manifestsContext>) =>
|
||||
Object.values(manifests)
|
||||
);
|
||||
@state() private _manifests?: IntegrationManifest[];
|
||||
|
||||
@state() private _shouldRender = false;
|
||||
|
||||
@@ -51,10 +38,6 @@ export class HaFilterIntegrations extends LitElement {
|
||||
@query("ha-list") private _list?: HTMLElement;
|
||||
|
||||
protected render() {
|
||||
const manifests = this._manifests
|
||||
? this._manifestList(this._manifests)
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
<ha-expansion-panel
|
||||
left-chevron
|
||||
@@ -63,7 +46,7 @@ export class HaFilterIntegrations extends LitElement {
|
||||
@expanded-changed=${this._expandedChanged}
|
||||
>
|
||||
<div slot="header" class="header">
|
||||
${this._localize("ui.panel.config.integrations.caption")}
|
||||
${this.hass.localize("ui.panel.config.integrations.caption")}
|
||||
${this.value?.length
|
||||
? html`<div class="badge">${this.value?.length}</div>
|
||||
<ha-icon-button
|
||||
@@ -72,7 +55,7 @@ export class HaFilterIntegrations extends LitElement {
|
||||
></ha-icon-button>`
|
||||
: nothing}
|
||||
</div>
|
||||
${manifests && this._shouldRender
|
||||
${this._manifests && this._shouldRender
|
||||
? html`<ha-input-search
|
||||
appearance="outlined"
|
||||
.value=${this._filter}
|
||||
@@ -86,11 +69,10 @@ export class HaFilterIntegrations extends LitElement {
|
||||
>
|
||||
${repeat(
|
||||
this._integrations(
|
||||
this._localize,
|
||||
manifests,
|
||||
this.hass.localize,
|
||||
this._manifests,
|
||||
this._filter,
|
||||
this.value,
|
||||
this._i18n.locale.language
|
||||
this.value
|
||||
),
|
||||
(i) => i.domain,
|
||||
(integration) =>
|
||||
@@ -135,8 +117,9 @@ export class HaFilterIntegrations extends LitElement {
|
||||
this.expanded = ev.detail.expanded;
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
this._i18n.loadBackendTranslation("title");
|
||||
protected async firstUpdated() {
|
||||
this._manifests = await fetchIntegrationManifests(this.hass);
|
||||
this.hass.loadBackendTranslation("title");
|
||||
}
|
||||
|
||||
private _integrations = memoizeOne(
|
||||
@@ -144,8 +127,7 @@ export class HaFilterIntegrations extends LitElement {
|
||||
localize: LocalizeFunc,
|
||||
manifest: IntegrationManifest[],
|
||||
filter: string | undefined,
|
||||
_value: string[] | undefined,
|
||||
language: string
|
||||
_value
|
||||
) =>
|
||||
manifest
|
||||
.map((mnfst) => ({
|
||||
@@ -162,20 +144,17 @@ export class HaFilterIntegrations extends LitElement {
|
||||
mnfst.name.toLowerCase().includes(filter) ||
|
||||
mnfst.domain.toLowerCase().includes(filter))
|
||||
)
|
||||
.sort((a, b) => stringCompare(a.name, b.name, language))
|
||||
.sort((a, b) =>
|
||||
stringCompare(a.name, b.name, this.hass.locale.language)
|
||||
)
|
||||
);
|
||||
|
||||
private _itemSelected(ev: CustomEvent<SelectedDetail<Set<number>>>) {
|
||||
if (!this._manifests) {
|
||||
return;
|
||||
}
|
||||
|
||||
const integrations = this._integrations(
|
||||
this._localize,
|
||||
this._manifestList(this._manifests),
|
||||
this.hass.localize,
|
||||
this._manifests!,
|
||||
this._filter,
|
||||
this.value,
|
||||
this._i18n.locale.language
|
||||
this.value
|
||||
);
|
||||
|
||||
const visibleDomains = new Set(integrations.map((i) => i.domain));
|
||||
|
||||
@@ -9,11 +9,7 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../common/array/ensure-array";
|
||||
import {
|
||||
fireEvent,
|
||||
type HASSDomCurrentTargetEvent,
|
||||
type HASSDomEvent,
|
||||
} from "../common/dom/fire_event";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeObjectId } from "../common/entity/compute_object_id";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
@@ -36,11 +32,9 @@ import {
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import { documentationUrl } from "../util/documentation-url";
|
||||
import "./ha-checkbox";
|
||||
import type { HaCheckbox } from "./ha-checkbox";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-markdown";
|
||||
import "./ha-selector/ha-selector";
|
||||
import type { HaSelector } from "./ha-selector/ha-selector";
|
||||
import "./ha-service-picker";
|
||||
import "./ha-service-section-icon";
|
||||
import "./ha-settings-row";
|
||||
@@ -62,22 +56,6 @@ const showOptionalToggle = (field) =>
|
||||
!field.required &&
|
||||
!("boolean" in field.selector && field.default);
|
||||
|
||||
const FULL_WIDTH_SELECTOR_TYPES = new Set([
|
||||
"action",
|
||||
"area",
|
||||
"device",
|
||||
"entity",
|
||||
"floor",
|
||||
"label",
|
||||
"target",
|
||||
]);
|
||||
|
||||
const isFullWidthSelector = (selector?: Selector): boolean =>
|
||||
!!selector &&
|
||||
Object.keys(selector).some((selectorType) =>
|
||||
FULL_WIDTH_SELECTOR_TYPES.has(selectorType)
|
||||
);
|
||||
|
||||
interface Field extends Omit<HassService["fields"][string], "selector"> {
|
||||
key: string;
|
||||
selector?: Selector;
|
||||
@@ -497,14 +475,6 @@ export class HaServiceControl extends LitElement {
|
||||
)) ||
|
||||
serviceData?.description;
|
||||
|
||||
const targetSelector =
|
||||
serviceData && "target" in serviceData
|
||||
? this._targetSelector(
|
||||
serviceData.target as TargetSelector,
|
||||
this._value?.target
|
||||
)
|
||||
: undefined;
|
||||
|
||||
return html`${this.hidePicker
|
||||
? nothing
|
||||
: html`<ha-service-picker
|
||||
@@ -542,15 +512,16 @@ export class HaServiceControl extends LitElement {
|
||||
</div>
|
||||
`}
|
||||
${serviceData && "target" in serviceData
|
||||
? html`<ha-settings-row
|
||||
.narrow=${this.narrow || isFullWidthSelector(targetSelector)}
|
||||
>
|
||||
? html`<ha-settings-row .narrow=${this.narrow}>
|
||||
<span slot="heading"
|
||||
>${this.hass.localize("ui.components.service-control.target")}</span
|
||||
>
|
||||
<ha-selector
|
||||
.hass=${this.hass}
|
||||
.selector=${targetSelector}
|
||||
.selector=${this._targetSelector(
|
||||
serviceData.target as TargetSelector,
|
||||
this._value?.target
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._targetChanged}
|
||||
.value=${this._value?.target}
|
||||
@@ -693,9 +664,7 @@ export class HaServiceControl extends LitElement {
|
||||
: undefined;
|
||||
|
||||
return dataField.selector
|
||||
? html`<ha-settings-row
|
||||
.narrow=${this.narrow || isFullWidthSelector(selector)}
|
||||
>
|
||||
? html`<ha-settings-row .narrow=${this.narrow}>
|
||||
${!showOptional
|
||||
? hasOptional
|
||||
? html`<div slot="prefix" class="checkbox-spacer"></div>`
|
||||
@@ -768,16 +737,14 @@ export class HaServiceControl extends LitElement {
|
||||
);
|
||||
};
|
||||
|
||||
private _toggleCheckbox(ev: HASSDomCurrentTargetEvent<HTMLElement>) {
|
||||
private _toggleCheckbox(ev: Event) {
|
||||
const checkbox = (
|
||||
ev.currentTarget as HTMLElement
|
||||
)?.parentElement?.querySelector("ha-checkbox");
|
||||
checkbox?.click();
|
||||
}
|
||||
|
||||
private _checkboxChanged(
|
||||
ev: HASSDomCurrentTargetEvent<HaCheckbox & { key: string }>
|
||||
) {
|
||||
private _checkboxChanged(ev) {
|
||||
const checked = ev.currentTarget.checked;
|
||||
const key = ev.currentTarget.key;
|
||||
let data;
|
||||
@@ -900,7 +867,7 @@ export class HaServiceControl extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _entityPicked(ev: HASSDomEvent<{ value: string | undefined }>) {
|
||||
private _entityPicked(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const newValue = ev.detail.value;
|
||||
if (this._value?.data?.entity_id === newValue) {
|
||||
@@ -921,12 +888,7 @@ export class HaServiceControl extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _targetChanged(
|
||||
ev: HASSDomEvent<{
|
||||
value: HassServiceTarget | undefined;
|
||||
isValid?: boolean;
|
||||
}>
|
||||
) {
|
||||
private _targetChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
if (ev.detail.isValid === false) {
|
||||
// Don't clear an object selector that returns invalid YAML
|
||||
@@ -948,16 +910,13 @@ export class HaServiceControl extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _serviceDataChanged(
|
||||
ev: HASSDomEvent<{ value: unknown; isValid?: boolean }> &
|
||||
HASSDomCurrentTargetEvent<HaSelector & { key: string }>
|
||||
) {
|
||||
private _serviceDataChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
if (ev.detail.isValid === false) {
|
||||
// Don't clear an object selector that returns invalid YAML
|
||||
return;
|
||||
}
|
||||
const key = ev.currentTarget.key;
|
||||
const key = (ev.currentTarget as any).key;
|
||||
const value = ev.detail.value;
|
||||
if (
|
||||
this._value?.data?.[key] === value ||
|
||||
@@ -972,9 +931,7 @@ export class HaServiceControl extends LitElement {
|
||||
if (
|
||||
value === "" ||
|
||||
value === undefined ||
|
||||
(value !== null &&
|
||||
typeof value === "object" &&
|
||||
!Object.keys(value).length)
|
||||
(typeof value === "object" && !Object.keys(value).length)
|
||||
) {
|
||||
delete data[key];
|
||||
delete this._stickySelector[key];
|
||||
@@ -988,12 +945,7 @@ export class HaServiceControl extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _dataChanged(
|
||||
ev: HASSDomEvent<{
|
||||
value: NonNullable<HaServiceControl["value"]>["data"];
|
||||
isValid?: boolean;
|
||||
}>
|
||||
) {
|
||||
private _dataChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
if (!ev.detail.isValid) {
|
||||
return;
|
||||
@@ -1017,15 +969,14 @@ export class HaServiceControl extends LitElement {
|
||||
|
||||
static styles = css`
|
||||
ha-settings-row {
|
||||
padding: var(--service-control-padding, 0 var(--ha-space-4));
|
||||
padding: var(--service-control-padding, 0 16px);
|
||||
}
|
||||
ha-settings-row[narrow] {
|
||||
padding-bottom: var(--ha-space-2);
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
ha-settings-row {
|
||||
--settings-row-content-width: 100%;
|
||||
--settings-row-prefix-display: contents;
|
||||
--ha-entities-picker-entity-min-width: 0;
|
||||
border-top: var(
|
||||
--service-control-items-border-top,
|
||||
1px solid var(--divider-color)
|
||||
@@ -1035,20 +986,20 @@ export class HaServiceControl extends LitElement {
|
||||
ha-entity-picker,
|
||||
ha-yaml-editor {
|
||||
display: block;
|
||||
margin: var(--service-control-padding, 0 var(--ha-space-4));
|
||||
margin: var(--service-control-padding, 0 16px);
|
||||
}
|
||||
ha-yaml-editor {
|
||||
padding: var(--ha-space-4) 0;
|
||||
padding: 16px 0;
|
||||
}
|
||||
p {
|
||||
margin: var(--service-control-padding, 0 var(--ha-space-4));
|
||||
padding: var(--ha-space-4) 0;
|
||||
margin: var(--service-control-padding, 0 16px);
|
||||
padding: 16px 0;
|
||||
}
|
||||
:host([hide-picker]) p {
|
||||
padding-top: 0;
|
||||
}
|
||||
.checkbox-spacer {
|
||||
width: var(--ha-space-8);
|
||||
width: 32px;
|
||||
}
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
@@ -1069,7 +1020,7 @@ export class HaServiceControl extends LitElement {
|
||||
}
|
||||
ha-expansion-panel {
|
||||
--ha-card-border-radius: var(--ha-border-radius-square);
|
||||
--expansion-panel-summary-padding: 0 var(--ha-space-4);
|
||||
--expansion-panel-summary-padding: 0 16px;
|
||||
--expansion-panel-content-padding: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -23,7 +23,6 @@ import type { LeafletModuleType } from "../../common/dom/setup-leaflet-map";
|
||||
import { setupLeafletMap } from "../../common/dom/setup-leaflet-map";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { getEntityLocation } from "../../common/entity/get_entity_location";
|
||||
import { DecoratedMarker } from "../../common/map/decorated_marker";
|
||||
import { filterXSS } from "../../common/util/xss";
|
||||
import type { HomeAssistant, ThemeMode } from "../../types";
|
||||
@@ -585,17 +584,18 @@ export class HaMap extends ReactiveElement {
|
||||
const customTitle = typeof entity !== "string" ? entity.name : undefined;
|
||||
const title = customTitle ?? computeStateName(stateObj);
|
||||
const {
|
||||
latitude,
|
||||
longitude,
|
||||
passive,
|
||||
icon,
|
||||
radius,
|
||||
entity_picture: entityPicture,
|
||||
gps_accuracy: gpsAccuracy,
|
||||
} = stateObj.attributes;
|
||||
|
||||
const location = getEntityLocation(stateObj, hass.states);
|
||||
if (!location) {
|
||||
if (!(latitude && longitude)) {
|
||||
continue;
|
||||
}
|
||||
const { latitude, longitude, gpsAccuracy } = location;
|
||||
|
||||
if (computeStateDomain(stateObj) === "zone") {
|
||||
// DRAW ZONE
|
||||
|
||||
@@ -26,13 +26,6 @@ export interface HomeFrontendSystemData {
|
||||
shortcuts?: ShortcutItem[];
|
||||
}
|
||||
|
||||
export interface EnergyFrontendSystemData {
|
||||
// Stable "<view>.<card-type>" keys of energy dashboard cards the user has
|
||||
// hidden. An absent key or array means nothing is hidden (all cards visible),
|
||||
// so cards added in the future are shown by default.
|
||||
hidden_cards?: string[];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface FrontendUserData {
|
||||
core: CoreFrontendUserData;
|
||||
@@ -41,7 +34,6 @@ declare global {
|
||||
interface FrontendSystemData {
|
||||
core: CoreFrontendSystemData;
|
||||
home: HomeFrontendSystemData;
|
||||
energy: EnergyFrontendSystemData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { getEntityLocation } from "../../../common/entity/get_entity_location";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/map/ha-map";
|
||||
import { showZoneEditor } from "../../../data/zone";
|
||||
@@ -22,13 +21,8 @@ class MoreInfoPerson extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const location = getEntityLocation(this.stateObj, this.hass.states);
|
||||
const hasOwnCoordinates =
|
||||
typeof this.stateObj.attributes.latitude === "number" &&
|
||||
typeof this.stateObj.attributes.longitude === "number";
|
||||
|
||||
return html`
|
||||
${location
|
||||
${this.stateObj.attributes.latitude && this.stateObj.attributes.longitude
|
||||
? html`
|
||||
<ha-map
|
||||
.hass=${this.hass}
|
||||
@@ -37,7 +31,10 @@ class MoreInfoPerson extends LitElement {
|
||||
></ha-map>
|
||||
`
|
||||
: ""}
|
||||
${!__DEMO__ && this.hass.user?.is_admin && hasOwnCoordinates
|
||||
${!__DEMO__ &&
|
||||
this.hass.user?.is_admin &&
|
||||
this.stateObj.attributes.latitude &&
|
||||
this.stateObj.attributes.longitude
|
||||
? html`
|
||||
<div class="actions">
|
||||
<ha-button
|
||||
|
||||
@@ -5,6 +5,7 @@ in core bundle slows things down and causes duplicate registration.
|
||||
This is the entry point for providing external app stuff from app entrypoint.
|
||||
*/
|
||||
|
||||
import type { HASSDomEvent } from "../common/dom/fire_event";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { mainWindow } from "../common/dom/get_main_window";
|
||||
import { navigate } from "../common/navigate";
|
||||
@@ -15,6 +16,7 @@ import type {
|
||||
EMIncomingMessageBarCodeScanResult,
|
||||
EMIncomingMessageCommands,
|
||||
ImprovDiscoveredDevice,
|
||||
MatterCommissionFinish,
|
||||
} from "./external_messaging";
|
||||
|
||||
const barCodeListeners = new Set<
|
||||
@@ -91,6 +93,8 @@ export const handleExternalMessage = (
|
||||
fireEvent(window, "improv-discovered-device", msg.payload);
|
||||
} else if (msg.command === "improv/device_setup_done") {
|
||||
fireEvent(window, "improv-device-setup-done");
|
||||
} else if (msg.command === "matter/commission/finish") {
|
||||
fireEvent(window, "matter-commission-finish", msg.payload);
|
||||
} else if (msg.command === "bar_code/scan_result") {
|
||||
barCodeListeners.forEach((listener) => listener(msg));
|
||||
} else if (msg.command === "bar_code/aborted") {
|
||||
@@ -115,5 +119,10 @@ declare global {
|
||||
interface HASSDomEvents {
|
||||
"improv-discovered-device": ImprovDiscoveredDevice;
|
||||
"improv-device-setup-done": undefined;
|
||||
"matter-commission-finish": MatterCommissionFinish;
|
||||
}
|
||||
|
||||
interface GlobalEventHandlersEventMap {
|
||||
"matter-commission-finish": HASSDomEvent<MatterCommissionFinish>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,6 +320,18 @@ export interface EMIncomingMessageKioskModeSet {
|
||||
};
|
||||
}
|
||||
|
||||
export interface MatterCommissionFinish {
|
||||
name: string | null;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface EMIncomingMessageMatterCommissionFinish extends EMMessage {
|
||||
id: number;
|
||||
type: "command";
|
||||
command: "matter/commission/finish";
|
||||
payload: MatterCommissionFinish;
|
||||
}
|
||||
|
||||
export type EMIncomingMessageCommands =
|
||||
| EMIncomingMessageRestart
|
||||
| EMIncomingMessageNavigate
|
||||
@@ -331,6 +343,7 @@ export type EMIncomingMessageCommands =
|
||||
| EMIncomingMessageBarCodeScanAborted
|
||||
| EMIncomingMessageImprovDeviceDiscovered
|
||||
| EMIncomingMessageImprovDeviceSetupDone
|
||||
| EMIncomingMessageMatterCommissionFinish
|
||||
| EMIncomingMessageKioskModeSet;
|
||||
|
||||
type EMIncomingMessage =
|
||||
@@ -346,6 +359,7 @@ export interface ExternalConfig {
|
||||
canWriteTag?: boolean;
|
||||
hasExoPlayer?: boolean;
|
||||
canCommissionMatter?: boolean;
|
||||
hasMatterStatusReport?: boolean;
|
||||
canImportThreadCredentials?: boolean;
|
||||
canTransferThreadCredentialsToKeychain?: boolean;
|
||||
hasAssist?: boolean;
|
||||
|
||||
@@ -232,6 +232,7 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
: nothing}
|
||||
</div>
|
||||
<ha-config-updates
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.updateEntities=${group.entities}
|
||||
showAll
|
||||
@@ -255,6 +256,7 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
<ha-config-updates
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.updateEntities=${skippedUpdates}
|
||||
showAll
|
||||
@@ -278,6 +280,7 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
<ha-config-updates
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.updateEntities=${notInstallableUpdates}
|
||||
showAll
|
||||
|
||||
@@ -326,6 +326,7 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
</a>
|
||||
</div>
|
||||
<ha-config-updates
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.updateEntities=${canInstallUpdates}
|
||||
></ha-config-updates>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { consumeLocalize } from "../../../common/decorators/consume-context-entry";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeDeviceNameDisplay } from "../../../common/entity/compute_device_name";
|
||||
import { getDeviceArea } from "../../../common/entity/context/get_device_context";
|
||||
@@ -14,51 +14,62 @@ import "../../../components/ha-spinner";
|
||||
import "../../../components/item/ha-list-item-button";
|
||||
import "../../../components/list/ha-list-base";
|
||||
import "../../../components/progress/ha-progress-ring";
|
||||
import {
|
||||
areasContext,
|
||||
devicesContext,
|
||||
fullEntitiesContext,
|
||||
statesContext,
|
||||
} from "../../../data/context";
|
||||
import { entityRegistryByEntityId } from "../../../data/entity/entity_registry";
|
||||
import type { DeviceRegistryEntry } from "../../../data/device/device_registry";
|
||||
import { subscribeDeviceRegistry } from "../../../data/device/device_registry";
|
||||
import type { EntityRegistryEntry } from "../../../data/entity/entity_registry";
|
||||
import { subscribeEntityRegistry } from "../../../data/entity/entity_registry";
|
||||
import type { UpdateEntity } from "../../../data/update";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
@customElement("ha-config-updates")
|
||||
class HaConfigUpdates extends LitElement {
|
||||
class HaConfigUpdates extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: false }) public updateEntities?: UpdateEntity[];
|
||||
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
@state() private _devices?: DeviceRegistryEntry[];
|
||||
|
||||
@consume({ context: statesContext, subscribe: true })
|
||||
private _states!: ContextType<typeof statesContext>;
|
||||
@state() private _entities?: EntityRegistryEntry[];
|
||||
|
||||
@consume({ context: devicesContext, subscribe: true })
|
||||
private _devices!: ContextType<typeof devicesContext>;
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeDeviceRegistry(this.hass.connection, (entries) => {
|
||||
this._devices = entries;
|
||||
}),
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entities = entities.filter((entity) => entity.device_id !== null);
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@consume({ context: areasContext, subscribe: true })
|
||||
private _areas!: ContextType<typeof areasContext>;
|
||||
private getDeviceEntry = memoizeOne(
|
||||
(deviceId: string): DeviceRegistryEntry | undefined =>
|
||||
this._devices?.find((device) => device.id === deviceId)
|
||||
);
|
||||
|
||||
@state()
|
||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||
private _entities: ContextType<typeof fullEntitiesContext> = [];
|
||||
private getEntityEntry = memoizeOne(
|
||||
(entityId: string): EntityRegistryEntry | undefined =>
|
||||
this._entities?.find((entity) => entity.entity_id === entityId)
|
||||
);
|
||||
|
||||
private _renderUpdateProgress(entity: UpdateEntity) {
|
||||
if (entity.attributes.update_percentage != null) {
|
||||
return html`<ha-progress-ring
|
||||
size="small"
|
||||
.value=${entity.attributes.update_percentage}
|
||||
.label=${this._localize("ui.panel.config.updates.update_in_progress")}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.updates.update_in_progress"
|
||||
)}
|
||||
></ha-progress-ring>`;
|
||||
}
|
||||
|
||||
if (entity.attributes.in_progress) {
|
||||
return html`<ha-spinner
|
||||
size="small"
|
||||
.ariaLabel=${this._localize(
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.updates.update_in_progress"
|
||||
)}
|
||||
></ha-spinner>`;
|
||||
@@ -73,23 +84,22 @@ class HaConfigUpdates extends LitElement {
|
||||
}
|
||||
|
||||
const updates = this.updateEntities;
|
||||
const entities = entityRegistryByEntityId(this._entities);
|
||||
|
||||
return html`
|
||||
<ha-list-base
|
||||
aria-label=${this._localize("ui.panel.config.updates.caption")}
|
||||
aria-label=${this.hass.localize("ui.panel.config.updates.caption")}
|
||||
>
|
||||
${updates.map((entity) => {
|
||||
const entityEntry = entities[entity.entity_id];
|
||||
const entityEntry = this.getEntityEntry(entity.entity_id);
|
||||
const deviceEntry =
|
||||
entityEntry && entityEntry.device_id
|
||||
? this._devices[entityEntry.device_id]
|
||||
? this.getDeviceEntry(entityEntry.device_id)
|
||||
: undefined;
|
||||
|
||||
const areaName =
|
||||
deviceEntry && deviceEntry.entry_type !== "service"
|
||||
? getDeviceArea(deviceEntry, this._areas)?.name ||
|
||||
this._localize("ui.panel.config.updates.no_area")
|
||||
? getDeviceArea(deviceEntry, this.hass.areas)?.name ||
|
||||
this.hass.localize("ui.panel.config.updates.no_area")
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
@@ -102,6 +112,7 @@ class HaConfigUpdates extends LitElement {
|
||||
<state-badge
|
||||
.title=${entity.attributes.title ||
|
||||
entity.attributes.friendly_name}
|
||||
.hass=${this.hass}
|
||||
.stateObj=${entity}
|
||||
class=${ifDefined(
|
||||
this.narrow && entity.attributes.in_progress
|
||||
@@ -119,8 +130,8 @@ class HaConfigUpdates extends LitElement {
|
||||
>${deviceEntry
|
||||
? computeDeviceNameDisplay(
|
||||
deviceEntry,
|
||||
this._localize,
|
||||
this._states
|
||||
this.hass.localize,
|
||||
this.hass.states
|
||||
)
|
||||
: entity.attributes.friendly_name}</span
|
||||
>
|
||||
@@ -128,7 +139,7 @@ class HaConfigUpdates extends LitElement {
|
||||
${areaName ? html`${areaName} ⸱ ` : nothing}
|
||||
${entity.attributes.title} ${entity.attributes.latest_version}
|
||||
${entity.attributes.skipped_version
|
||||
? `(${this._localize("ui.panel.config.updates.skipped")})`
|
||||
? `(${this.hass.localize("ui.panel.config.updates.skipped")})`
|
||||
: nothing}
|
||||
</span>
|
||||
${!this.narrow
|
||||
|
||||
@@ -846,6 +846,7 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
@expanded-changed=${this._filterExpanded}
|
||||
></ha-filter-floor-areas>
|
||||
<ha-filter-integrations
|
||||
.hass=${this.hass}
|
||||
.value=${this._filters["ha-filter-integrations"]?.value}
|
||||
@data-table-filter-changed=${this._filterChanged}
|
||||
slot="filter-pane"
|
||||
|
||||
@@ -1,288 +0,0 @@
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeKeys } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-dialog";
|
||||
import "../../../../components/ha-dialog-footer";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-settings-row";
|
||||
import "../../../../components/ha-spinner";
|
||||
import "../../../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-tooltip";
|
||||
import {
|
||||
connectionContext,
|
||||
internationalizationContext,
|
||||
} from "../../../../data/context";
|
||||
import {
|
||||
fetchFrontendSystemData,
|
||||
saveFrontendSystemData,
|
||||
} from "../../../../data/frontend";
|
||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
import { showToast } from "../../../../util/toast";
|
||||
import type {
|
||||
EnergyCardCatalogEntry,
|
||||
EnergyViewPath,
|
||||
} from "../../../energy/strategies/energy-cards";
|
||||
import { ENERGY_CARD_CATALOG } from "../../../energy/strategies/energy-cards";
|
||||
import type { EnergyCustomiseDialogParams } from "./show-dialog-energy-customise";
|
||||
|
||||
const VIEW_GROUPS: { view: EnergyViewPath; labelKey: LocalizeKeys }[] = [
|
||||
{
|
||||
view: "overview",
|
||||
labelKey: "ui.panel.config.energy.customise.groups.overview",
|
||||
},
|
||||
{ view: "electricity", labelKey: "ui.panel.config.energy.tabs.electricity" },
|
||||
{ view: "gas", labelKey: "ui.panel.config.energy.tabs.gas" },
|
||||
{ view: "water", labelKey: "ui.panel.config.energy.tabs.water" },
|
||||
{ view: "now", labelKey: "ui.panel.config.energy.customise.groups.now" },
|
||||
];
|
||||
|
||||
@customElement("dialog-energy-customise")
|
||||
export class DialogEnergyCustomise
|
||||
extends LitElement
|
||||
implements HassDialog<EnergyCustomiseDialogParams>
|
||||
{
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n!: ContextType<typeof internationalizationContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: connectionContext, subscribe: true })
|
||||
private _connection!: ContextType<typeof connectionContext>;
|
||||
|
||||
@state() private _params?: EnergyCustomiseDialogParams;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
// Working copy of the hidden card keys. A switch that is ON means the card is
|
||||
// visible, i.e. its key is NOT in this set.
|
||||
@state() private _hidden?: Set<string>;
|
||||
|
||||
public showDialog(params: EnergyCustomiseDialogParams): void {
|
||||
this._params = params;
|
||||
this._open = true;
|
||||
this._loadHidden();
|
||||
}
|
||||
|
||||
public closeDialog(): boolean {
|
||||
this._open = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this._params = undefined;
|
||||
this._hidden = undefined;
|
||||
this._error = undefined;
|
||||
this._submitting = false;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
private async _loadHidden(): Promise<void> {
|
||||
this._error = undefined;
|
||||
// showDialog runs before the dialog is connected to the DOM, so wait for
|
||||
// the first update to ensure the consumed contexts have resolved.
|
||||
await this.updateComplete;
|
||||
if (!this._params) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// The card labels reuse keys from the "energy" translation fragment,
|
||||
// which is not guaranteed to be loaded on the config page.
|
||||
const [data] = await Promise.all([
|
||||
fetchFrontendSystemData(this._connection.connection, "energy"),
|
||||
this._i18n.loadFragmentTranslation("energy"),
|
||||
]);
|
||||
this._hidden = new Set(data?.hidden_cards ?? []);
|
||||
} catch (err: any) {
|
||||
this._error = err?.message || "Unknown error";
|
||||
this._hidden = new Set();
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
.headerTitle=${this._i18n.localize(
|
||||
"ui.panel.config.energy.customise.title"
|
||||
)}
|
||||
prevent-scrim-close
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
${!this._hidden
|
||||
? html`<div class="loading">
|
||||
<ha-spinner size="large"></ha-spinner>
|
||||
</div>`
|
||||
: this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: html`<div class="groups">${this._renderGroups()}</div>`}
|
||||
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="secondaryAction"
|
||||
@click=${this.closeDialog}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this._i18n.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._save}
|
||||
.disabled=${this._submitting || !this._hidden || !!this._error}
|
||||
>
|
||||
${this._i18n.localize("ui.common.save")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderGroups() {
|
||||
const prefs = this._params!.preferences;
|
||||
return VIEW_GROUPS.map((group) => {
|
||||
const cards = ENERGY_CARD_CATALOG.filter((c) => c.view === group.view);
|
||||
// Hide the whole group when none of its cards apply to the current config.
|
||||
if (!cards.some((c) => c.isApplicable(prefs))) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<ha-expansion-panel
|
||||
outlined
|
||||
expanded
|
||||
.header=${this._i18n.localize(group.labelKey)}
|
||||
>
|
||||
<div class="cards">
|
||||
${cards.map((card) => this._renderCardRow(card))}
|
||||
</div>
|
||||
</ha-expansion-panel>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
private _renderCardRow(card: EnergyCardCatalogEntry) {
|
||||
const applicable = card.isApplicable(this._params!.preferences);
|
||||
const label = this._i18n.localize(card.labelKey);
|
||||
const rowId = `row-${card.key}`;
|
||||
return html`
|
||||
<ha-settings-row slim id=${rowId}>
|
||||
<span slot="heading" class=${applicable ? "" : "disabled"}
|
||||
>${label}</span
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${applicable && !this._hidden!.has(card.key)}
|
||||
.disabled=${!applicable}
|
||||
.ariaLabel=${label}
|
||||
data-card-key=${card.key}
|
||||
@change=${this._toggleCard}
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
${applicable
|
||||
? nothing
|
||||
: html`
|
||||
<ha-tooltip .for=${rowId} placement="top">
|
||||
${this._i18n.localize(
|
||||
"ui.panel.config.energy.customise.unavailable"
|
||||
)}
|
||||
</ha-tooltip>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
|
||||
private _toggleCard = (ev: Event): void => {
|
||||
const target = ev.currentTarget as HaSwitch;
|
||||
const cardKey = target.dataset.cardKey;
|
||||
if (!cardKey) {
|
||||
return;
|
||||
}
|
||||
const next = new Set(this._hidden);
|
||||
if (target.checked) {
|
||||
next.delete(cardKey);
|
||||
} else {
|
||||
next.add(cardKey);
|
||||
}
|
||||
this._hidden = next;
|
||||
};
|
||||
|
||||
private async _save(): Promise<void> {
|
||||
if (!this._hidden) {
|
||||
return;
|
||||
}
|
||||
this._submitting = true;
|
||||
try {
|
||||
const hidden = Array.from(this._hidden);
|
||||
await saveFrontendSystemData(this._connection.connection, "energy", {
|
||||
hidden_cards: hidden.length ? hidden : undefined,
|
||||
});
|
||||
this._params?.saveCallback?.();
|
||||
this.closeDialog();
|
||||
} catch (_err) {
|
||||
showToast(this, {
|
||||
message: this._i18n.localize(
|
||||
"ui.panel.config.energy.customise.save_failed"
|
||||
),
|
||||
duration: 0,
|
||||
dismissable: true,
|
||||
});
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog {
|
||||
--dialog-content-padding: var(--ha-space-2) var(--ha-space-6);
|
||||
}
|
||||
.loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: var(--ha-space-6);
|
||||
}
|
||||
span.disabled {
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
.groups {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--ha-space-4);
|
||||
}
|
||||
ha-expansion-panel {
|
||||
display: block;
|
||||
--expansion-panel-content-padding: 0;
|
||||
--expansion-panel-summary-padding: 0 var(--ha-space-4);
|
||||
border-radius: var(--ha-border-radius-md);
|
||||
--ha-card-border-radius: var(--ha-border-radius-md);
|
||||
}
|
||||
.cards {
|
||||
padding: 0 var(--ha-space-4);
|
||||
}
|
||||
.cards ha-settings-row {
|
||||
min-height: 48px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-energy-customise": DialogEnergyCustomise;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { EnergyPreferences } from "../../../../data/energy";
|
||||
|
||||
export interface EnergyCustomiseDialogParams {
|
||||
preferences: EnergyPreferences;
|
||||
// Called after a successful save (e.g. to show a toast on the page).
|
||||
saveCallback?: () => void;
|
||||
}
|
||||
|
||||
export const loadEnergyCustomiseDialog = () =>
|
||||
import("./dialog-energy-customise");
|
||||
|
||||
export const showEnergyCustomiseDialog = (
|
||||
element: HTMLElement,
|
||||
params: EnergyCustomiseDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-energy-customise",
|
||||
dialogImport: loadEnergyCustomiseDialog,
|
||||
dialogParams: params,
|
||||
});
|
||||
};
|
||||
@@ -1,11 +1,5 @@
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import {
|
||||
mdiDownload,
|
||||
mdiFire,
|
||||
mdiLightningBolt,
|
||||
mdiViewDashboardEdit,
|
||||
mdiWater,
|
||||
} from "@mdi/js";
|
||||
import { mdiDownload, mdiFire, mdiLightningBolt, mdiWater } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@@ -37,9 +31,7 @@ import "./components/ha-energy-battery-settings";
|
||||
import "./components/ha-energy-gas-settings";
|
||||
import "./components/ha-energy-water-settings";
|
||||
import { fileDownload } from "../../../util/file_download";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import type { PageNavigation } from "../../../layouts/hass-tabs-subpage";
|
||||
import { showEnergyCustomiseDialog } from "./dialogs/show-dialog-energy-customise";
|
||||
|
||||
const INITIAL_CONFIG: EnergyPreferences = {
|
||||
energy_sources: [],
|
||||
@@ -132,22 +124,14 @@ class HaConfigEnergy extends LitElement {
|
||||
.route=${this.route}
|
||||
.tabs=${TABS}
|
||||
>
|
||||
<div slot="toolbar-icon" class="toolbar-icons">
|
||||
<ha-icon-button
|
||||
.path=${mdiViewDashboardEdit}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.energy.customise.toolbar_action"
|
||||
)}
|
||||
@click=${this._customise}
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
.path=${mdiDownload}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.devices.download_diagnostics"
|
||||
)}
|
||||
@click=${this._downloadDiagnostics}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
.path=${mdiDownload}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.devices.download_diagnostics"
|
||||
)}
|
||||
@click=${this._downloadDiagnostics}
|
||||
></ha-icon-button>
|
||||
<ha-alert>
|
||||
${this.hass.localize("ui.panel.config.energy.new_device_info")}
|
||||
</ha-alert>
|
||||
@@ -270,20 +254,6 @@ class HaConfigEnergy extends LitElement {
|
||||
this._statsMetadata = statsMetadata;
|
||||
}
|
||||
|
||||
private _customise() {
|
||||
if (!this._preferences) {
|
||||
return;
|
||||
}
|
||||
showEnergyCustomiseDialog(this, {
|
||||
preferences: this._preferences,
|
||||
saveCallback: () => {
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.panel.config.energy.customise.saved"),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async _downloadDiagnostics() {
|
||||
const data = {
|
||||
version: this.hass.config.version,
|
||||
@@ -315,10 +285,6 @@ class HaConfigEnergy extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.toolbar-icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.content {
|
||||
padding: 0 var(--ha-space-5);
|
||||
max-width: 1040px;
|
||||
|
||||
@@ -1014,6 +1014,7 @@ export class HaConfigEntities extends LitElement {
|
||||
@expanded-changed=${this._filterExpanded}
|
||||
></ha-filter-domains>
|
||||
<ha-filter-integrations
|
||||
.hass=${this.hass}
|
||||
.value=${this._filters["ha-filter-integrations"]}
|
||||
@data-table-filter-changed=${this._filterChanged}
|
||||
slot="filter-pane"
|
||||
|
||||
+62
-1
@@ -2,6 +2,7 @@ import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { dynamicElement } from "../../../../../common/dom/dynamic-element-directive";
|
||||
import type { HASSDomEvent } from "../../../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../../../common/entity/compute_domain";
|
||||
import { computeDeviceName } from "../../../../../common/entity/compute_device_name";
|
||||
@@ -10,6 +11,7 @@ import "../../../../../components/ha-dialog-footer";
|
||||
import "../../../../../components/ha-icon-button-arrow-prev";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-dialog";
|
||||
import type { MatterCommissionFinish } from "../../../../../external_app/external_messaging";
|
||||
import {
|
||||
commissionMatterDevice,
|
||||
watchForNewMatterDevice,
|
||||
@@ -82,6 +84,10 @@ class DialogMatterAddDevice extends LitElement {
|
||||
|
||||
@state() private _mainEntity?: ExtEntityRegistryEntry;
|
||||
|
||||
@state() private _proposedDeviceName?: string;
|
||||
|
||||
@state() private _commissioningFinished = false;
|
||||
|
||||
@state() private _deviceAddedState: {
|
||||
name: string;
|
||||
area: string | undefined;
|
||||
@@ -104,15 +110,63 @@ class DialogMatterAddDevice extends LitElement {
|
||||
// make sure a refresh of the page will navigate to the device page, old iOS apps will refresh the webview when commissioning is done
|
||||
setRefreshUrl(`/config/devices/device/${device.id}`);
|
||||
this._newDevice = device;
|
||||
this._step = "device_added";
|
||||
this._fetchMainEntity();
|
||||
this._maybeShowDeviceAdded();
|
||||
});
|
||||
if (this._waitForCommissioningFinish) {
|
||||
window.addEventListener(
|
||||
"matter-commission-finish",
|
||||
this._handleCommissionFinish
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
private get _waitForCommissioningFinish(): boolean {
|
||||
// When the app supports reporting Matter commissioning status, defer
|
||||
// advancing past the spinner until we receive matter/commission/finish.
|
||||
return !!this.hass.auth.external?.config.hasMatterStatusReport;
|
||||
}
|
||||
|
||||
private _maybeShowDeviceAdded(): void {
|
||||
if (!this._newDevice) {
|
||||
return;
|
||||
}
|
||||
if (this._waitForCommissioningFinish && !this._commissioningFinished) {
|
||||
return;
|
||||
}
|
||||
this._step = "device_added";
|
||||
}
|
||||
|
||||
private _handleCommissionFinish = (
|
||||
ev: HASSDomEvent<MatterCommissionFinish>
|
||||
) => {
|
||||
const { name, success } = ev.detail;
|
||||
if (!success) {
|
||||
if (this._newDevice) {
|
||||
// Device already showed up in the registry — ignore the failure signal
|
||||
// and let the user finish the rename flow.
|
||||
return;
|
||||
}
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.dialogs.matter-add-device.add_device_failed"
|
||||
),
|
||||
duration: 2000,
|
||||
});
|
||||
this.closeDialog();
|
||||
return;
|
||||
}
|
||||
this._commissioningFinished = true;
|
||||
if (name) {
|
||||
this._proposedDeviceName = name;
|
||||
}
|
||||
this._maybeShowDeviceAdded();
|
||||
};
|
||||
|
||||
protected updated(changedProps: Map<string, unknown>): void {
|
||||
// Retry fetching main entity when hass updates (entities may not be available immediately)
|
||||
if (
|
||||
@@ -160,6 +214,8 @@ class DialogMatterAddDevice extends LitElement {
|
||||
this._newDevice = undefined;
|
||||
this._mainEntity = undefined;
|
||||
this._mainEntityFetched = false;
|
||||
this._proposedDeviceName = undefined;
|
||||
this._commissioningFinished = false;
|
||||
this._deviceAddedState = {
|
||||
name: "",
|
||||
area: undefined,
|
||||
@@ -168,6 +224,10 @@ class DialogMatterAddDevice extends LitElement {
|
||||
};
|
||||
this._unsub?.();
|
||||
this._unsub = undefined;
|
||||
window.removeEventListener(
|
||||
"matter-commission-finish",
|
||||
this._handleCommissionFinish
|
||||
);
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
@@ -211,6 +271,7 @@ class DialogMatterAddDevice extends LitElement {
|
||||
hass: this.hass,
|
||||
device: this._newDevice,
|
||||
mainEntity: this._mainEntity,
|
||||
proposedName: this._proposedDeviceName,
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
|
||||
+16
-2
@@ -36,6 +36,8 @@ class MatterAddDeviceDeviceAdded extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public mainEntity?: ExtEntityRegistryEntry;
|
||||
|
||||
@property({ attribute: false }) public proposedName?: string;
|
||||
|
||||
@state() private _deviceName = "";
|
||||
|
||||
@state() private _area: string | undefined;
|
||||
@@ -49,8 +51,18 @@ class MatterAddDeviceDeviceAdded extends LitElement {
|
||||
protected willUpdate(changedProps: PropertyValues) {
|
||||
if (!this._initialized && this.device) {
|
||||
this._initialized = true;
|
||||
this._deviceName = computeDeviceName(this.device) ?? "";
|
||||
this._deviceName =
|
||||
this.proposedName || (computeDeviceName(this.device) ?? "");
|
||||
this._area = this.device.area_id ?? undefined;
|
||||
} else if (
|
||||
changedProps.has("proposedName") &&
|
||||
this.proposedName &&
|
||||
this.device &&
|
||||
this._deviceName === (computeDeviceName(this.device) ?? "")
|
||||
) {
|
||||
// proposedName arrived after we initialized, and the user hasn't
|
||||
// changed the name yet — adopt it
|
||||
this._deviceName = this.proposedName;
|
||||
}
|
||||
if (
|
||||
!this._deviceClassInitialized &&
|
||||
@@ -158,7 +170,9 @@ class MatterAddDeviceDeviceAdded extends LitElement {
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<div class="device-name">
|
||||
<span>${computeDeviceName(this.device)}</span>
|
||||
<span
|
||||
>${this.proposedName || computeDeviceName(this.device)}</span
|
||||
>
|
||||
<span class="secondary">Matter</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -41,10 +41,7 @@ import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-list";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import {
|
||||
fullEntitiesContext,
|
||||
type RelatedContextItem,
|
||||
} from "../../../data/context";
|
||||
import { fullEntitiesContext } from "../../../data/context";
|
||||
import type { DeviceRegistryEntry } from "../../../data/device/device_registry";
|
||||
import type { EntityRegistryEntry } from "../../../data/entity/entity_registry";
|
||||
import { updateEntityRegistryEntry } from "../../../data/entity/entity_registry";
|
||||
@@ -153,8 +150,6 @@ export class HaSceneEditor extends PreventUnsavedMixin(
|
||||
|
||||
private _entityRegistryUpdate?: EntityRegistryUpdate;
|
||||
|
||||
private _relatedContext?: RelatedContextItem;
|
||||
|
||||
private _newSceneId?: string;
|
||||
|
||||
private _entityRegCreated?: (
|
||||
@@ -658,40 +653,6 @@ export class HaSceneEditor extends PreventUnsavedMixin(
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
changedProps.has("sceneId") ||
|
||||
changedProps.has("_scene") ||
|
||||
changedProps.has("_registryEntry")
|
||||
) {
|
||||
this._setRelatedContext();
|
||||
}
|
||||
}
|
||||
|
||||
private _setRelatedContext(): void {
|
||||
const context: RelatedContextItem | undefined = this.sceneId
|
||||
? this._registryEntry?.area_id
|
||||
? {
|
||||
itemType: "area",
|
||||
itemId: this._registryEntry.area_id,
|
||||
}
|
||||
: this._scene
|
||||
? {
|
||||
itemType: "scene",
|
||||
itemId: this._scene.entity_id,
|
||||
}
|
||||
: undefined
|
||||
: undefined;
|
||||
|
||||
if (
|
||||
context?.itemType === this._relatedContext?.itemType &&
|
||||
context?.itemId === this._relatedContext?.itemId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._relatedContext = context;
|
||||
fireEvent(this, "hass-related-context", context);
|
||||
}
|
||||
|
||||
private _handleMenuAction(ev: HaDropdownSelectEvent) {
|
||||
|
||||
@@ -6,8 +6,6 @@ import "../../components/ha-alert";
|
||||
import "../../components/ha-icon-button-arrow-prev";
|
||||
import "../../components/ha-menu-button";
|
||||
import "../../components/ha-top-app-bar-fixed";
|
||||
import type { EnergyFrontendSystemData } from "../../data/frontend";
|
||||
import { fetchFrontendSystemData } from "../../data/frontend";
|
||||
import type { LovelaceConfig } from "../../data/lovelace/config/types";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant, PanelInfo } from "../../types";
|
||||
@@ -28,8 +26,6 @@ class PanelEnergy extends LitElement {
|
||||
|
||||
@state() private _lovelace?: Lovelace;
|
||||
|
||||
@state() private _config: EnergyFrontendSystemData = {};
|
||||
|
||||
@property({ attribute: false }) public route?: {
|
||||
path: string;
|
||||
prefix: string;
|
||||
@@ -62,23 +58,10 @@ class PanelEnergy extends LitElement {
|
||||
await Promise.all([
|
||||
this.hass.loadFragmentTranslation("lovelace"),
|
||||
this.hass.loadFragmentTranslation("energy"),
|
||||
this._loadSystemData(),
|
||||
]);
|
||||
this._loadConfig();
|
||||
}
|
||||
|
||||
private async _loadSystemData() {
|
||||
try {
|
||||
const data = await fetchFrontendSystemData(
|
||||
this.hass.connection,
|
||||
"energy"
|
||||
);
|
||||
this._config = data || {};
|
||||
} catch (_err) {
|
||||
this._config = {};
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadConfig() {
|
||||
try {
|
||||
this._error = undefined;
|
||||
@@ -111,7 +94,6 @@ class PanelEnergy extends LitElement {
|
||||
this.route?.path === "/now"
|
||||
? DEFAULT_POWER_COLLECTION_KEY
|
||||
: undefined,
|
||||
hidden_cards: this._config.hidden_cards,
|
||||
},
|
||||
},
|
||||
this.hass
|
||||
@@ -182,8 +164,7 @@ class PanelEnergy extends LitElement {
|
||||
navigate(`/config/energy/${tab}?historyBack=1`);
|
||||
}
|
||||
|
||||
private async _reloadConfig() {
|
||||
await this._loadSystemData();
|
||||
private _reloadConfig() {
|
||||
this._loadConfig();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,296 +0,0 @@
|
||||
import type { LocalizeKeys } from "../../../common/translations/localize";
|
||||
import type {
|
||||
EnergyPreferences,
|
||||
GridSourceTypeEnergyPreference,
|
||||
} from "../../../data/energy";
|
||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||
|
||||
/** Strategy config shared by the per-view energy strategies. */
|
||||
export interface EnergyViewStrategyConfig extends LovelaceStrategyConfig {
|
||||
collection_key?: string;
|
||||
hidden_cards?: string[];
|
||||
}
|
||||
|
||||
export type EnergyViewPath =
|
||||
| "overview"
|
||||
| "electricity"
|
||||
| "gas"
|
||||
| "water"
|
||||
| "now";
|
||||
|
||||
// --- Applicability helpers -------------------------------------------------
|
||||
// These mirror, one-to-one, the conditions the individual view strategies use
|
||||
// to decide whether to emit a card. The catalog and the strategies must agree
|
||||
// on what "applicable" means, so the conditions live here and are reused.
|
||||
|
||||
export const hasGridSource = (prefs: EnergyPreferences): boolean =>
|
||||
prefs.energy_sources.some(
|
||||
(source): source is GridSourceTypeEnergyPreference =>
|
||||
source.type === "grid" &&
|
||||
(!!source.stat_energy_from || !!source.stat_energy_to)
|
||||
);
|
||||
|
||||
export const hasReturn = (prefs: EnergyPreferences): boolean =>
|
||||
prefs.energy_sources.some(
|
||||
(source) => source.type === "grid" && !!source.stat_energy_to
|
||||
);
|
||||
|
||||
export const hasSolar = (prefs: EnergyPreferences): boolean =>
|
||||
prefs.energy_sources.some((source) => source.type === "solar");
|
||||
|
||||
export const hasBattery = (prefs: EnergyPreferences): boolean =>
|
||||
prefs.energy_sources.some((source) => source.type === "battery");
|
||||
|
||||
export const hasGasSource = (prefs: EnergyPreferences): boolean =>
|
||||
prefs.energy_sources.some((source) => source.type === "gas");
|
||||
|
||||
export const hasWaterSource = (prefs: EnergyPreferences): boolean =>
|
||||
prefs.energy_sources.some((source) => source.type === "water");
|
||||
|
||||
export const hasWaterDevices = (prefs: EnergyPreferences): boolean =>
|
||||
(prefs.device_consumption_water?.length ?? 0) > 0;
|
||||
|
||||
export const hasDeviceConsumption = (prefs: EnergyPreferences): boolean =>
|
||||
prefs.device_consumption.length > 0;
|
||||
|
||||
export const hasPowerSources = (prefs: EnergyPreferences): boolean =>
|
||||
prefs.energy_sources.some((source) => {
|
||||
if (source.type === "solar" && source.stat_rate) return true;
|
||||
if (source.type === "battery" && source.stat_rate) return true;
|
||||
if (source.type === "grid") {
|
||||
return !!source.stat_rate || !!source.power_config;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
export const hasPowerDevices = (prefs: EnergyPreferences): boolean =>
|
||||
prefs.device_consumption.some((device) => device.stat_rate);
|
||||
|
||||
export const hasPowerWaterDevices = (prefs: EnergyPreferences): boolean =>
|
||||
(prefs.device_consumption_water ?? []).some((device) => device.stat_rate);
|
||||
|
||||
// --- Card catalog ----------------------------------------------------------
|
||||
|
||||
export interface EnergyCardCatalogEntry {
|
||||
/** Stable identifier and storage token: `<view>.<cardType>`. */
|
||||
key: string;
|
||||
view: EnergyViewPath;
|
||||
/** Localize key for the label shown in the customise dialog. */
|
||||
labelKey: LocalizeKeys;
|
||||
/** Whether this card is emitted for the given preferences. */
|
||||
isApplicable: (prefs: EnergyPreferences) => boolean;
|
||||
}
|
||||
|
||||
export const energyCardKey = (view: EnergyViewPath, cardType: string): string =>
|
||||
`${view}.${cardType}`;
|
||||
|
||||
const entry = (
|
||||
view: EnergyViewPath,
|
||||
cardType: string,
|
||||
labelKey: LocalizeKeys,
|
||||
isApplicable: (prefs: EnergyPreferences) => boolean
|
||||
): EnergyCardCatalogEntry => ({
|
||||
key: energyCardKey(view, cardType),
|
||||
view,
|
||||
labelKey,
|
||||
isApplicable,
|
||||
});
|
||||
|
||||
export const ENERGY_CARD_CATALOG: readonly EnergyCardCatalogEntry[] = [
|
||||
// --- Overview ---
|
||||
entry(
|
||||
"overview",
|
||||
"energy-distribution",
|
||||
"ui.panel.energy.cards.energy_distribution_title",
|
||||
(p) => hasGridSource(p) || hasBattery(p) || hasSolar(p)
|
||||
),
|
||||
entry(
|
||||
"overview",
|
||||
"energy-sources-table",
|
||||
"ui.panel.energy.cards.energy_sources_table_title",
|
||||
(p) => p.energy_sources.length > 0
|
||||
),
|
||||
entry(
|
||||
"overview",
|
||||
"power-sources-graph",
|
||||
"ui.panel.energy.cards.power_sources_graph_title",
|
||||
(p) => hasPowerSources(p)
|
||||
),
|
||||
entry(
|
||||
"overview",
|
||||
"energy-usage-graph",
|
||||
"ui.panel.energy.cards.energy_usage_graph_title",
|
||||
(p) => hasGridSource(p) || hasBattery(p)
|
||||
),
|
||||
entry(
|
||||
"overview",
|
||||
"energy-gas-graph",
|
||||
"ui.panel.energy.cards.energy_gas_graph_title",
|
||||
(p) => hasGasSource(p)
|
||||
),
|
||||
// One toggle gates the water row, which renders energy-water-graph (sources)
|
||||
// or, with only water devices, water-sankey.
|
||||
entry(
|
||||
"overview",
|
||||
"energy-water-graph",
|
||||
"ui.panel.energy.cards.energy_water_graph_title",
|
||||
(p) => hasWaterSource(p) || hasWaterDevices(p)
|
||||
),
|
||||
|
||||
// --- Electricity ---
|
||||
entry(
|
||||
"electricity",
|
||||
"energy-distribution",
|
||||
"ui.panel.energy.cards.energy_distribution_title",
|
||||
(p) => hasGridSource(p) || hasBattery(p) || hasSolar(p)
|
||||
),
|
||||
entry(
|
||||
"electricity",
|
||||
"energy-grid-balance",
|
||||
"ui.panel.energy.cards.energy_grid_balance_title",
|
||||
(p) => hasGridSource(p) && hasReturn(p)
|
||||
),
|
||||
entry(
|
||||
"electricity",
|
||||
"energy-grid-neutrality-gauge",
|
||||
"ui.panel.energy.cards.energy_grid_neutrality_gauge_title",
|
||||
(p) => hasReturn(p)
|
||||
),
|
||||
entry(
|
||||
"electricity",
|
||||
"energy-solar-consumed-gauge",
|
||||
"ui.panel.energy.cards.energy_solar_consumed_gauge_title",
|
||||
(p) => hasSolar(p) && hasReturn(p)
|
||||
),
|
||||
entry(
|
||||
"electricity",
|
||||
"energy-self-sufficiency-gauge",
|
||||
"ui.panel.energy.cards.energy_self_sufficiency_gauge_title",
|
||||
(p) => hasSolar(p) && hasGridSource(p)
|
||||
),
|
||||
entry(
|
||||
"electricity",
|
||||
"energy-carbon-consumed-gauge",
|
||||
"ui.panel.energy.cards.energy_carbon_consumed_gauge_title",
|
||||
(p) => hasGridSource(p)
|
||||
),
|
||||
entry(
|
||||
"electricity",
|
||||
"energy-usage-graph",
|
||||
"ui.panel.energy.cards.energy_usage_graph_title",
|
||||
(p) => hasGridSource(p) || hasBattery(p)
|
||||
),
|
||||
entry(
|
||||
"electricity",
|
||||
"energy-solar-graph",
|
||||
"ui.panel.energy.cards.energy_solar_graph_title",
|
||||
(p) => hasSolar(p)
|
||||
),
|
||||
entry(
|
||||
"electricity",
|
||||
"energy-sources-table",
|
||||
"ui.panel.energy.cards.energy_sources_table_title",
|
||||
(p) => hasGridSource(p) || hasSolar(p) || hasBattery(p)
|
||||
),
|
||||
entry(
|
||||
"electricity",
|
||||
"energy-devices-detail-graph",
|
||||
"ui.panel.energy.cards.energy_devices_detail_graph_title",
|
||||
(p) => hasDeviceConsumption(p)
|
||||
),
|
||||
entry(
|
||||
"electricity",
|
||||
"energy-devices-graph",
|
||||
"ui.panel.energy.cards.energy_devices_graph_title",
|
||||
(p) => hasDeviceConsumption(p)
|
||||
),
|
||||
entry(
|
||||
"electricity",
|
||||
"energy-sankey",
|
||||
"ui.panel.energy.cards.energy_sankey_title",
|
||||
(p) => hasDeviceConsumption(p)
|
||||
),
|
||||
|
||||
// --- Gas ---
|
||||
entry(
|
||||
"gas",
|
||||
"energy-gas-graph",
|
||||
"ui.panel.energy.cards.energy_gas_graph_title",
|
||||
(p) => hasGasSource(p)
|
||||
),
|
||||
entry(
|
||||
"gas",
|
||||
"energy-sources-table",
|
||||
"ui.panel.energy.cards.energy_sources_table_title",
|
||||
(p) => hasGasSource(p)
|
||||
),
|
||||
|
||||
// --- Water ---
|
||||
entry(
|
||||
"water",
|
||||
"energy-water-graph",
|
||||
"ui.panel.energy.cards.energy_water_graph_title",
|
||||
(p) => hasWaterSource(p)
|
||||
),
|
||||
entry(
|
||||
"water",
|
||||
"energy-sources-table",
|
||||
"ui.panel.energy.cards.energy_sources_table_title",
|
||||
(p) => hasWaterSource(p)
|
||||
),
|
||||
entry(
|
||||
"water",
|
||||
"water-sankey",
|
||||
"ui.panel.energy.cards.water_sankey_title",
|
||||
(p) => hasWaterDevices(p)
|
||||
),
|
||||
|
||||
// --- Now (power) ---
|
||||
entry(
|
||||
"now",
|
||||
"power-sources-graph",
|
||||
"ui.panel.energy.cards.power_sources_graph_title",
|
||||
(p) => hasPowerSources(p)
|
||||
),
|
||||
entry(
|
||||
"now",
|
||||
"power-sankey",
|
||||
"ui.panel.energy.cards.power_sankey_title",
|
||||
(p) => hasPowerDevices(p)
|
||||
),
|
||||
entry(
|
||||
"now",
|
||||
"water-flow-sankey",
|
||||
"ui.panel.energy.cards.water_flow_sankey_title",
|
||||
(p) => hasPowerWaterDevices(p)
|
||||
),
|
||||
];
|
||||
|
||||
// --- Lookup helpers --------------------------------------------------------
|
||||
|
||||
export const isEnergyCardHidden = (
|
||||
view: EnergyViewPath,
|
||||
cardType: string,
|
||||
hidden: string[] | undefined
|
||||
): boolean => !!hidden?.includes(energyCardKey(view, cardType));
|
||||
|
||||
/** Keys of all catalog cards that apply to the given preferences for a view. */
|
||||
export const applicableEnergyCardKeys = (
|
||||
view: EnergyViewPath,
|
||||
prefs: EnergyPreferences
|
||||
): string[] =>
|
||||
ENERGY_CARD_CATALOG.filter(
|
||||
(c) => c.view === view && c.isApplicable(prefs)
|
||||
).map((c) => c.key);
|
||||
|
||||
/** True when a view has applicable cards but every one of them is hidden. */
|
||||
export const isEnergyViewEmpty = (
|
||||
view: EnergyViewPath,
|
||||
prefs: EnergyPreferences,
|
||||
hidden: string[] | undefined
|
||||
): boolean => {
|
||||
const applicable = applicableEnergyCardKeys(view, prefs);
|
||||
return (
|
||||
applicable.length > 0 && applicable.every((key) => hidden?.includes(key))
|
||||
);
|
||||
};
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import type { EnergyPreferences } from "../../../data/energy";
|
||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||
import type { LovelaceConfig } from "../../../data/lovelace/config/types";
|
||||
import type { LovelaceStrategyViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { LocalizeKeys } from "../../../common/translations/localize";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceStrategyDependency } from "../../lovelace/strategies/types";
|
||||
@@ -15,8 +15,6 @@ import {
|
||||
DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
DEFAULT_POWER_COLLECTION_KEY,
|
||||
} from "../constants";
|
||||
import type { EnergyViewPath } from "./energy-cards";
|
||||
import { isEnergyViewEmpty } from "./energy-cards";
|
||||
|
||||
const OVERVIEW_VIEW = {
|
||||
path: "overview",
|
||||
@@ -24,7 +22,7 @@ const OVERVIEW_VIEW = {
|
||||
type: "energy-overview",
|
||||
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
},
|
||||
} as LovelaceStrategyViewConfig;
|
||||
} as LovelaceViewConfig;
|
||||
|
||||
const ENERGY_VIEW = {
|
||||
path: "electricity",
|
||||
@@ -32,7 +30,7 @@ const ENERGY_VIEW = {
|
||||
type: "energy",
|
||||
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
},
|
||||
} as LovelaceStrategyViewConfig;
|
||||
} as LovelaceViewConfig;
|
||||
|
||||
const WATER_VIEW = {
|
||||
path: "water",
|
||||
@@ -40,7 +38,7 @@ const WATER_VIEW = {
|
||||
type: "water",
|
||||
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
},
|
||||
} as LovelaceStrategyViewConfig;
|
||||
} as LovelaceViewConfig;
|
||||
|
||||
const GAS_VIEW = {
|
||||
path: "gas",
|
||||
@@ -48,7 +46,7 @@ const GAS_VIEW = {
|
||||
type: "gas",
|
||||
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
},
|
||||
} as LovelaceStrategyViewConfig;
|
||||
} as LovelaceViewConfig;
|
||||
|
||||
const POWER_VIEW = {
|
||||
path: "now",
|
||||
@@ -56,7 +54,7 @@ const POWER_VIEW = {
|
||||
type: "power",
|
||||
collection_key: DEFAULT_POWER_COLLECTION_KEY,
|
||||
},
|
||||
} as LovelaceStrategyViewConfig;
|
||||
} as LovelaceViewConfig;
|
||||
|
||||
const WIZARD_VIEW = {
|
||||
type: "panel",
|
||||
@@ -67,7 +65,6 @@ const WIZARD_VIEW = {
|
||||
export interface EnergyDashboardStrategyConfig extends LovelaceStrategyConfig {
|
||||
type: "energy";
|
||||
default_collection?: string;
|
||||
hidden_cards?: string[];
|
||||
}
|
||||
|
||||
@customElement("energy-dashboard-strategy")
|
||||
@@ -118,42 +115,28 @@ export class EnergyDashboardStrategy extends ReactiveElement {
|
||||
|
||||
const hasDeviceConsumption = prefs.device_consumption.length > 0;
|
||||
|
||||
const hidden = _config.hidden_cards;
|
||||
|
||||
const candidateViews: LovelaceStrategyViewConfig[] = [];
|
||||
const views: LovelaceViewConfig[] = [];
|
||||
if (hasEnergy || hasDeviceConsumption) {
|
||||
candidateViews.push(ENERGY_VIEW);
|
||||
views.push(ENERGY_VIEW);
|
||||
}
|
||||
if (hasGas) {
|
||||
candidateViews.push(GAS_VIEW);
|
||||
views.push(GAS_VIEW);
|
||||
}
|
||||
if (hasWater) {
|
||||
candidateViews.push(WATER_VIEW);
|
||||
views.push(WATER_VIEW);
|
||||
}
|
||||
if (hasPower) {
|
||||
candidateViews.push(POWER_VIEW);
|
||||
views.push(POWER_VIEW);
|
||||
}
|
||||
if (
|
||||
hasPowerSource ||
|
||||
[hasEnergy, hasGas, hasWater].filter(Boolean).length > 1
|
||||
) {
|
||||
candidateViews.unshift(OVERVIEW_VIEW);
|
||||
views.unshift(OVERVIEW_VIEW);
|
||||
}
|
||||
|
||||
// Drop a view (tab) when every card it would render has been hidden, so we
|
||||
// don't show an empty tab. Keep at least one view so the dashboard never
|
||||
// renders blank and the customize entry stays reachable.
|
||||
let views = candidateViews.filter(
|
||||
(view) => !isEnergyViewEmpty(view.path as EnergyViewPath, prefs, hidden)
|
||||
);
|
||||
if (views.length === 0) {
|
||||
views = candidateViews;
|
||||
}
|
||||
|
||||
return {
|
||||
views: views.map((view) => ({
|
||||
...view,
|
||||
strategy: { ...view.strategy, hidden_cards: hidden },
|
||||
title:
|
||||
view.title ||
|
||||
hass.localize(`ui.panel.energy.title.${view.path}` as LocalizeKeys),
|
||||
|
||||
@@ -4,22 +4,20 @@ import type { GridSourceTypeEnergyPreference } from "../../../data/energy";
|
||||
import { getEnergyDataCollection } from "../../../data/energy";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||
import type { LovelaceStrategyDependency } from "../../lovelace/strategies/types";
|
||||
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../constants";
|
||||
import type { EnergyViewStrategyConfig } from "./energy-cards";
|
||||
import { isEnergyCardHidden } from "./energy-cards";
|
||||
|
||||
@customElement("energy-overview-view-strategy")
|
||||
export class EnergyOverviewViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [];
|
||||
|
||||
static async generate(
|
||||
_config: EnergyViewStrategyConfig,
|
||||
_config: LovelaceStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceViewConfig> {
|
||||
const collectionKey =
|
||||
_config.collection_key || DEFAULT_ENERGY_COLLECTION_KEY;
|
||||
const hidden = _config.hidden_cards;
|
||||
|
||||
const view: LovelaceViewConfig = {
|
||||
type: "sections",
|
||||
@@ -78,10 +76,7 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
|
||||
return false;
|
||||
});
|
||||
|
||||
if (
|
||||
(hasGrid || hasBattery || hasSolar) &&
|
||||
!isEnergyCardHidden("overview", "energy-distribution", hidden)
|
||||
) {
|
||||
if (hasGrid || hasBattery || hasSolar) {
|
||||
view.sections!.push({
|
||||
type: "grid",
|
||||
cards: [
|
||||
@@ -96,10 +91,7 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
prefs.energy_sources.length &&
|
||||
!isEnergyCardHidden("overview", "energy-sources-table", hidden)
|
||||
) {
|
||||
if (prefs.energy_sources.length) {
|
||||
view.sections!.push({
|
||||
type: "grid",
|
||||
cards: [
|
||||
@@ -115,10 +107,7 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
hasPowerSources &&
|
||||
!isEnergyCardHidden("overview", "power-sources-graph", hidden)
|
||||
) {
|
||||
if (hasPowerSources) {
|
||||
view.sections!.push({
|
||||
type: "grid",
|
||||
cards: [
|
||||
@@ -134,10 +123,7 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
(hasGrid || hasBattery) &&
|
||||
!isEnergyCardHidden("overview", "energy-usage-graph", hidden)
|
||||
) {
|
||||
if (hasGrid || hasBattery) {
|
||||
view.sections!.push({
|
||||
type: "grid",
|
||||
cards: [
|
||||
@@ -152,7 +138,7 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
|
||||
});
|
||||
}
|
||||
|
||||
if (hasGas && !isEnergyCardHidden("overview", "energy-gas-graph", hidden)) {
|
||||
if (hasGas) {
|
||||
view.sections!.push({
|
||||
type: "grid",
|
||||
cards: [
|
||||
@@ -167,10 +153,7 @@ export class EnergyOverviewViewStrategy extends ReactiveElement {
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
(hasWaterSources || hasWaterDevices) &&
|
||||
!isEnergyCardHidden("overview", "energy-water-graph", hidden)
|
||||
) {
|
||||
if (hasWaterSources || hasWaterDevices) {
|
||||
view.sections!.push({
|
||||
type: "grid",
|
||||
cards: [
|
||||
|
||||
@@ -3,11 +3,10 @@ import { customElement } from "lit/decorators";
|
||||
import type { GridSourceTypeEnergyPreference } from "../../../data/energy";
|
||||
import { getEnergyDataCollection } from "../../../data/energy";
|
||||
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||
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 { isEnergyCardHidden } from "./energy-cards";
|
||||
import { shouldShowFloorsAndAreas } from "./show-floors-and-areas";
|
||||
import {
|
||||
LARGE_SCREEN_CONDITION,
|
||||
@@ -20,12 +19,11 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [];
|
||||
|
||||
static async generate(
|
||||
_config: EnergyViewStrategyConfig,
|
||||
_config: LovelaceStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceViewConfig> {
|
||||
const collectionKey =
|
||||
_config.collection_key || DEFAULT_ENERGY_COLLECTION_KEY;
|
||||
const hidden = _config.hidden_cards;
|
||||
|
||||
const view: LovelaceViewConfig = {
|
||||
type: "sections",
|
||||
@@ -80,10 +78,7 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
const gaugeCards: LovelaceCardConfig[] = [];
|
||||
const sidebarSection = view.sidebar!.sections![0];
|
||||
|
||||
if (
|
||||
(hasGrid || hasBattery || hasSolar) &&
|
||||
!isEnergyCardHidden("electricity", "energy-distribution", hidden)
|
||||
) {
|
||||
if (hasGrid || hasBattery || hasSolar) {
|
||||
const distributionCard = {
|
||||
title: hass.localize("ui.panel.energy.cards.energy_distribution_title"),
|
||||
type: "energy-distribution",
|
||||
@@ -99,11 +94,7 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
}
|
||||
|
||||
// Only include if we have both grid import and export configured
|
||||
if (
|
||||
hasGrid &&
|
||||
hasReturn &&
|
||||
!isEnergyCardHidden("electricity", "energy-grid-balance", hidden)
|
||||
) {
|
||||
if (hasGrid && hasReturn) {
|
||||
const gridResultCard = {
|
||||
type: "energy-grid-balance",
|
||||
collection_key: collectionKey,
|
||||
@@ -118,10 +109,7 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
}
|
||||
|
||||
// Only include if we have a grid source & return.
|
||||
if (
|
||||
hasReturn &&
|
||||
!isEnergyCardHidden("electricity", "energy-grid-neutrality-gauge", hidden)
|
||||
) {
|
||||
if (hasReturn) {
|
||||
const card = {
|
||||
type: "energy-grid-neutrality-gauge",
|
||||
collection_key: collectionKey,
|
||||
@@ -131,28 +119,14 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
|
||||
// Only include if we have a solar source.
|
||||
if (hasSolar) {
|
||||
if (
|
||||
hasReturn &&
|
||||
!isEnergyCardHidden(
|
||||
"electricity",
|
||||
"energy-solar-consumed-gauge",
|
||||
hidden
|
||||
)
|
||||
) {
|
||||
if (hasReturn) {
|
||||
const card = {
|
||||
type: "energy-solar-consumed-gauge",
|
||||
collection_key: collectionKey,
|
||||
};
|
||||
gaugeCards.push(card);
|
||||
}
|
||||
if (
|
||||
hasGrid &&
|
||||
!isEnergyCardHidden(
|
||||
"electricity",
|
||||
"energy-self-sufficiency-gauge",
|
||||
hidden
|
||||
)
|
||||
) {
|
||||
if (hasGrid) {
|
||||
const card = {
|
||||
type: "energy-self-sufficiency-gauge",
|
||||
collection_key: collectionKey,
|
||||
@@ -162,10 +136,7 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
}
|
||||
|
||||
// Only include if we have a grid
|
||||
if (
|
||||
hasGrid &&
|
||||
!isEnergyCardHidden("electricity", "energy-carbon-consumed-gauge", hidden)
|
||||
) {
|
||||
if (hasGrid) {
|
||||
const card = {
|
||||
type: "energy-carbon-consumed-gauge",
|
||||
collection_key: collectionKey,
|
||||
@@ -200,10 +171,7 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
});
|
||||
|
||||
// Only include if we have a grid or battery.
|
||||
if (
|
||||
(hasGrid || hasBattery) &&
|
||||
!isEnergyCardHidden("electricity", "energy-usage-graph", hidden)
|
||||
) {
|
||||
if (hasGrid || hasBattery) {
|
||||
mainCards.push({
|
||||
title: hass.localize("ui.panel.energy.cards.energy_usage_graph_title"),
|
||||
type: "energy-usage-graph",
|
||||
@@ -213,10 +181,7 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
}
|
||||
|
||||
// Only include if we have a solar source.
|
||||
if (
|
||||
hasSolar &&
|
||||
!isEnergyCardHidden("electricity", "energy-solar-graph", hidden)
|
||||
) {
|
||||
if (hasSolar) {
|
||||
mainCards.push({
|
||||
title: hass.localize("ui.panel.energy.cards.energy_solar_graph_title"),
|
||||
type: "energy-solar-graph",
|
||||
@@ -225,10 +190,7 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
(hasGrid || hasSolar || hasBattery) &&
|
||||
!isEnergyCardHidden("electricity", "energy-sources-table", hidden)
|
||||
) {
|
||||
if (hasGrid || hasSolar || hasBattery) {
|
||||
mainCards.push({
|
||||
title: hass.localize(
|
||||
"ui.panel.energy.cards.energy_sources_table_title"
|
||||
@@ -242,47 +204,35 @@ export class EnergyViewStrategy extends ReactiveElement {
|
||||
|
||||
// Only include if we have at least 1 device in the config.
|
||||
if (prefs.device_consumption.length) {
|
||||
if (
|
||||
!isEnergyCardHidden(
|
||||
"electricity",
|
||||
"energy-devices-detail-graph",
|
||||
hidden
|
||||
)
|
||||
) {
|
||||
mainCards.push({
|
||||
title: hass.localize(
|
||||
"ui.panel.energy.cards.energy_devices_detail_graph_title"
|
||||
),
|
||||
type: "energy-devices-detail-graph",
|
||||
collection_key: collectionKey,
|
||||
grid_options: { columns: 36 },
|
||||
});
|
||||
}
|
||||
if (!isEnergyCardHidden("electricity", "energy-devices-graph", hidden)) {
|
||||
mainCards.push({
|
||||
title: hass.localize(
|
||||
"ui.panel.energy.cards.energy_devices_graph_title"
|
||||
),
|
||||
type: "energy-devices-graph",
|
||||
collection_key: collectionKey,
|
||||
grid_options: { columns: 36 },
|
||||
});
|
||||
}
|
||||
if (!isEnergyCardHidden("electricity", "energy-sankey", hidden)) {
|
||||
const showFloorsAndAreas = shouldShowFloorsAndAreas(
|
||||
prefs.device_consumption,
|
||||
hass,
|
||||
(d) => d.stat_consumption
|
||||
);
|
||||
mainCards.push({
|
||||
title: hass.localize("ui.panel.energy.cards.energy_sankey_title"),
|
||||
type: "energy-sankey",
|
||||
collection_key: collectionKey,
|
||||
group_by_floor: showFloorsAndAreas,
|
||||
group_by_area: showFloorsAndAreas,
|
||||
grid_options: { columns: 36 },
|
||||
});
|
||||
}
|
||||
const showFloorsAndAreas = shouldShowFloorsAndAreas(
|
||||
prefs.device_consumption,
|
||||
hass,
|
||||
(d) => d.stat_consumption
|
||||
);
|
||||
mainCards.push({
|
||||
title: hass.localize(
|
||||
"ui.panel.energy.cards.energy_devices_detail_graph_title"
|
||||
),
|
||||
type: "energy-devices-detail-graph",
|
||||
collection_key: collectionKey,
|
||||
grid_options: { columns: 36 },
|
||||
});
|
||||
mainCards.push({
|
||||
title: hass.localize(
|
||||
"ui.panel.energy.cards.energy_devices_graph_title"
|
||||
),
|
||||
type: "energy-devices-graph",
|
||||
collection_key: collectionKey,
|
||||
grid_options: { columns: 36 },
|
||||
});
|
||||
mainCards.push({
|
||||
title: hass.localize("ui.panel.energy.cards.energy_sankey_title"),
|
||||
type: "energy-sankey",
|
||||
collection_key: collectionKey,
|
||||
group_by_floor: showFloorsAndAreas,
|
||||
group_by_area: showFloorsAndAreas,
|
||||
grid_options: { columns: 36 },
|
||||
});
|
||||
}
|
||||
|
||||
view.sections!.push({
|
||||
|
||||
@@ -3,9 +3,8 @@ import { customElement } from "lit/decorators";
|
||||
import { getEnergyDataCollection } from "../../../data/energy";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../constants";
|
||||
import type { EnergyViewStrategyConfig } from "./energy-cards";
|
||||
import { isEnergyCardHidden } from "./energy-cards";
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
import type { LovelaceStrategyDependency } from "../../lovelace/strategies/types";
|
||||
|
||||
@@ -14,12 +13,11 @@ export class GasViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [];
|
||||
|
||||
static async generate(
|
||||
_config: EnergyViewStrategyConfig,
|
||||
_config: LovelaceStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceViewConfig> {
|
||||
const collectionKey =
|
||||
_config.collection_key || DEFAULT_ENERGY_COLLECTION_KEY;
|
||||
const hidden = _config.hidden_cards;
|
||||
|
||||
const view: LovelaceViewConfig = {
|
||||
type: "sections",
|
||||
@@ -62,30 +60,24 @@ export class GasViewStrategy extends ReactiveElement {
|
||||
},
|
||||
});
|
||||
|
||||
if (!isEnergyCardHidden("gas", "energy-gas-graph", hidden)) {
|
||||
section.cards!.push({
|
||||
title: hass.localize("ui.panel.energy.cards.energy_gas_graph_title"),
|
||||
type: "energy-gas-graph",
|
||||
collection_key: collectionKey,
|
||||
grid_options: {
|
||||
columns: 24,
|
||||
},
|
||||
});
|
||||
}
|
||||
section.cards!.push({
|
||||
title: hass.localize("ui.panel.energy.cards.energy_gas_graph_title"),
|
||||
type: "energy-gas-graph",
|
||||
collection_key: collectionKey,
|
||||
grid_options: {
|
||||
columns: 24,
|
||||
},
|
||||
});
|
||||
|
||||
if (!isEnergyCardHidden("gas", "energy-sources-table", hidden)) {
|
||||
section.cards!.push({
|
||||
title: hass.localize(
|
||||
"ui.panel.energy.cards.energy_sources_table_title"
|
||||
),
|
||||
type: "energy-sources-table",
|
||||
collection_key: collectionKey,
|
||||
types: ["gas"],
|
||||
grid_options: {
|
||||
columns: 12,
|
||||
},
|
||||
});
|
||||
}
|
||||
section.cards!.push({
|
||||
title: hass.localize("ui.panel.energy.cards.energy_sources_table_title"),
|
||||
type: "energy-sources-table",
|
||||
collection_key: collectionKey,
|
||||
types: ["gas"],
|
||||
grid_options: {
|
||||
columns: 12,
|
||||
},
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { getEnergyDataCollection } from "../../../data/energy";
|
||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||
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 { isEnergyCardHidden } from "./energy-cards";
|
||||
import { shouldShowFloorsAndAreas } from "./show-floors-and-areas";
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
import type { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge";
|
||||
@@ -16,12 +15,11 @@ export class PowerViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [];
|
||||
|
||||
static async generate(
|
||||
_config: EnergyViewStrategyConfig,
|
||||
_config: LovelaceStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceViewConfig> {
|
||||
const collectionKey =
|
||||
_config.collection_key || DEFAULT_ENERGY_COLLECTION_KEY;
|
||||
const hidden = _config.hidden_cards;
|
||||
|
||||
const energyCollection = getEnergyDataCollection(hass, {
|
||||
key: collectionKey,
|
||||
@@ -81,18 +79,14 @@ export class PowerViewStrategy extends ReactiveElement {
|
||||
collection_key: collectionKey,
|
||||
});
|
||||
|
||||
if (!isEnergyCardHidden("now", "power-sources-graph", hidden)) {
|
||||
chartsSection.cards!.push({
|
||||
title: hass.localize(
|
||||
"ui.panel.energy.cards.power_sources_graph_title"
|
||||
),
|
||||
type: "power-sources-graph",
|
||||
collection_key: collectionKey,
|
||||
grid_options: {
|
||||
columns: 36,
|
||||
},
|
||||
});
|
||||
}
|
||||
chartsSection.cards!.push({
|
||||
title: hass.localize("ui.panel.energy.cards.power_sources_graph_title"),
|
||||
type: "power-sources-graph",
|
||||
collection_key: collectionKey,
|
||||
grid_options: {
|
||||
columns: 36,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (hasGasSources) {
|
||||
@@ -118,7 +112,7 @@ export class PowerViewStrategy extends ReactiveElement {
|
||||
}
|
||||
});
|
||||
|
||||
if (hasPowerDevices && !isEnergyCardHidden("now", "power-sankey", hidden)) {
|
||||
if (hasPowerDevices) {
|
||||
const showFloorsAndAreas = shouldShowFloorsAndAreas(
|
||||
prefs.device_consumption,
|
||||
hass,
|
||||
@@ -136,10 +130,7 @@ export class PowerViewStrategy extends ReactiveElement {
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
hasWaterDevices &&
|
||||
!isEnergyCardHidden("now", "water-flow-sankey", hidden)
|
||||
) {
|
||||
if (hasWaterDevices) {
|
||||
const showFloorsAndAreas = shouldShowFloorsAndAreas(
|
||||
prefs.device_consumption_water,
|
||||
hass,
|
||||
|
||||
@@ -2,12 +2,11 @@ import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { getEnergyDataCollection } from "../../../data/energy";
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||
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 { isEnergyCardHidden } from "./energy-cards";
|
||||
import { shouldShowFloorsAndAreas } from "./show-floors-and-areas";
|
||||
|
||||
@customElement("water-view-strategy")
|
||||
@@ -15,12 +14,11 @@ export class WaterViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [];
|
||||
|
||||
static async generate(
|
||||
_config: EnergyViewStrategyConfig,
|
||||
_config: LovelaceStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceViewConfig> {
|
||||
const collectionKey =
|
||||
_config.collection_key || DEFAULT_ENERGY_COLLECTION_KEY;
|
||||
const hidden = _config.hidden_cards;
|
||||
|
||||
const view: LovelaceViewConfig = {
|
||||
type: "sections",
|
||||
@@ -65,38 +63,29 @@ export class WaterViewStrategy extends ReactiveElement {
|
||||
});
|
||||
|
||||
if (hasWaterSources) {
|
||||
if (!isEnergyCardHidden("water", "energy-water-graph", hidden)) {
|
||||
section.cards!.push({
|
||||
title: hass.localize(
|
||||
"ui.panel.energy.cards.energy_water_graph_title"
|
||||
),
|
||||
type: "energy-water-graph",
|
||||
collection_key: collectionKey,
|
||||
grid_options: {
|
||||
columns: 24,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (!isEnergyCardHidden("water", "energy-sources-table", hidden)) {
|
||||
section.cards!.push({
|
||||
title: hass.localize(
|
||||
"ui.panel.energy.cards.energy_sources_table_title"
|
||||
),
|
||||
type: "energy-sources-table",
|
||||
collection_key: collectionKey,
|
||||
types: ["water"],
|
||||
grid_options: {
|
||||
columns: 12,
|
||||
},
|
||||
});
|
||||
}
|
||||
section.cards!.push({
|
||||
title: hass.localize("ui.panel.energy.cards.energy_water_graph_title"),
|
||||
type: "energy-water-graph",
|
||||
collection_key: collectionKey,
|
||||
grid_options: {
|
||||
columns: 24,
|
||||
},
|
||||
});
|
||||
section.cards!.push({
|
||||
title: hass.localize(
|
||||
"ui.panel.energy.cards.energy_sources_table_title"
|
||||
),
|
||||
type: "energy-sources-table",
|
||||
collection_key: collectionKey,
|
||||
types: ["water"],
|
||||
grid_options: {
|
||||
columns: 12,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Only include if we have at least 1 water device in the config.
|
||||
if (
|
||||
hasWaterDevices &&
|
||||
!isEnergyCardHidden("water", "water-sankey", hidden)
|
||||
) {
|
||||
if (hasWaterDevices) {
|
||||
const showFloorsAndAreas = shouldShowFloorsAndAreas(
|
||||
prefs.device_consumption_water,
|
||||
hass,
|
||||
|
||||
@@ -15,7 +15,6 @@ import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { getEntityLocation } from "../../../common/entity/get_entity_location";
|
||||
import { deepEqual } from "../../../common/util/deep-equal";
|
||||
import parseAspectRatio from "../../../common/util/parse-aspect-ratio";
|
||||
import "../../../components/ha-alert";
|
||||
@@ -91,7 +90,10 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
const personSources = new Set<string>();
|
||||
const locationEntities: string[] = [];
|
||||
Object.values(hass.states).forEach((entity) => {
|
||||
if (!getEntityLocation(entity, hass.states)) {
|
||||
if (
|
||||
!("latitude" in entity.attributes) ||
|
||||
!("longitude" in entity.attributes)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
locationEntities.push(entity.entity_id);
|
||||
|
||||
@@ -16,11 +16,10 @@ import {
|
||||
union,
|
||||
} from "superstruct";
|
||||
import { ContextProvider } from "@lit/context";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
import { getEntityLocation } from "../../../../common/entity/get_entity_location";
|
||||
import { hasLocation } from "../../../../common/entity/has_location";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import { orderProperties } from "../../../../common/util/order-properties";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
@@ -276,13 +275,10 @@ export class HuiMapCardEditor extends LitElement implements LovelaceCardEditor {
|
||||
this._locationEntities = !this.hass
|
||||
? []
|
||||
: Object.keys(this.hass!.states).filter((entity_id) =>
|
||||
getEntityLocation(this.hass!.states[entity_id], this.hass!.states)
|
||||
hasLocation(this.hass!.states[entity_id])
|
||||
);
|
||||
}
|
||||
|
||||
private _entityHasLocation = (stateObj: HassEntity) =>
|
||||
!!getEntityLocation(stateObj, this.hass!.states);
|
||||
|
||||
protected render() {
|
||||
if (!this.hass || !this._config) {
|
||||
return nothing;
|
||||
@@ -324,7 +320,7 @@ export class HuiMapCardEditor extends LitElement implements LovelaceCardEditor {
|
||||
<hui-entity-editor
|
||||
.hass=${this.hass}
|
||||
.entities=${configEntities}
|
||||
.entityFilter=${this._entityHasLocation}
|
||||
.entityFilter=${hasLocation}
|
||||
can-edit
|
||||
.required=${!this._config.show_all}
|
||||
@entities-changed=${this._entitiesValueChanged}
|
||||
|
||||
+35
-39
@@ -11,11 +11,6 @@ import type {
|
||||
MediaPlayerSoundModeCardFeatureConfig,
|
||||
} from "../../card-features/types";
|
||||
import type { LovelaceCardFeatureEditor } from "../../types";
|
||||
import {
|
||||
customizableListData,
|
||||
customizableListSchema,
|
||||
processCustomizableListValue,
|
||||
} from "./customizable-list-feature";
|
||||
|
||||
@customElement("hui-media-player-sound-mode-card-feature-editor")
|
||||
export class HuiMediaPlayerSoundModeCardFeatureEditor
|
||||
@@ -33,20 +28,28 @@ export class HuiMediaPlayerSoundModeCardFeatureEditor
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(stateObj: MediaPlayerEntity | undefined, customize: boolean) =>
|
||||
customizableListSchema({
|
||||
field: "sound_modes",
|
||||
customize,
|
||||
options:
|
||||
stateObj?.attributes.sound_mode_list?.map((mode) => ({
|
||||
value: mode,
|
||||
label: this.hass!.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
"sound_mode",
|
||||
mode
|
||||
),
|
||||
})) ?? [],
|
||||
})
|
||||
(hass: HomeAssistant, stateObj?: MediaPlayerEntity) =>
|
||||
[
|
||||
{
|
||||
name: "sound_modes",
|
||||
selector: {
|
||||
select: {
|
||||
multiple: true,
|
||||
mode: "list" as const,
|
||||
reorder: true,
|
||||
options:
|
||||
stateObj?.attributes.sound_mode_list?.map((mode) => ({
|
||||
value: mode,
|
||||
label: hass.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
"sound_mode",
|
||||
mode
|
||||
),
|
||||
})) ?? [],
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const
|
||||
);
|
||||
|
||||
protected render() {
|
||||
@@ -60,13 +63,12 @@ export class HuiMediaPlayerSoundModeCardFeatureEditor
|
||||
| undefined)
|
||||
: undefined;
|
||||
|
||||
const data = customizableListData(this._config, "sound_modes");
|
||||
const schema = this._schema(stateObj, data.customize);
|
||||
const schema = this._schema(this.hass!, stateObj);
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.data=${this._config}
|
||||
.schema=${schema}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
@@ -77,27 +79,21 @@ export class HuiMediaPlayerSoundModeCardFeatureEditor
|
||||
private _valueChanged(
|
||||
ev: ValueChangedEvent<MediaPlayerSoundModeCardFeatureConfig>
|
||||
): void {
|
||||
const stateObj = this.context?.entity_id
|
||||
? (this.hass!.states[this.context.entity_id] as
|
||||
| MediaPlayerEntity
|
||||
| undefined)
|
||||
: undefined;
|
||||
const defaults = stateObj?.attributes.sound_mode_list ?? [];
|
||||
const config =
|
||||
processCustomizableListValue<MediaPlayerSoundModeCardFeatureConfig>(
|
||||
ev.detail.value,
|
||||
"sound_modes",
|
||||
defaults
|
||||
);
|
||||
fireEvent(this, "config-changed", { config });
|
||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) =>
|
||||
this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.features.types.media-player-sound-mode.${schema.name}`
|
||||
);
|
||||
) => {
|
||||
switch (schema.name) {
|
||||
case "sound_modes":
|
||||
return this.hass?.localize(
|
||||
`ui.panel.lovelace.editor.features.types.media-player-sound-mode.${schema.name}`
|
||||
);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
+31
-39
@@ -11,11 +11,6 @@ import type {
|
||||
MediaPlayerSourceCardFeatureConfig,
|
||||
} from "../../card-features/types";
|
||||
import type { LovelaceCardFeatureEditor } from "../../types";
|
||||
import {
|
||||
customizableListData,
|
||||
customizableListSchema,
|
||||
processCustomizableListValue,
|
||||
} from "./customizable-list-feature";
|
||||
|
||||
@customElement("hui-media-player-source-card-feature-editor")
|
||||
export class HuiMediaPlayerSourceCardFeatureEditor
|
||||
@@ -33,20 +28,24 @@ export class HuiMediaPlayerSourceCardFeatureEditor
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(stateObj: MediaPlayerEntity | undefined, customize: boolean) =>
|
||||
customizableListSchema({
|
||||
field: "sources",
|
||||
customize,
|
||||
options:
|
||||
stateObj?.attributes.source_list?.map((source) => ({
|
||||
value: source,
|
||||
label: this.hass!.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
"source",
|
||||
source
|
||||
),
|
||||
})) ?? [],
|
||||
})
|
||||
(stateObj?: MediaPlayerEntity) =>
|
||||
[
|
||||
{
|
||||
name: "sources",
|
||||
selector: {
|
||||
select: {
|
||||
multiple: true,
|
||||
mode: "list" as const,
|
||||
reorder: true,
|
||||
options:
|
||||
stateObj?.attributes.source_list?.map((source) => ({
|
||||
value: source,
|
||||
label: source,
|
||||
})) ?? [],
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const
|
||||
);
|
||||
|
||||
protected render() {
|
||||
@@ -60,13 +59,12 @@ export class HuiMediaPlayerSourceCardFeatureEditor
|
||||
| undefined)
|
||||
: undefined;
|
||||
|
||||
const data = customizableListData(this._config, "sources");
|
||||
const schema = this._schema(stateObj, data.customize);
|
||||
const schema = this._schema(stateObj);
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.data=${this._config}
|
||||
.schema=${schema}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
@@ -77,27 +75,21 @@ export class HuiMediaPlayerSourceCardFeatureEditor
|
||||
private _valueChanged(
|
||||
ev: ValueChangedEvent<MediaPlayerSourceCardFeatureConfig>
|
||||
): void {
|
||||
const stateObj = this.context?.entity_id
|
||||
? (this.hass!.states[this.context.entity_id] as
|
||||
| MediaPlayerEntity
|
||||
| undefined)
|
||||
: undefined;
|
||||
const defaults = stateObj?.attributes.source_list ?? [];
|
||||
const config =
|
||||
processCustomizableListValue<MediaPlayerSourceCardFeatureConfig>(
|
||||
ev.detail.value,
|
||||
"sources",
|
||||
defaults
|
||||
);
|
||||
fireEvent(this, "config-changed", { config });
|
||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) =>
|
||||
this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.features.types.media-player-source.${schema.name}`
|
||||
);
|
||||
) => {
|
||||
switch (schema.name) {
|
||||
case "sources":
|
||||
return this.hass?.localize(
|
||||
`ui.panel.lovelace.editor.features.types.media-player-source.${schema.name}`
|
||||
);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { getEntityLocation } from "../../common/entity/get_entity_location";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-menu-button";
|
||||
@@ -65,10 +64,11 @@ class HaPanelMap extends LitElement {
|
||||
const personSources = new Set<string>();
|
||||
const locationEntities: string[] = [];
|
||||
Object.values(this.hass!.states).forEach((entity) => {
|
||||
if (entity.state === "home") {
|
||||
return;
|
||||
}
|
||||
if (!getEntityLocation(entity, this.hass!.states)) {
|
||||
if (
|
||||
entity.state === "home" ||
|
||||
!("latitude" in entity.attributes) ||
|
||||
!("longitude" in entity.attributes)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
locationEntities.push(entity.entity_id);
|
||||
|
||||
@@ -4139,17 +4139,6 @@
|
||||
"gas": "Gas",
|
||||
"water": "Water"
|
||||
},
|
||||
"customise": {
|
||||
"toolbar_action": "Customize cards",
|
||||
"title": "Customize energy",
|
||||
"saved": "Energy dashboard updated",
|
||||
"save_failed": "Failed to save energy customization",
|
||||
"unavailable": "This card isn't shown because the energy source or device it needs isn't configured.",
|
||||
"groups": {
|
||||
"overview": "Overview",
|
||||
"now": "Now"
|
||||
}
|
||||
},
|
||||
"delete_source": "Are you sure you want to remove this source?",
|
||||
"delete_integration": "Are you sure you want to remove this integration? It will remove the entities it provides",
|
||||
"grid": {
|
||||
@@ -10160,13 +10149,11 @@
|
||||
},
|
||||
"media-player-sound-mode": {
|
||||
"label": "Media player sound mode",
|
||||
"sound_modes": "Sound modes",
|
||||
"customize": "Customize sound modes"
|
||||
"sound_modes": "Sound modes"
|
||||
},
|
||||
"media-player-source": {
|
||||
"label": "Media player source",
|
||||
"sources": "Sources",
|
||||
"customize": "Customize sources"
|
||||
"sources": "Sources"
|
||||
},
|
||||
"media-player-volume-buttons": {
|
||||
"label": "Media player volume buttons",
|
||||
@@ -11207,12 +11194,7 @@
|
||||
"energy_top_consumers_title": "Top consumers",
|
||||
"power_sankey_title": "Current power flow",
|
||||
"water_flow_sankey_title": "Current water flow",
|
||||
"power_sources_graph_title": "Power sources",
|
||||
"energy_grid_balance_title": "Grid energy balance",
|
||||
"energy_grid_neutrality_gauge_title": "Grid neutrality gauge",
|
||||
"energy_solar_consumed_gauge_title": "Solar consumed gauge",
|
||||
"energy_self_sufficiency_gauge_title": "Self-sufficiency gauge",
|
||||
"energy_carbon_consumed_gauge_title": "Carbon consumed gauge"
|
||||
"power_sources_graph_title": "Power sources"
|
||||
}
|
||||
},
|
||||
"history": {
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type {
|
||||
EnergyPreferences,
|
||||
EnergySource,
|
||||
} from "../../../../src/data/energy";
|
||||
import {
|
||||
applicableEnergyCardKeys,
|
||||
ENERGY_CARD_CATALOG,
|
||||
energyCardKey,
|
||||
isEnergyCardHidden,
|
||||
isEnergyViewEmpty,
|
||||
} from "../../../../src/panels/energy/strategies/energy-cards";
|
||||
|
||||
const source = (s: Partial<EnergySource> & { type: string }): EnergySource =>
|
||||
s as unknown as EnergySource;
|
||||
|
||||
const makePrefs = (
|
||||
prefs: Partial<EnergyPreferences> = {}
|
||||
): EnergyPreferences => ({
|
||||
energy_sources: [],
|
||||
device_consumption: [],
|
||||
device_consumption_water: [],
|
||||
...prefs,
|
||||
});
|
||||
|
||||
const GRID_RETURN = source({
|
||||
type: "grid",
|
||||
stat_energy_from: "sensor.grid_in",
|
||||
stat_energy_to: "sensor.grid_out",
|
||||
});
|
||||
const SOLAR = source({ type: "solar", stat_energy_from: "sensor.solar" });
|
||||
const GAS = source({ type: "gas", stat_energy_from: "sensor.gas" });
|
||||
const WATER = source({ type: "water", stat_energy_from: "sensor.water" });
|
||||
|
||||
describe("energyCardKey", () => {
|
||||
it("joins the view path and card type", () => {
|
||||
expect(energyCardKey("electricity", "energy-solar-graph")).toBe(
|
||||
"electricity.energy-solar-graph"
|
||||
);
|
||||
expect(energyCardKey("now", "power-sankey")).toBe("now.power-sankey");
|
||||
});
|
||||
});
|
||||
|
||||
describe("isEnergyCardHidden", () => {
|
||||
it("returns true only when the composite key is in the hidden list", () => {
|
||||
const hidden = ["electricity.energy-solar-graph"];
|
||||
expect(
|
||||
isEnergyCardHidden("electricity", "energy-solar-graph", hidden)
|
||||
).toBe(true);
|
||||
// Same card type in a different view is independent.
|
||||
expect(isEnergyCardHidden("overview", "energy-solar-graph", hidden)).toBe(
|
||||
false
|
||||
);
|
||||
expect(
|
||||
isEnergyCardHidden("electricity", "energy-usage-graph", hidden)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("treats undefined/empty hidden lists as nothing hidden", () => {
|
||||
expect(
|
||||
isEnergyCardHidden("electricity", "energy-solar-graph", undefined)
|
||||
).toBe(false);
|
||||
expect(isEnergyCardHidden("electricity", "energy-solar-graph", [])).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("catalog applicability", () => {
|
||||
it("only lists cards relevant to the configured sources", () => {
|
||||
const gasOnly = makePrefs({ energy_sources: [GAS] });
|
||||
expect(applicableEnergyCardKeys("gas", gasOnly)).toEqual([
|
||||
"gas.energy-gas-graph",
|
||||
"gas.energy-sources-table",
|
||||
]);
|
||||
// No electricity sources -> no electricity cards apply.
|
||||
expect(applicableEnergyCardKeys("electricity", gasOnly)).toEqual([]);
|
||||
});
|
||||
|
||||
it("gates the solar graph and gauges on their sources", () => {
|
||||
const solarGraph = ENERGY_CARD_CATALOG.find(
|
||||
(c) => c.key === "electricity.energy-solar-graph"
|
||||
)!;
|
||||
expect(
|
||||
solarGraph.isApplicable(makePrefs({ energy_sources: [SOLAR] }))
|
||||
).toBe(true);
|
||||
expect(
|
||||
solarGraph.isApplicable(makePrefs({ energy_sources: [GRID_RETURN] }))
|
||||
).toBe(false);
|
||||
|
||||
const neutralityGauge = ENERGY_CARD_CATALOG.find(
|
||||
(c) => c.key === "electricity.energy-grid-neutrality-gauge"
|
||||
)!;
|
||||
// Needs grid export (return).
|
||||
expect(
|
||||
neutralityGauge.isApplicable(makePrefs({ energy_sources: [GRID_RETURN] }))
|
||||
).toBe(true);
|
||||
expect(
|
||||
neutralityGauge.isApplicable(makePrefs({ energy_sources: [SOLAR] }))
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isEnergyViewEmpty", () => {
|
||||
const prefs = makePrefs({ energy_sources: [WATER] });
|
||||
|
||||
it("is false when no cards in the view are hidden", () => {
|
||||
expect(isEnergyViewEmpty("water", prefs, undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it("is false when only some applicable cards are hidden", () => {
|
||||
expect(
|
||||
isEnergyViewEmpty("water", prefs, ["water.energy-water-graph"])
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("is true when every applicable card is hidden", () => {
|
||||
expect(
|
||||
isEnergyViewEmpty("water", prefs, [
|
||||
"water.energy-water-graph",
|
||||
"water.energy-sources-table",
|
||||
])
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("is false when the view has no applicable cards at all", () => {
|
||||
// Water source configured, but the gas view has nothing applicable.
|
||||
expect(isEnergyViewEmpty("gas", prefs, [])).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -4041,161 +4041,161 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/basic@npm:4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "@tsparticles/basic@npm:4.1.2"
|
||||
"@tsparticles/basic@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/basic@npm:4.1.1"
|
||||
dependencies:
|
||||
"@tsparticles/engine": "npm:4.1.2"
|
||||
"@tsparticles/plugin-blend": "npm:4.1.2"
|
||||
"@tsparticles/plugin-hex-color": "npm:4.1.2"
|
||||
"@tsparticles/plugin-hsl-color": "npm:4.1.2"
|
||||
"@tsparticles/plugin-move": "npm:4.1.2"
|
||||
"@tsparticles/plugin-rgb-color": "npm:4.1.2"
|
||||
"@tsparticles/shape-circle": "npm:4.1.2"
|
||||
"@tsparticles/updater-opacity": "npm:4.1.2"
|
||||
"@tsparticles/updater-out-modes": "npm:4.1.2"
|
||||
"@tsparticles/updater-paint": "npm:4.1.2"
|
||||
"@tsparticles/updater-size": "npm:4.1.2"
|
||||
checksum: 10/1c145d25373562cd3b45f20664610226c050a0a6867396c2d138a76761d3e7a5796cf107d8bdcbb8eb8cca19a3a1e4192eb356d37387d6547bf6b1ff796b71b2
|
||||
"@tsparticles/engine": "npm:4.1.1"
|
||||
"@tsparticles/plugin-blend": "npm:4.1.1"
|
||||
"@tsparticles/plugin-hex-color": "npm:4.1.1"
|
||||
"@tsparticles/plugin-hsl-color": "npm:4.1.1"
|
||||
"@tsparticles/plugin-move": "npm:4.1.1"
|
||||
"@tsparticles/plugin-rgb-color": "npm:4.1.1"
|
||||
"@tsparticles/shape-circle": "npm:4.1.1"
|
||||
"@tsparticles/updater-opacity": "npm:4.1.1"
|
||||
"@tsparticles/updater-out-modes": "npm:4.1.1"
|
||||
"@tsparticles/updater-paint": "npm:4.1.1"
|
||||
"@tsparticles/updater-size": "npm:4.1.1"
|
||||
checksum: 10/99191aeee4b9a3856aa82a2cea21e54d2099b6d58b4af3bacf4bb133277bd71de16ac07fe632ebabe08cc2a06be1aa6c00d82d593027276bf588870afc9182f5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/canvas-utils@npm:4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "@tsparticles/canvas-utils@npm:4.1.2"
|
||||
"@tsparticles/canvas-utils@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/canvas-utils@npm:4.1.1"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.2
|
||||
checksum: 10/ffedc8400b5ff758331bdf1ef2362aac713c3e41d5bcece00153acc04c725947c41d545202d73bd6ef290d2ec220353f1801fb4ad22e99870989b0c68d7540c4
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/2b5d7e9a55aa8086007f2dff940800c650233751ebb708fdbf9f91be4b0cd9d975400da42cdf531bb74860974dff5ea3c76199520ccd3f089904fba0d1bc0722
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/engine@npm:4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "@tsparticles/engine@npm:4.1.2"
|
||||
checksum: 10/6fe6aa50bba564a8a8691945cb378267e695ec58323066a4157e8a036e7a3caa99e1ced08d05bffd173045bbc33c4d41523afc28624b1bea015393c1a88ef2bb
|
||||
"@tsparticles/engine@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/engine@npm:4.1.1"
|
||||
checksum: 10/74886de63046f8752515f097176cae2fa8d506fd9d2d6a84106d43a89ff688e047d99a5018e56e3fffc17d5d13a9061f63fafcb2b4ebe773f78379621d0d9855
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/interaction-particles-links@npm:4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "@tsparticles/interaction-particles-links@npm:4.1.2"
|
||||
"@tsparticles/interaction-particles-links@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/interaction-particles-links@npm:4.1.1"
|
||||
dependencies:
|
||||
"@tsparticles/canvas-utils": "npm:4.1.2"
|
||||
"@tsparticles/canvas-utils": "npm:4.1.1"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.2
|
||||
"@tsparticles/plugin-interactivity": 4.1.2
|
||||
checksum: 10/b9e1e85bdb3226a25d7a4486556abfe458c81c109855c04857623d2d3506ef325fcc2dc4fcee962414e216aa6346bc0849df1bf7199a3e4e75bed2fb7f069702
|
||||
"@tsparticles/engine": 4.1.1
|
||||
"@tsparticles/plugin-interactivity": 4.1.1
|
||||
checksum: 10/c0c7ad3740f2168b75ca7ae09341fefcc1d8c8d117fbee7a6519a622843dfe5d57bb65113a4eba40f26906904f069cbac196840f45fc7be12864fddfa8106184
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-blend@npm:4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "@tsparticles/plugin-blend@npm:4.1.2"
|
||||
"@tsparticles/plugin-blend@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/plugin-blend@npm:4.1.1"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.2
|
||||
checksum: 10/1f51bcf76d6d749a0a18799fd3a814a82e62f060fd3c20d28f8bb170311815aeba8efc361e73cef7356c51151c7debafc75d34bff6ab2d4e58099b51774a28c6
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/9a666dc1fe0ed9ff4743fc23ca3bf27bd5d66073cc0127203b566077d431321b61af440875e96c87950e18011b1e6b9b43d328e4200923dd4be9fd934c07a5cd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-hex-color@npm:4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "@tsparticles/plugin-hex-color@npm:4.1.2"
|
||||
"@tsparticles/plugin-hex-color@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/plugin-hex-color@npm:4.1.1"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.2
|
||||
checksum: 10/d32a39bbd6732b9e630f84a7d226753735f02d65b70a1d5d11fa680421d5f1378aee75af1ecb5ada5af9875822bbaf2b5d1f213d1e0cbce2a50820743eb9c2fc
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/33fde7c2763affb1dd7fa033431a2553646bde79c9ab52106be6226a9716ae973c90c8b4bf381d96f661ab75467934019cd456fe27ccac59dca11b5c989c3a75
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-hsl-color@npm:4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "@tsparticles/plugin-hsl-color@npm:4.1.2"
|
||||
"@tsparticles/plugin-hsl-color@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/plugin-hsl-color@npm:4.1.1"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.2
|
||||
checksum: 10/c130e80b0fd2750402fa0e309d0bb624b2138474069ae4ccb76610722c4f0966f278ad1a35c85fbcf5449914936f1c53322246f1cdd028106d83f779beee538c
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/594f4a840f6f6f134a11d76c1a7fa80ed60a5d945534aad53e8ab5718341187f0fac73eb45d1c39246b98e982f1d60f9dbb95a083edcf8407e015048056b84e7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-interactivity@npm:4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "@tsparticles/plugin-interactivity@npm:4.1.2"
|
||||
"@tsparticles/plugin-interactivity@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/plugin-interactivity@npm:4.1.1"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.2
|
||||
checksum: 10/78efd4ffe3a07752f26262fe27ee0a931b45fea6c81d62e156d8f965dc22a2141d64395eb99477624ca9aac9e152f9e2585fc8aced9c822c61cb22ecfad184a4
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/448bd8f7c741ed0359ace49a6c58d58947d95660158a131c86c248427b9caa41645c6e8c9cea6a8f0f6a70cd25c0dd64017edba79c8f225b6db07f39b660eb4d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-move@npm:4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "@tsparticles/plugin-move@npm:4.1.2"
|
||||
"@tsparticles/plugin-move@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/plugin-move@npm:4.1.1"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.2
|
||||
checksum: 10/fac5b90904d3fa62b310a1ce19647c5ae4a592111b5eeccfc6e946c6d17d677b0f3931ba3c114f04045827a92c1a0ed66791e99b352cb75d6c50f3749f389bfc
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/b854cb6e2dcea2971f1abaca75c6e8cde40611178f13513559f60cc45da568e8b61c2f027619041d94e11c60af08a76eb4957d344a910b7a6255265937199094
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-rgb-color@npm:4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "@tsparticles/plugin-rgb-color@npm:4.1.2"
|
||||
"@tsparticles/plugin-rgb-color@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/plugin-rgb-color@npm:4.1.1"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.2
|
||||
checksum: 10/7e3e83171f74f7e9e3f68ce8d0aeb4685b277ecac3c4874a951e86228d3db78978d6197ef9e04b17190471294323c561c18c92c9d9ae8e8a115e6238cbecdb43
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/2372dfe5ceec163b49c02b3b0169e6032fae34e4ade3c29d42bd26af23fac76b3416d70eed9cac9a0f2b470c763ee903d12d0e9156b7693b3c8908e25714cf76
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/preset-links@npm:4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "@tsparticles/preset-links@npm:4.1.2"
|
||||
"@tsparticles/preset-links@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/preset-links@npm:4.1.1"
|
||||
dependencies:
|
||||
"@tsparticles/basic": "npm:4.1.2"
|
||||
"@tsparticles/engine": "npm:4.1.2"
|
||||
"@tsparticles/interaction-particles-links": "npm:4.1.2"
|
||||
"@tsparticles/plugin-interactivity": "npm:4.1.2"
|
||||
checksum: 10/d907884c4e9fd023aa8e0e1ff2388ef24d06a446433cc4ffbd620a4d22801f52ce97fe5f441b9fdea854f36d6f421d7571648925e5ea5c0976c121194156a2b1
|
||||
"@tsparticles/basic": "npm:4.1.1"
|
||||
"@tsparticles/engine": "npm:4.1.1"
|
||||
"@tsparticles/interaction-particles-links": "npm:4.1.1"
|
||||
"@tsparticles/plugin-interactivity": "npm:4.1.1"
|
||||
checksum: 10/26dd1dcd4ede3bae32cae3585a7f929e0089a75e389f83cb5b00213e977b647c3e7e743b5e83d3a5400beffcff9343bde7538412579708bad761d2e933708638
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/shape-circle@npm:4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "@tsparticles/shape-circle@npm:4.1.2"
|
||||
"@tsparticles/shape-circle@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/shape-circle@npm:4.1.1"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.2
|
||||
checksum: 10/60a87755d4e598c278c1850b58b07bcfbef603de242de3979702cd8120cf24fd95d985b2092c7a8a2cccd43eb6a1939ab215f74a93898ea1e2d425be5deb405c
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/401e8a267cf9301dae8386e9bb1c5ff3a02dfb5b5f136187c73ed6b89e33f176f929fdc53acaa60b13f3ee1ed7f8c6f35f3f0e26ff6930a33d1949a0f23e4c1f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-opacity@npm:4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "@tsparticles/updater-opacity@npm:4.1.2"
|
||||
"@tsparticles/updater-opacity@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/updater-opacity@npm:4.1.1"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.2
|
||||
checksum: 10/4dc426175bb3e4c5d3bc35e7d83ace7130c4f8817c7f88ee6bf1b4b1a52b28511d99b1fc7ded3c549a5baae401f7c22cc701d83919694387154aef86ea6974c2
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/3fe157203e02dfec1ef9ac693d517cd9abcad8c28acaac8d4ca923b8301a99ae73129ed4559ea507c0d7d6ad626bdbbdb03f25868d7d28f852e2d8ffc7d13790
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-out-modes@npm:4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "@tsparticles/updater-out-modes@npm:4.1.2"
|
||||
"@tsparticles/updater-out-modes@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/updater-out-modes@npm:4.1.1"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.2
|
||||
checksum: 10/901be7b7dfa97d3d4ff857715a6b61dd4bc2ca09f23fee38d86cd6827351134c5cd4c3fa702d2aec8601544b1f6935f2b4034c4567a00128f9cf36cbaf379722
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/bc5f074661c42acc20a87ee3ff093fe63d2fd87a7f7d3156c2fb23dc1d24881ccc9e9cb82313ff7265317fcdc447a5b17b2204930faf8ba627278e2dc0fab2c3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-paint@npm:4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "@tsparticles/updater-paint@npm:4.1.2"
|
||||
"@tsparticles/updater-paint@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/updater-paint@npm:4.1.1"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.2
|
||||
checksum: 10/5a0b4bc0a4c6061e1eb93a6af7a97b472e5d552677dafe8c888b0f9a71b68ed10ef255d86f2cf8d8466ecd1d33ba8f5e52920947237fe1218cbcd002c50dff47
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/77459f6337b869d654573696dbe2bc1d238ddf3410857d8b91df606f7402301acae34f4a334727faa552f9816f518353b7a98e0bda4854ea5419cb34e8d447d2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-size@npm:4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "@tsparticles/updater-size@npm:4.1.2"
|
||||
"@tsparticles/updater-size@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/updater-size@npm:4.1.1"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.2
|
||||
checksum: 10/d5c4f79d5c1d3d977dd5ebac56c9679df134ddba919b8e2ee1473820f2c551f5b162356b49b22e6e54134b87a27c51cb5a6c2e2441ba2569b0dab62974e91a47
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/33ae9d51394e299459478a9dedd369de1bd266ad9c2e2236ec2c20bb455aad9118efa13afc7d359b6d7d010a34bba5b60cbf2e3d8b853e124c0b53c2aae8db18
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4548,105 +4548,105 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/eslint-plugin@npm:8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.60.1"
|
||||
"@typescript-eslint/eslint-plugin@npm:8.60.0":
|
||||
version: 8.60.0
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.60.0"
|
||||
dependencies:
|
||||
"@eslint-community/regexpp": "npm:^4.12.2"
|
||||
"@typescript-eslint/scope-manager": "npm:8.60.1"
|
||||
"@typescript-eslint/type-utils": "npm:8.60.1"
|
||||
"@typescript-eslint/utils": "npm:8.60.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.60.1"
|
||||
"@typescript-eslint/scope-manager": "npm:8.60.0"
|
||||
"@typescript-eslint/type-utils": "npm:8.60.0"
|
||||
"@typescript-eslint/utils": "npm:8.60.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.60.0"
|
||||
ignore: "npm:^7.0.5"
|
||||
natural-compare: "npm:^1.4.0"
|
||||
ts-api-utils: "npm:^2.5.0"
|
||||
peerDependencies:
|
||||
"@typescript-eslint/parser": ^8.60.1
|
||||
"@typescript-eslint/parser": ^8.60.0
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/f3633bb2700bc32299578baeaf6650418656229be256147ba9d1ab09b34ef2b7fed83804ef4d2439e9189dbdcb89399d67bc8fea55262be6caa32114be048538
|
||||
checksum: 10/aec6f08be04ad0014c80e5cf3bd8ec83d59c44244c9ca357c4cf182b6f0debdd690e64daa88215e937183e97c4bdee6749dbf4162191c5851ae9c648439c8a96
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/parser@npm:8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/parser@npm:8.60.1"
|
||||
"@typescript-eslint/parser@npm:8.60.0":
|
||||
version: 8.60.0
|
||||
resolution: "@typescript-eslint/parser@npm:8.60.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager": "npm:8.60.1"
|
||||
"@typescript-eslint/types": "npm:8.60.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.60.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.60.1"
|
||||
"@typescript-eslint/scope-manager": "npm:8.60.0"
|
||||
"@typescript-eslint/types": "npm:8.60.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.60.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.60.0"
|
||||
debug: "npm:^4.4.3"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/f9c484c4a3897015328f328a1c6ee778d113dd134201f635c0421cb72efe6e63f3a68524aff0df6e19e76ff93daf5cabd946e67f12f10dddcf19bda534aa68dc
|
||||
checksum: 10/f55fa3547e3d0a0ec88bcb886b9bf6cef9b425c016dfa47e2ad7fbcbaa854640ba3f501cc0115824b58f33be4bf8bdf544505847988688906d11c154b600c54d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/project-service@npm:8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/project-service@npm:8.60.1"
|
||||
"@typescript-eslint/project-service@npm:8.60.0":
|
||||
version: 8.60.0
|
||||
resolution: "@typescript-eslint/project-service@npm:8.60.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.60.1"
|
||||
"@typescript-eslint/types": "npm:^8.60.1"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.60.0"
|
||||
"@typescript-eslint/types": "npm:^8.60.0"
|
||||
debug: "npm:^4.4.3"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/fec693dd79c3a1e6a24091127a37af4eb9d9cee8192cf2a434adae48543eadff834bc0623b5b563c8b592b250bc080570f9e7b42807252ea898442c525beeee9
|
||||
checksum: 10/21e233d1292775753861aad32b30448f9fb5508f53d5a12c8ce7e75613df236757377fa877c738cc858ac863f2f8259a1f63bfb15a32ee9c5476fe9b2d12fbb0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/scope-manager@npm:8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.60.1"
|
||||
"@typescript-eslint/scope-manager@npm:8.60.0":
|
||||
version: 8.60.0
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.60.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.60.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.60.1"
|
||||
checksum: 10/7228c110410ff8cfc01e96d8f17c986f8b4dd447fe3a3291baaab8fe946026ccdf0291865f788f18cf538ab49bfc067fe797708b6b8590104a65f7e69f921cc5
|
||||
"@typescript-eslint/types": "npm:8.60.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.60.0"
|
||||
checksum: 10/c08274fdb38be51d2d655ee32bed271cfedf5f5775709da98b3d6cf5f7eb419e98228fb087b48f4a591f4dd71ebcb27a8bd716fa831442c7cad708288625e454
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.60.1, @typescript-eslint/tsconfig-utils@npm:^8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.60.1"
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.60.0, @typescript-eslint/tsconfig-utils@npm:^8.60.0":
|
||||
version: 8.60.0
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.60.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/afc78b19b856a71dc4e493f931ae44e1a91dc6441a14cb92e4063db880892f3874768f9d347d4b2f45362f2090e4455407c70f42027d77ddc85d6cba95cdb76c
|
||||
checksum: 10/d82cac7dec0366c0e680d002b4d20bc2564a198a2d9a80099f4fa7ee2b2f394dd2d47df03f1c4b276c4de6c7b8684a50e7bad0ddd5b33907188e90cc203a9593
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/type-utils@npm:8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.60.1"
|
||||
"@typescript-eslint/type-utils@npm:8.60.0":
|
||||
version: 8.60.0
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.60.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.60.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.60.1"
|
||||
"@typescript-eslint/utils": "npm:8.60.1"
|
||||
"@typescript-eslint/types": "npm:8.60.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.60.0"
|
||||
"@typescript-eslint/utils": "npm:8.60.0"
|
||||
debug: "npm:^4.4.3"
|
||||
ts-api-utils: "npm:^2.5.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/6f426263be597063831bf308e52328e8d387af5db955a09cb85fde1c72f5b1b36a365133b9c9a74330e5e948e59bf9a9b82605f4c9c4e3bf9b6cb7f4c37e4b18
|
||||
checksum: 10/4b29dcc1ee7a006b2df8a50700b43701bedd4f8380e94311a8988102d98fdd89244c233a8063a800cbdee86278bdc98874bfa6a8a3c71f1b278be1be6698961b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:8.60.1, @typescript-eslint/types@npm:^8.56.0, @typescript-eslint/types@npm:^8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/types@npm:8.60.1"
|
||||
checksum: 10/c603417e621b5b1263c2f60fad9e202d560fd07fce7f40e9a356c0530e5eaf0ff1a9af865237bf93aa18a5a4e2f034ee0cce0fe6c070f08df33e35a099bdea47
|
||||
"@typescript-eslint/types@npm:8.60.0, @typescript-eslint/types@npm:^8.56.0, @typescript-eslint/types@npm:^8.60.0":
|
||||
version: 8.60.0
|
||||
resolution: "@typescript-eslint/types@npm:8.60.0"
|
||||
checksum: 10/8c6967503b3a370af10fea7bfec9adc7a4152e0e8aaa72ee790f105f08721683f6e8829acf610de82bfcdeb56bdf07f6795ccec394edbdac222fd3a1d76fe9cd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.60.1"
|
||||
"@typescript-eslint/typescript-estree@npm:8.60.0":
|
||||
version: 8.60.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.60.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service": "npm:8.60.1"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.60.1"
|
||||
"@typescript-eslint/types": "npm:8.60.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.60.1"
|
||||
"@typescript-eslint/project-service": "npm:8.60.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.60.0"
|
||||
"@typescript-eslint/types": "npm:8.60.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.60.0"
|
||||
debug: "npm:^4.4.3"
|
||||
minimatch: "npm:^10.2.2"
|
||||
semver: "npm:^7.7.3"
|
||||
@@ -4654,32 +4654,32 @@ __metadata:
|
||||
ts-api-utils: "npm:^2.5.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/9c3a56266aadf589bc6e770cd04cb3f55b1ee1507dcacda61866408c656ae4462aa7e11baf39eb939bc4d1e3b843cf58e60f3ebdeb3e75f042ff0f6fb39c311b
|
||||
checksum: 10/ad02384fd48152a7d9bb5db1aa5d6cbda1cfa9e549a2d529d801ec1401d1d7d011c5e071f5b4d99c5ed656c95e5e97c46a783b45dcc7c016df7fee37ab5bdc0a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/utils@npm:8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/utils@npm:8.60.1"
|
||||
"@typescript-eslint/utils@npm:8.60.0":
|
||||
version: 8.60.0
|
||||
resolution: "@typescript-eslint/utils@npm:8.60.0"
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils": "npm:^4.9.1"
|
||||
"@typescript-eslint/scope-manager": "npm:8.60.1"
|
||||
"@typescript-eslint/types": "npm:8.60.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.60.1"
|
||||
"@typescript-eslint/scope-manager": "npm:8.60.0"
|
||||
"@typescript-eslint/types": "npm:8.60.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.60.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/a75f8714995b6280b4c15ca957bbc6634862453461111e4a2a07b8bc72b51a504484a9b957fc5b7a646c4bf09f1e414a0c52cd3b6798c42fb8c4de83b1b5a364
|
||||
checksum: 10/9fc8bc7a62deacd3823d957de8e8ca2012ffa90715734cd89d0e3a62c2c9e2775d3ba9da80e419339893a44af8674d690488cb195c981e8de9fd9dfa4948956d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.60.1"
|
||||
"@typescript-eslint/visitor-keys@npm:8.60.0":
|
||||
version: 8.60.0
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.60.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.60.1"
|
||||
"@typescript-eslint/types": "npm:8.60.0"
|
||||
eslint-visitor-keys: "npm:^5.0.0"
|
||||
checksum: 10/6d120b4a790477ae0291e69f6457686c71b929cc40519148f6b6c7fbc09604b15821ae8cf1005aa23acec5105b4016db256a68d68f30eda8d6c24d4fdb0ede86
|
||||
checksum: 10/4854d08416e2c97837cc1ecf8dacb50b3337ebb34bd6d703ad40b6585fdf78243074e56994ddc90650586146cebd6ad7390b6fa3ddda4e3532be4b872dd8f541
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5641,12 +5641,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"barcode-detector@npm:3.2.0":
|
||||
version: 3.2.0
|
||||
resolution: "barcode-detector@npm:3.2.0"
|
||||
"barcode-detector@npm:3.1.3":
|
||||
version: 3.1.3
|
||||
resolution: "barcode-detector@npm:3.1.3"
|
||||
dependencies:
|
||||
zxing-wasm: "npm:3.1.0"
|
||||
checksum: 10/f52eb18ddae2af3d4c9c76b47e7b639d0834cd32f558901d8a23cc00349047e53a6da5c3958653fa524dcb912ed9178f3e0d37939b9be00f9607772a84d90ccf
|
||||
zxing-wasm: "npm:3.0.3"
|
||||
checksum: 10/3e33b00bdc4b6f6bae67ca2a2fbe7def8861060591bf5a46ece6a8f30eadc9c66ce32776633107bb704ad0910302845a6bd520045e169410ec7ad8d4346633a7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -7957,10 +7957,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fuse.js@npm:7.4.1":
|
||||
version: 7.4.1
|
||||
resolution: "fuse.js@npm:7.4.1"
|
||||
checksum: 10/581941d5015968ee624feb10a56d9b49d5d954672b2c9ec189d4ca513da6f8a3dea2d5f6637386d8298ffc5846f6d83435210d40a47c58e14d11dc5707544c75
|
||||
"fuse.js@npm:7.4.0":
|
||||
version: 7.4.0
|
||||
resolution: "fuse.js@npm:7.4.0"
|
||||
checksum: 10/dba0ef239be1f28ba5daefb3a17371c73291f4d0db3d1733b625848a7311e05aa58a795cd5b2fd9626c09857608a74e8c9620f5d1ce2d3d0b2d40155ae15e21e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8487,8 +8487,8 @@ __metadata:
|
||||
"@rspack/dev-server": "npm:2.0.3"
|
||||
"@swc/helpers": "npm:0.5.23"
|
||||
"@thomasloven/round-slider": "npm:0.6.0"
|
||||
"@tsparticles/engine": "npm:4.1.2"
|
||||
"@tsparticles/preset-links": "npm:4.1.2"
|
||||
"@tsparticles/engine": "npm:4.1.1"
|
||||
"@tsparticles/preset-links": "npm:4.1.1"
|
||||
"@types/chromecast-caf-receiver": "npm:6.0.26"
|
||||
"@types/chromecast-caf-sender": "npm:1.0.11"
|
||||
"@types/color-name": "npm:2.0.0"
|
||||
@@ -8509,7 +8509,7 @@ __metadata:
|
||||
"@webcomponents/webcomponentsjs": "npm:2.8.0"
|
||||
babel-loader: "npm:10.1.1"
|
||||
babel-plugin-template-html-minifier: "npm:4.1.0"
|
||||
barcode-detector: "npm:3.2.0"
|
||||
barcode-detector: "npm:3.1.3"
|
||||
browserslist-useragent-regexp: "npm:4.1.4"
|
||||
cally: "npm:0.9.2"
|
||||
color-name: "npm:2.1.0"
|
||||
@@ -8534,7 +8534,7 @@ __metadata:
|
||||
eslint-plugin-wc: "npm:3.1.0"
|
||||
fancy-log: "npm:2.0.0"
|
||||
fs-extra: "npm:11.3.5"
|
||||
fuse.js: "npm:7.4.1"
|
||||
fuse.js: "npm:7.4.0"
|
||||
generate-license-file: "npm:4.2.1"
|
||||
glob: "npm:13.0.6"
|
||||
globals: "npm:17.6.0"
|
||||
@@ -8582,12 +8582,12 @@ __metadata:
|
||||
sortablejs: "patch:sortablejs@npm%3A1.15.6#~/.yarn/patches/sortablejs-npm-1.15.6-3235a8f83b.patch"
|
||||
stacktrace-js: "npm:2.0.2"
|
||||
superstruct: "npm:2.0.2"
|
||||
tar: "npm:7.5.16"
|
||||
tar: "npm:7.5.15"
|
||||
terser-webpack-plugin: "npm:5.6.1"
|
||||
tinykeys: "patch:tinykeys@npm%3A4.0.0#~/.yarn/patches/tinykeys-npm-4.0.0-a6ca3fd771.patch"
|
||||
tinykeys: "npm:4.0.0"
|
||||
ts-lit-plugin: "npm:2.0.2"
|
||||
typescript: "npm:6.0.3"
|
||||
typescript-eslint: "npm:8.60.1"
|
||||
typescript-eslint: "npm:8.60.0"
|
||||
vite-tsconfig-paths: "npm:6.1.1"
|
||||
vitest: "npm:4.1.8"
|
||||
webpack-stats-plugin: "npm:1.1.3"
|
||||
@@ -12999,16 +12999,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tar@npm:*, tar@npm:7.5.16, tar@npm:^7.4.3, tar@npm:^7.5.4":
|
||||
version: 7.5.16
|
||||
resolution: "tar@npm:7.5.16"
|
||||
"tar@npm:*, tar@npm:7.5.15, tar@npm:^7.4.3, tar@npm:^7.5.4":
|
||||
version: 7.5.15
|
||||
resolution: "tar@npm:7.5.15"
|
||||
dependencies:
|
||||
"@isaacs/fs-minipass": "npm:^4.0.0"
|
||||
chownr: "npm:^3.0.0"
|
||||
minipass: "npm:^7.1.2"
|
||||
minizlib: "npm:^3.1.0"
|
||||
yallist: "npm:^5.0.0"
|
||||
checksum: 10/fafa22efceb9f056bf29ddc47d9bd90bb82fe3ce57b8d1242fc45771251741964cebba69d4e14a24fd1643f3c7f68478e945a19def534703cf370c2d9dca2e09
|
||||
checksum: 10/b4cb6acd822159867f81ebda8d765c6941ec8292f1cf2f870d3713f4933c14bf0ed7bf4a92338143c31e8815ca0a1fdd62aa03ddb48a42ae187f7ef696583ffe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -13196,13 +13196,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tinykeys@patch:tinykeys@npm%3A4.0.0#~/.yarn/patches/tinykeys-npm-4.0.0-a6ca3fd771.patch":
|
||||
version: 4.0.0
|
||||
resolution: "tinykeys@patch:tinykeys@npm%3A4.0.0#~/.yarn/patches/tinykeys-npm-4.0.0-a6ca3fd771.patch::version=4.0.0&hash=267e16"
|
||||
checksum: 10/f1ca5d1eef7fc628ba55c53da218869db0d6efc6ba28ebbc4974476ff6f2a29ec34cd584008eabc6f03453cfcd4c7fc59b51d8fbca355fd30a8e784eb3c9bc8d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tinyrainbow@npm:^3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "tinyrainbow@npm:3.1.0"
|
||||
@@ -13399,12 +13392,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"type-fest@npm:^5.7.0":
|
||||
version: 5.7.0
|
||||
resolution: "type-fest@npm:5.7.0"
|
||||
"type-fest@npm:^5.6.0":
|
||||
version: 5.6.0
|
||||
resolution: "type-fest@npm:5.6.0"
|
||||
dependencies:
|
||||
tagged-tag: "npm:^1.0.0"
|
||||
checksum: 10/4867626aa489968df98e09ecdefbc45dfbb191ae5fb8924b3bd45da9cd940879b387086226366dce028570983a3fbe80adc53ad105a169bbbd27621c496bd6f0
|
||||
checksum: 10/2cc7a510f46a538af7a365ed50cee10e51a69bd353ca66c2a432e172b90ff12efff9ba59c7ba493d6361d07fd298c84945b9e4481ab63f85a29860c0b0430233
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -13461,18 +13454,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript-eslint@npm:8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "typescript-eslint@npm:8.60.1"
|
||||
"typescript-eslint@npm:8.60.0":
|
||||
version: 8.60.0
|
||||
resolution: "typescript-eslint@npm:8.60.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.60.1"
|
||||
"@typescript-eslint/parser": "npm:8.60.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.60.1"
|
||||
"@typescript-eslint/utils": "npm:8.60.1"
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.60.0"
|
||||
"@typescript-eslint/parser": "npm:8.60.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.60.0"
|
||||
"@typescript-eslint/utils": "npm:8.60.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/e12091ab2540b817c76b0ec6aad92e341f810310bec2b24bc95780aee106049c05363998f6ea52ed066130c8afc41dca1627f56e4c1df1dd519f4d4ca0ce4910
|
||||
checksum: 10/625e49e6d06e32adcfe903087d1fb2adc3be925adafe1f4e57f690bb196b35e2aac01760a3d5e17a53ea2feb6fef3a13da4b8faa214f628ec56e64f99f20e4ad
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -14867,14 +14860,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zxing-wasm@npm:3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "zxing-wasm@npm:3.1.0"
|
||||
"zxing-wasm@npm:3.0.3":
|
||||
version: 3.0.3
|
||||
resolution: "zxing-wasm@npm:3.0.3"
|
||||
dependencies:
|
||||
"@types/emscripten": "npm:^1.41.5"
|
||||
type-fest: "npm:^5.7.0"
|
||||
type-fest: "npm:^5.6.0"
|
||||
peerDependencies:
|
||||
"@types/emscripten": ">=1.39.6"
|
||||
checksum: 10/ea68d0cfbe31d8dabcd9b942dcfdb703866c1f76ee0d804fb75f1e49f092a26771575705dd48d69927bc6525f146fd9e35030a5a72341222716d7f62e5f6c788
|
||||
checksum: 10/0c0636fab96a67d7f8540dede8d98b5cbb53c95313cfbe21114e2fd15262d6b860c84467d627d6e768bcdc684d75c7b5b8ef6b3d53d8d14004436e809a1d4b32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
Reference in New Issue
Block a user