mirror of
https://github.com/home-assistant/frontend.git
synced 2026-05-26 11:07:14 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 94aa5020da | |||
| a147fc4fee | |||
| a300085208 | |||
| 44989a6972 | |||
| 54a8e6c294 | |||
| bfec22d828 | |||
| cde6450cfc | |||
| ab39e70629 | |||
| 69f209e3c3 | |||
| f4c5561a54 | |||
| 5147937a6f | |||
| ee39605aa7 | |||
| 4af4f1dc51 | |||
| a2d8859d94 | |||
| afea8180c4 | |||
| b9c077489d | |||
| 440bb32056 | |||
| 8f371621ad | |||
| 61815b20e3 | |||
| 1942fa3a77 | |||
| 865e67a06f | |||
| 412dce4c1f | |||
| ced2ac7ad5 | |||
| 6649f52bcd | |||
| 7dbd6ae5a2 | |||
| e1528d21b3 | |||
| 79cb3137f2 | |||
| 313360701a |
@@ -41,14 +41,14 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -62,4 +62,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
|
||||
+248
-248
File diff suppressed because one or more lines are too long
+1
-1
@@ -13,4 +13,4 @@ nodeLinker: node-modules
|
||||
|
||||
npmMinimalAgeGate: 3d
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.14.1.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.15.0.cjs
|
||||
|
||||
+12
-12
@@ -38,7 +38,7 @@
|
||||
"@codemirror/search": "6.7.0",
|
||||
"@codemirror/state": "6.6.0",
|
||||
"@codemirror/view": "6.43.0",
|
||||
"@date-fns/tz": "1.4.1",
|
||||
"@date-fns/tz": "1.5.0",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "7.4.6",
|
||||
"@formatjs/intl-displaynames": "7.3.8",
|
||||
@@ -74,8 +74,8 @@
|
||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||
"@swc/helpers": "0.5.21",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@tsparticles/engine": "4.0.4",
|
||||
"@tsparticles/preset-links": "4.0.4",
|
||||
"@tsparticles/engine": "4.0.5",
|
||||
"@tsparticles/preset-links": "4.0.5",
|
||||
"@vibrant/color": "4.0.4",
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||
@@ -86,7 +86,7 @@
|
||||
"core-js": "3.49.0",
|
||||
"cropperjs": "1.6.2",
|
||||
"culori": "4.0.2",
|
||||
"date-fns": "4.2.1",
|
||||
"date-fns": "4.3.0",
|
||||
"deep-clone-simple": "1.1.1",
|
||||
"deep-freeze": "0.0.1",
|
||||
"dialog-polyfill": "0.5.6",
|
||||
@@ -97,8 +97,8 @@
|
||||
"gulp-zopfli-green": "7.0.0",
|
||||
"hls.js": "1.6.16",
|
||||
"home-assistant-js-websocket": "9.6.0",
|
||||
"idb-keyval": "6.2.2",
|
||||
"intl-messageformat": "11.2.6",
|
||||
"idb-keyval": "6.2.4",
|
||||
"intl-messageformat": "11.2.7",
|
||||
"js-yaml": "4.1.1",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
|
||||
@@ -118,7 +118,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": "3.0.0",
|
||||
"tinykeys": "4.0.0",
|
||||
"weekstart": "2.0.0",
|
||||
"workbox-cacheable-response": "7.4.1",
|
||||
"workbox-core": "7.4.1",
|
||||
@@ -141,7 +141,7 @@
|
||||
"@octokit/plugin-retry": "8.1.0",
|
||||
"@octokit/rest": "22.0.1",
|
||||
"@rsdoctor/rspack-plugin": "1.5.11",
|
||||
"@rspack/core": "2.0.3",
|
||||
"@rspack/core": "2.0.4",
|
||||
"@rspack/dev-server": "2.0.1",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.26",
|
||||
@@ -160,7 +160,7 @@
|
||||
"@types/sortablejs": "1.15.9",
|
||||
"@types/tar": "7.0.87",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@vitest/coverage-v8": "4.1.6",
|
||||
"@vitest/coverage-v8": "4.1.7",
|
||||
"babel-loader": "10.1.1",
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"browserslist-useragent-regexp": "4.1.4",
|
||||
@@ -175,7 +175,7 @@
|
||||
"eslint-plugin-wc": "3.1.0",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.3.5",
|
||||
"generate-license-file": "4.1.1",
|
||||
"generate-license-file": "4.2.1",
|
||||
"glob": "13.0.6",
|
||||
"globals": "17.6.0",
|
||||
"gulp": "5.0.1",
|
||||
@@ -203,7 +203,7 @@
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "8.59.4",
|
||||
"vite-tsconfig-paths": "6.1.1",
|
||||
"vitest": "4.1.6",
|
||||
"vitest": "4.1.7",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
"webpackbar": "7.0.0",
|
||||
"workbox-build": "patch:workbox-build@npm%3A7.4.1#~/.yarn/patches/workbox-build-npm-7.4.1-c84561662c.patch"
|
||||
@@ -219,7 +219,7 @@
|
||||
"@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
|
||||
"glob@^10.2.2": "^10.5.0"
|
||||
},
|
||||
"packageManager": "yarn@4.14.1",
|
||||
"packageManager": "yarn@4.15.0",
|
||||
"volta": {
|
||||
"node": "24.16.0"
|
||||
}
|
||||
|
||||
@@ -128,7 +128,9 @@ export class HaAutomationRow extends LitElement {
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
padding: 0 0 0 var(--ha-space-3);
|
||||
padding-left: var(--ha-space-3);
|
||||
padding-inline-start: var(--ha-space-3);
|
||||
padding-inline-end: initial;
|
||||
min-height: 48px;
|
||||
align-items: flex-start;
|
||||
cursor: pointer;
|
||||
@@ -144,6 +146,8 @@ export class HaAutomationRow extends LitElement {
|
||||
transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
color: var(--ha-color-on-neutral-quiet);
|
||||
margin-left: calc(var(--ha-space-2) * -1);
|
||||
margin-inline-start: calc(var(--ha-space-2) * -1);
|
||||
margin-inline-end: initial;
|
||||
}
|
||||
:host([building-block]) .leading-icon-wrapper {
|
||||
background-color: var(--ha-color-fill-neutral-loud-resting);
|
||||
|
||||
@@ -294,6 +294,7 @@ export class HaDrawer extends LitElement {
|
||||
border-inline-end: 1px solid var(--divider-color, rgba(0, 0, 0, 0.12));
|
||||
box-sizing: border-box;
|
||||
transition: width var(--ha-animation-duration-normal) ease;
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
.app-content {
|
||||
|
||||
@@ -109,6 +109,8 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
@property({ attribute: "custom-value-label" })
|
||||
public customValueLabel?: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "no-sort" }) public noSort = false;
|
||||
|
||||
@query(".container") private _containerElement?: HTMLDivElement;
|
||||
|
||||
@query("ha-picker-combo-box") private _comboBox?: HaPickerComboBox;
|
||||
@@ -271,6 +273,7 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
.selectedSection=${this.selectedSection}
|
||||
.searchKeys=${this.searchKeys}
|
||||
.customValueLabel=${this.customValueLabel}
|
||||
.noSort=${this.noSort}
|
||||
></ha-picker-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -167,6 +167,8 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public clearable = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "no-sort" }) public noSort = false;
|
||||
|
||||
@query("lit-virtualizer") public virtualizerElement?: LitVirtualizer;
|
||||
|
||||
@query("ha-input-search") private _searchFieldElement?: HaInputSearch;
|
||||
@@ -342,7 +344,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
private _getItems = () => {
|
||||
let items = [...(this.getItems(this._search, this._selectedSection) || [])];
|
||||
|
||||
if (!this.sections?.length) {
|
||||
if (!this.sections?.length && !this.noSort) {
|
||||
items = items.sort((entityA, entityB) => {
|
||||
const sortLabelA =
|
||||
typeof entityA === "string" ? entityA : entityA.sorting_label;
|
||||
|
||||
@@ -199,6 +199,7 @@ export class HaSelectSelector extends LitElement {
|
||||
: nothing}
|
||||
|
||||
<ha-generic-picker
|
||||
no-sort
|
||||
.hass=${this.hass}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
@@ -215,6 +216,7 @@ export class HaSelectSelector extends LitElement {
|
||||
if (this.selector.select?.custom_value) {
|
||||
return html`
|
||||
<ha-generic-picker
|
||||
no-sort
|
||||
.hass=${this.hass}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
|
||||
@@ -112,6 +112,7 @@ export class HaTileContainer extends LitElement {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.vertical ::slotted([slot="info"]) {
|
||||
width: 100%;
|
||||
|
||||
+11
-2
@@ -17,6 +17,7 @@ export interface BluetoothDeviceData extends DataTableRowData {
|
||||
source: string;
|
||||
time: number;
|
||||
tx_power: number;
|
||||
raw: string | null;
|
||||
}
|
||||
|
||||
export interface BluetoothConnectionData extends DataTableRowData {
|
||||
@@ -58,13 +59,21 @@ export interface BluetoothAllocationsData {
|
||||
allocated: string[];
|
||||
}
|
||||
|
||||
export type BluetoothScannerMode = "active" | "passive";
|
||||
|
||||
export type BluetoothScannerRequestedMode = BluetoothScannerMode | "auto";
|
||||
|
||||
export interface BluetoothScannerState {
|
||||
source: string;
|
||||
adapter: string;
|
||||
current_mode: "active" | "passive" | null;
|
||||
requested_mode: "active" | "passive" | null;
|
||||
current_mode: BluetoothScannerMode | null;
|
||||
requested_mode: BluetoothScannerRequestedMode | null;
|
||||
}
|
||||
|
||||
export const isScannerStateMismatch = (state: BluetoothScannerState): boolean =>
|
||||
state.requested_mode !== "auto" &&
|
||||
state.current_mode !== state.requested_mode;
|
||||
|
||||
export const subscribeBluetoothScannersDetailsUpdates = (
|
||||
conn: Connection,
|
||||
store: Store<BluetoothScannersDetails>
|
||||
|
||||
@@ -161,6 +161,10 @@ export interface VacuumEntityOptions {
|
||||
last_seen_segments?: Segment[];
|
||||
}
|
||||
|
||||
export interface DeviceTrackerEntityOptions {
|
||||
associated_zone?: string | null;
|
||||
}
|
||||
|
||||
export interface EntityRegistryOptions {
|
||||
number?: NumberEntityOptions;
|
||||
sensor?: SensorEntityOptions;
|
||||
@@ -172,6 +176,7 @@ export interface EntityRegistryOptions {
|
||||
cover?: CoverEntityOptions;
|
||||
valve?: ValveEntityOptions;
|
||||
vacuum?: VacuumEntityOptions;
|
||||
device_tracker?: DeviceTrackerEntityOptions;
|
||||
switch_as_x?: SwitchAsXEntityOptions;
|
||||
conversation?: Record<string, unknown>;
|
||||
"cloud.alexa"?: Record<string, unknown>;
|
||||
@@ -197,7 +202,8 @@ export interface EntityRegistryEntryUpdateParams {
|
||||
| LightEntityOptions
|
||||
| CoverEntityOptions
|
||||
| ValveEntityOptions
|
||||
| VacuumEntityOptions;
|
||||
| VacuumEntityOptions
|
||||
| DeviceTrackerEntityOptions;
|
||||
aliases?: (string | null)[];
|
||||
labels?: string[];
|
||||
categories?: Record<string, string | null>;
|
||||
|
||||
@@ -343,9 +343,9 @@ export class HaVoiceAssistantSetupDialog extends LitElement {
|
||||
this._step = this._previousSteps.pop()!;
|
||||
}
|
||||
|
||||
private _goToNextStep(ev?: CustomEvent) {
|
||||
private async _goToNextStep(ev?: CustomEvent) {
|
||||
if (ev?.detail?.updateConfig) {
|
||||
this._fetchAssistConfiguration();
|
||||
await this._fetchAssistConfiguration();
|
||||
}
|
||||
if (ev?.detail?.nextStep) {
|
||||
this._nextStep = ev.detail.nextStep;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { createRef, ref } from "lit/directives/ref";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { IFRAME_SANDBOX } from "../../util/iframe";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import { computeRouteTail } from "../../common/url/route";
|
||||
import { nextRender } from "../../common/util/render-status";
|
||||
@@ -136,6 +137,8 @@ class HaPanelApp extends LitElement {
|
||||
})}
|
||||
title=${this._addon.name}
|
||||
src=${this._addon.ingress_url!}
|
||||
.sandbox=${IFRAME_SANDBOX}
|
||||
allow="microphone; camera; clipboard-write"
|
||||
@load=${this._checkLoaded}
|
||||
${ref(this._iframeRef)}
|
||||
>
|
||||
|
||||
@@ -190,16 +190,20 @@ class PanelCalendar extends SubscribeMixin(LitElement) {
|
||||
.label=${this.hass.localize("ui.common.refresh")}
|
||||
@click=${this._handleRefresh}
|
||||
></ha-icon-button>
|
||||
${showPane && this.hass.user?.is_admin
|
||||
? html`<ha-list slot="pane" multi}>${calendarItems}</ha-list>
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
slot="pane-footer"
|
||||
@click=${this._addCalendar}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="graphic"></ha-svg-icon>
|
||||
${this.hass.localize("ui.components.calendar.create_calendar")}
|
||||
</ha-list-item>`
|
||||
${showPane
|
||||
? html`<ha-list slot="pane" multi>${calendarItems}</ha-list>${this
|
||||
.hass.user?.is_admin
|
||||
? html`<ha-list-item
|
||||
graphic="icon"
|
||||
slot="pane-footer"
|
||||
@click=${this._addCalendar}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="graphic"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.components.calendar.create_calendar"
|
||||
)}
|
||||
</ha-list-item>`
|
||||
: nothing}`
|
||||
: nothing}
|
||||
<ha-full-calendar
|
||||
add-fab
|
||||
|
||||
@@ -6,8 +6,8 @@ import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/item/ha-row-item";
|
||||
|
||||
import { formatDate } from "../../../../common/datetime/format_date";
|
||||
import type { HaSwitch } from "../../../../components/ha-switch";
|
||||
@@ -143,7 +143,7 @@ export class CloudRemotePref extends LitElement {
|
||||
"ui.panel.config.cloud.account.remote.security_options"
|
||||
)}
|
||||
>
|
||||
<ha-md-list-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.remote.external_activation"
|
||||
@@ -160,9 +160,9 @@ export class CloudRemotePref extends LitElement {
|
||||
@change=${this._toggleAllowRemoteEnabledChanged}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-md-list-item>
|
||||
</ha-row-item>
|
||||
<hr />
|
||||
<ha-md-list-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.remote.certificate_info"
|
||||
@@ -194,7 +194,7 @@ export class CloudRemotePref extends LitElement {
|
||||
"ui.panel.config.cloud.account.remote.more_info"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-md-list-item>
|
||||
</ha-row-item>
|
||||
</ha-expansion-panel>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -281,10 +281,12 @@ export class CloudRemotePref extends LitElement {
|
||||
ha-expansion-panel {
|
||||
margin-top: 16px;
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
--md-item-overflow: visible;
|
||||
ha-row-item {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
}
|
||||
ha-row-item::part(headline),
|
||||
ha-row-item::part(supporting-text) {
|
||||
white-space: wrap;
|
||||
}
|
||||
ha-expansion-panel {
|
||||
--expansion-panel-content-padding: 0 16px;
|
||||
|
||||
@@ -4,9 +4,9 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import "../../../../components/ha-spinner";
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/item/ha-row-item";
|
||||
import type { CloudStatusLoggedIn, CloudWebhook } from "../../../../data/cloud";
|
||||
import { createCloudhook, deleteCloudhook } from "../../../../data/cloud";
|
||||
import type { Webhook, WebhookError } from "../../../../data/webhook";
|
||||
@@ -76,7 +76,7 @@ export class CloudWebhooks extends LitElement {
|
||||
`
|
||||
: this._localHooks.map(
|
||||
(entry) => html`
|
||||
<ha-md-list-item .entry=${entry}>
|
||||
<ha-row-item .entry=${entry}>
|
||||
<span slot="headline"
|
||||
>${entry.name}
|
||||
${entry.domain !== entry.name.toLowerCase()
|
||||
@@ -108,7 +108,7 @@ export class CloudWebhooks extends LitElement {
|
||||
@click=${this._enableWebhook}
|
||||
>
|
||||
</ha-switch>`}
|
||||
</ha-md-list-item>
|
||||
</ha-row-item>
|
||||
`
|
||||
)}
|
||||
<div class="footer">
|
||||
@@ -237,12 +237,12 @@ export class CloudWebhooks extends LitElement {
|
||||
.footer a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
--md-item-overflow: visible;
|
||||
ha-row-item {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
}
|
||||
ha-md-list-item [slot="supporting-text"] {
|
||||
ha-row-item::part(headline),
|
||||
ha-row-item::part(supporting-text) {
|
||||
white-space: wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
`,
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-md-list";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||
import "../../../../components/entity/ha-entity-picker";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "./ha-debug-connection-row";
|
||||
import "./ha-debug-disable-view-transition-row";
|
||||
import "./ha-debug-viewport-environment-card";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/list/ha-list-base";
|
||||
import type { ExtEntityRegistryEntry } from "../../../../data/entity/entity_registry";
|
||||
import { getExtendedEntityRegistryEntry } from "../../../../data/entity/entity_registry";
|
||||
import {
|
||||
getStatisticMetadata,
|
||||
validateStatistics,
|
||||
} from "../../../../data/recorder";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { showToast } from "../../../../util/toast";
|
||||
import { getExtendedEntityRegistryEntry } from "../../../../data/entity/entity_registry";
|
||||
import type { ExtEntityRegistryEntry } from "../../../../data/entity/entity_registry";
|
||||
import "./ha-debug-connection-row";
|
||||
import "./ha-debug-disable-view-transition-row";
|
||||
import "./ha-debug-viewport-environment-card";
|
||||
|
||||
@customElement("developer-tools-debug")
|
||||
class HaPanelDevDebug extends SubscribeMixin(LitElement) {
|
||||
@@ -34,14 +34,14 @@ class HaPanelDevDebug extends SubscribeMixin(LitElement) {
|
||||
"ui.panel.config.developer-tools.tabs.debug.title"
|
||||
)}
|
||||
>
|
||||
<ha-md-list>
|
||||
<ha-list-base>
|
||||
<ha-debug-connection-row
|
||||
.hass=${this.hass}
|
||||
></ha-debug-connection-row>
|
||||
<ha-debug-disable-view-transition-row
|
||||
.hass=${this.hass}
|
||||
></ha-debug-disable-view-transition-row>
|
||||
</ha-md-list>
|
||||
</ha-list-base>
|
||||
</ha-card>
|
||||
<ha-card
|
||||
.header=${this.hass.localize(
|
||||
@@ -131,11 +131,6 @@ class HaPanelDevDebug extends SubscribeMixin(LitElement) {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
ha-md-list {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
background: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import "../../../../components/item/ha-list-item-base";
|
||||
import "../../../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../../../components/ha-switch";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
@@ -14,7 +14,7 @@ class HaDebugConnectionRow extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-md-list-item>
|
||||
<ha-list-item-base>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.developer-tools.tabs.debug.debug_connection.title"
|
||||
@@ -30,7 +30,7 @@ class HaDebugConnectionRow extends LitElement {
|
||||
.checked=${this.hass.debugConnection}
|
||||
@change=${this._checkedChanged}
|
||||
></ha-switch>
|
||||
</ha-md-list-item>
|
||||
</ha-list-item-base>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { setViewTransitionDisabled } from "../../../../common/util/view-transition";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import "../../../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../../../components/ha-switch";
|
||||
import "../../../../components/item/ha-list-item-base";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
|
||||
@customElement("ha-debug-disable-view-transition-row")
|
||||
@@ -17,7 +17,7 @@ class HaDebugDisableViewTransitionRow extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-md-list-item>
|
||||
<ha-list-item-base>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.developer-tools.tabs.debug.disable_view_transition.title"
|
||||
@@ -33,7 +33,7 @@ class HaDebugDisableViewTransitionRow extends LitElement {
|
||||
.checked=${this._disabled}
|
||||
@change=${this._checkedChanged}
|
||||
></ha-switch>
|
||||
</ha-md-list-item>
|
||||
</ha-list-item-base>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import "../../../components/ha-alert";
|
||||
import "../../../components/ha-area-picker";
|
||||
import "../../../components/ha-color-picker";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-icon-button-next";
|
||||
import "../../../components/ha-icon-picker";
|
||||
@@ -56,6 +57,7 @@ import { updateDeviceRegistryEntry } from "../../../data/device/device_registry"
|
||||
import type {
|
||||
AlarmControlPanelEntityOptions,
|
||||
CalendarEntityOptions,
|
||||
DeviceTrackerEntityOptions,
|
||||
EntityRegistryEntry,
|
||||
EntityRegistryEntryUpdateParams,
|
||||
ExtEntityRegistryEntry,
|
||||
@@ -138,6 +140,10 @@ const SWITCH_AS_DOMAINS_INVERT = ["cover", "lock", "valve"];
|
||||
|
||||
const PRECISIONS = [0, 1, 2, 3, 4, 5, 6];
|
||||
|
||||
const SCANNER_SOURCE_TYPES = ["router", "bluetooth", "bluetooth_le"];
|
||||
|
||||
const ZONE_DOMAINS = ["zone"];
|
||||
|
||||
@customElement("entity-registry-settings-editor")
|
||||
export class EntityRegistrySettingsEditor extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -202,6 +208,8 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
|
||||
@state() private _calendarColor?: string | null;
|
||||
|
||||
@state() private _associatedZone?: string;
|
||||
|
||||
@state() private _noDeviceArea?: boolean;
|
||||
|
||||
private _origEntityId!: string;
|
||||
@@ -264,6 +272,11 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
this._calendarColor = this.entry.options?.calendar?.color;
|
||||
}
|
||||
|
||||
if (domain === "device_tracker") {
|
||||
this._associatedZone =
|
||||
this.entry.options?.device_tracker?.associated_zone ?? "zone.home";
|
||||
}
|
||||
|
||||
if (domain === "weather") {
|
||||
const stateObj: HassEntity | undefined =
|
||||
this.hass.states[this.entry.entity_id];
|
||||
@@ -655,6 +668,21 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
></ha-color-picker>
|
||||
`
|
||||
: nothing}
|
||||
${domain === "device_tracker" &&
|
||||
SCANNER_SOURCE_TYPES.includes(stateObj?.attributes?.source_type)
|
||||
? html`
|
||||
<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._associatedZone}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.associated_zone"
|
||||
)}
|
||||
.includeDomains=${ZONE_DOMAINS}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._associatedZoneChanged}
|
||||
></ha-entity-picker>
|
||||
`
|
||||
: nothing}
|
||||
${domain === "sensor" &&
|
||||
this._deviceClass &&
|
||||
stateObj?.attributes.unit_of_measurement &&
|
||||
@@ -1146,6 +1174,17 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
(params.options as CalendarEntityOptions).color = this._calendarColor;
|
||||
}
|
||||
}
|
||||
if (
|
||||
domain === "device_tracker" &&
|
||||
this._associatedZone !== undefined &&
|
||||
(this.entry.options?.device_tracker?.associated_zone ?? "zone.home") !==
|
||||
this._associatedZone
|
||||
) {
|
||||
params.options_domain = "device_tracker";
|
||||
params.options = {
|
||||
associated_zone: this._associatedZone,
|
||||
} as DeviceTrackerEntityOptions;
|
||||
}
|
||||
if (
|
||||
domain === "weather" &&
|
||||
(stateObj?.attributes?.precipitation_unit !== this._precipitation_unit ||
|
||||
@@ -1385,6 +1424,11 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
this._calendarColor = ev.detail.value || null;
|
||||
}
|
||||
|
||||
private _associatedZoneChanged(ev: CustomEvent): void {
|
||||
fireEvent(this, "change");
|
||||
this._associatedZone = ev.detail.value || "zone.home";
|
||||
}
|
||||
|
||||
private _precipitationUnitChanged(ev: HaSelectSelectEvent): void {
|
||||
fireEvent(this, "change");
|
||||
this._precipitation_unit = ev.detail.value;
|
||||
|
||||
+9
-3
@@ -19,6 +19,7 @@ import type {
|
||||
HaScannerType,
|
||||
} from "../../../../../data/bluetooth";
|
||||
import {
|
||||
isScannerStateMismatch,
|
||||
subscribeBluetoothConnectionAllocations,
|
||||
subscribeBluetoothScannerState,
|
||||
subscribeBluetoothScannersDetails,
|
||||
@@ -285,9 +286,7 @@ export class BluetoothAdapterInfoPage extends LitElement {
|
||||
const scannerType: HaScannerType =
|
||||
scannerDetails?.scanner_type ?? "unknown";
|
||||
const isRemoteScanner = scannerType === "remote";
|
||||
const hasMismatch =
|
||||
scannerState &&
|
||||
scannerState.current_mode !== scannerState.requested_mode;
|
||||
const hasMismatch = scannerState && isScannerStateMismatch(scannerState);
|
||||
|
||||
const allocations = scannerDetails
|
||||
? this._connectionAllocationData.find(
|
||||
@@ -438,6 +437,13 @@ export class BluetoothAdapterInfoPage extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
if (scannerState.requested_mode === "auto") {
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.bluetooth.scanning_mode_auto_with_current",
|
||||
{ current: this._formatMode(scannerState.current_mode) }
|
||||
);
|
||||
}
|
||||
|
||||
return this._formatModeLabel(scannerState.current_mode);
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -23,6 +23,7 @@ import type {
|
||||
BluetoothScannerState,
|
||||
} from "../../../../../data/bluetooth";
|
||||
import {
|
||||
isScannerStateMismatch,
|
||||
subscribeBluetoothAdvertisements,
|
||||
subscribeBluetoothConnectionAllocations,
|
||||
subscribeBluetoothScannerState,
|
||||
@@ -144,7 +145,7 @@ export class BluetoothConfigDashboard extends LitElement {
|
||||
0
|
||||
);
|
||||
const hasMismatch = Object.values(this._scannerStates).some(
|
||||
(s) => s.current_mode !== s.requested_mode
|
||||
isScannerStateMismatch
|
||||
);
|
||||
const isOffline = adapterCount === 0;
|
||||
const status = isOffline ? "offline" : hasMismatch ? "warning" : "online";
|
||||
|
||||
+23
-2
@@ -1,5 +1,5 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { copyToClipboard } from "../../../../../common/util/copy-clipboard";
|
||||
@@ -121,6 +121,19 @@ class DialogBluetoothDeviceInfo extends LitElement {
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
${this._params.entry.raw
|
||||
? html`
|
||||
<h4>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.raw_advertisement"
|
||||
)}
|
||||
</h4>
|
||||
<div class="raw">
|
||||
${this.showDataAsHex(this._params.entry.raw)}
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
slot="secondaryAction"
|
||||
@@ -133,6 +146,14 @@ class DialogBluetoothDeviceInfo extends LitElement {
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
static readonly styles: CSSResultGroup = css`
|
||||
.raw {
|
||||
word-break: break-all;
|
||||
font-family: var(--ha-font-family-code);
|
||||
font-size: var(--ha-font-size-s);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
+429
@@ -0,0 +1,429 @@
|
||||
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-dialog";
|
||||
import "../../../../../components/ha-dialog-footer";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/input/ha-input-search";
|
||||
import "../../../../../components/item/ha-list-item-option";
|
||||
import type { HaListItemOption } from "../../../../../components/item/ha-list-item-option";
|
||||
import "../../../../../components/list/ha-list-selectable";
|
||||
import type { HaListSelectable } from "../../../../../components/list/ha-list-selectable";
|
||||
import type { HaListSelectedDetail } from "../../../../../components/list/types";
|
||||
import "../../../../../components/ha-spinner";
|
||||
import type { ZHADeviceEndpoint, ZHAGroup } from "../../../../../data/zha";
|
||||
import {
|
||||
addMembersToGroup,
|
||||
fetchGroup,
|
||||
fetchGroupableDevices,
|
||||
} from "../../../../../data/zha";
|
||||
import type { HassDialog } from "../../../../../dialogs/make-dialog-manager";
|
||||
import { haStyleScrollbar } from "../../../../../resources/styles";
|
||||
import { loadVirtualizer } from "../../../../../resources/virtualizer";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type { ZHAAddGroupMembersDialogParams } from "./show-dialog-zha-add-group-members";
|
||||
|
||||
@customElement("dialog-zha-add-group-members")
|
||||
class DialogZHAAddGroupMembers
|
||||
extends LitElement
|
||||
implements HassDialog<ZHAAddGroupMembersDialogParams>
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _deviceEndpoints: ZHADeviceEndpoint[] = [];
|
||||
|
||||
@state() private _filter = "";
|
||||
|
||||
@state() private _group?: ZHAGroup;
|
||||
|
||||
@state() private _loading = false;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
@state() private _params?: ZHAAddGroupMembersDialogParams;
|
||||
|
||||
@state() private _processingAdd = false;
|
||||
|
||||
@state() private _selectedDevicesToAdd: string[] = [];
|
||||
|
||||
@state() private _virtualizerReady = false;
|
||||
|
||||
private _fetchDataToken = 0;
|
||||
|
||||
public showDialog(params: ZHAAddGroupMembersDialogParams): void {
|
||||
this._params = params;
|
||||
this._deviceEndpoints = [];
|
||||
this._filter = "";
|
||||
this._group = undefined;
|
||||
this._selectedDevicesToAdd = [];
|
||||
this._open = true;
|
||||
this._fetchData();
|
||||
}
|
||||
|
||||
public closeDialog(): boolean {
|
||||
if (this._processingAdd) {
|
||||
return false;
|
||||
}
|
||||
this._open = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this._params = undefined;
|
||||
this._deviceEndpoints = [];
|
||||
this._filter = "";
|
||||
this._group = undefined;
|
||||
this._loading = false;
|
||||
this._processingAdd = false;
|
||||
this._selectedDevicesToAdd = [];
|
||||
this._virtualizerReady = false;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | typeof nothing {
|
||||
if (!this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const deviceEndpoints = this._filteredDeviceEndpoints;
|
||||
const showSearch =
|
||||
this._availableDeviceEndpoints.length > 5 || this._filter;
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
header-title=${this.hass.localize(
|
||||
"ui.panel.config.zha.groups.add_members"
|
||||
)}
|
||||
?prevent-scrim-close=${this._selectedDevicesToAdd.length > 0}
|
||||
@after-show=${this._loadVirtualizer}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="headerNavigationIcon"
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
.disabled=${this._processingAdd}
|
||||
@click=${this.closeDialog}
|
||||
></ha-icon-button>
|
||||
<div class="content">
|
||||
${this._loading
|
||||
? this._renderLoadingSpinner()
|
||||
: html`
|
||||
${showSearch
|
||||
? html`
|
||||
<ha-input-search
|
||||
appearance="outlined"
|
||||
.value=${this._filter}
|
||||
@input=${this._handleFilterChanged}
|
||||
></ha-input-search>
|
||||
`
|
||||
: nothing}
|
||||
<div class="list-container">
|
||||
${deviceEndpoints.length
|
||||
? html`
|
||||
${this._virtualizerReady
|
||||
? html`
|
||||
<ha-list-selectable
|
||||
multi
|
||||
@ha-list-selected=${this._handleSelected}
|
||||
>
|
||||
<lit-virtualizer
|
||||
scroller
|
||||
class="ha-scrollbar"
|
||||
.items=${deviceEndpoints}
|
||||
.renderItem=${this._renderDeviceEndpoint}
|
||||
.keyFunction=${this._keyFunction}
|
||||
></lit-virtualizer>
|
||||
</ha-list-selectable>
|
||||
`
|
||||
: this._renderLoadingSpinner()}
|
||||
`
|
||||
: html`
|
||||
<div class="empty-list">
|
||||
${this._filter
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.zha.groups.no_devices_found"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.zha.groups.no_devices_to_add"
|
||||
)}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
slot="secondaryAction"
|
||||
appearance="plain"
|
||||
@click=${this.closeDialog}
|
||||
.disabled=${this._processingAdd}
|
||||
>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
.disabled=${this._loading ||
|
||||
!this._selectedDevicesToAdd.length ||
|
||||
this._processingAdd}
|
||||
.loading=${this._processingAdd}
|
||||
@click=${this._addMembersToGroup}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.zha.groups.add_members")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderLoadingSpinner(): TemplateResult {
|
||||
return html`
|
||||
<div class="spinner-container">
|
||||
<ha-spinner size="medium"></ha-spinner>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private get _availableDeviceEndpoints(): ZHADeviceEndpoint[] {
|
||||
if (!this._group) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this._deviceEndpoints.filter(
|
||||
(deviceEndpoint) =>
|
||||
!this._group!.members.some(
|
||||
(member) =>
|
||||
member.device.ieee === deviceEndpoint.device.ieee &&
|
||||
member.endpoint_id === deviceEndpoint.endpoint_id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private get _filteredDeviceEndpoints(): ZHADeviceEndpoint[] {
|
||||
const normalizedFilter = this._filter.trim().toLowerCase();
|
||||
const deviceEndpoints = this._availableDeviceEndpoints;
|
||||
|
||||
if (!normalizedFilter) {
|
||||
return deviceEndpoints;
|
||||
}
|
||||
|
||||
return deviceEndpoints.filter((deviceEndpoint) =>
|
||||
[
|
||||
this._deviceEndpointName(deviceEndpoint),
|
||||
this._deviceEndpointDetails(deviceEndpoint),
|
||||
deviceEndpoint.device.ieee,
|
||||
deviceEndpoint.device.manufacturer,
|
||||
deviceEndpoint.device.model,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.some((value) => value!.toLowerCase().includes(normalizedFilter))
|
||||
);
|
||||
}
|
||||
|
||||
private async _loadVirtualizer(): Promise<void> {
|
||||
await loadVirtualizer();
|
||||
this._virtualizerReady = true;
|
||||
}
|
||||
|
||||
private _keyFunction = (deviceEndpoint: unknown): string =>
|
||||
this._deviceEndpointId(deviceEndpoint as ZHADeviceEndpoint);
|
||||
|
||||
private _renderDeviceEndpoint: RenderItemFunction<ZHADeviceEndpoint> = (
|
||||
deviceEndpoint
|
||||
) => {
|
||||
const id = this._deviceEndpointId(deviceEndpoint);
|
||||
|
||||
return html`
|
||||
<ha-list-item-option
|
||||
appearance="checkbox"
|
||||
.value=${id}
|
||||
.selected=${this._selectedDevicesToAdd.includes(id)}
|
||||
>
|
||||
<span slot="headline">${this._deviceEndpointName(deviceEndpoint)}</span>
|
||||
<span slot="supporting-text">
|
||||
${this._deviceEndpointDetails(deviceEndpoint)}
|
||||
</span>
|
||||
</ha-list-item-option>
|
||||
`;
|
||||
};
|
||||
|
||||
private _deviceEndpointId(deviceEndpoint: ZHADeviceEndpoint): string {
|
||||
return `${deviceEndpoint.device.ieee}_${deviceEndpoint.endpoint_id}`;
|
||||
}
|
||||
|
||||
private _deviceEndpointName(deviceEndpoint: ZHADeviceEndpoint): string {
|
||||
return deviceEndpoint.device.user_given_name || deviceEndpoint.device.name;
|
||||
}
|
||||
|
||||
private _deviceEndpointDetails(deviceEndpoint: ZHADeviceEndpoint): string {
|
||||
const entityNames = deviceEndpoint.entities.map(
|
||||
(entity) => entity.name || entity.original_name || entity.entity_id
|
||||
);
|
||||
const entitySummary = entityNames.length
|
||||
? entityNames.length > 2
|
||||
? `${entityNames.slice(0, 2).join(", ")} +${entityNames.length - 2}`
|
||||
: entityNames.join(", ")
|
||||
: this.hass.localize("ui.panel.config.zha.groups.no_entities");
|
||||
|
||||
return [
|
||||
deviceEndpoint.device.area_id
|
||||
? this.hass.areas[deviceEndpoint.device.area_id]?.name
|
||||
: undefined,
|
||||
`${this.hass.localize("ui.panel.config.zha.groups.endpoint")} ${
|
||||
deviceEndpoint.endpoint_id
|
||||
}`,
|
||||
entitySummary,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
}
|
||||
|
||||
private async _fetchData(): Promise<void> {
|
||||
const token = ++this._fetchDataToken;
|
||||
this._loading = true;
|
||||
const [group, deviceEndpoints] = await Promise.all([
|
||||
fetchGroup(this.hass, this._params!.groupId),
|
||||
fetchGroupableDevices(this.hass),
|
||||
]);
|
||||
|
||||
if (token !== this._fetchDataToken || !this._params) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._group = group;
|
||||
this._deviceEndpoints = deviceEndpoints;
|
||||
this._loading = false;
|
||||
}
|
||||
|
||||
private _handleFilterChanged(ev: Event): void {
|
||||
this._filter = (ev.currentTarget as HTMLInputElement).value;
|
||||
}
|
||||
|
||||
private _handleSelected(ev: CustomEvent<HaListSelectedDetail>): void {
|
||||
const list = ev.currentTarget as HaListSelectable;
|
||||
let selectedDevicesToAdd = this._selectedDevicesToAdd;
|
||||
|
||||
ev.detail.diff?.added.forEach((index) => {
|
||||
const item = list.items[index] as HaListItemOption | undefined;
|
||||
if (item?.value && !selectedDevicesToAdd.includes(item.value)) {
|
||||
selectedDevicesToAdd = [...selectedDevicesToAdd, item.value];
|
||||
}
|
||||
});
|
||||
|
||||
ev.detail.diff?.removed.forEach((index) => {
|
||||
const item = list.items[index] as HaListItemOption | undefined;
|
||||
if (item?.value) {
|
||||
selectedDevicesToAdd = selectedDevicesToAdd.filter(
|
||||
(selectedDeviceId) => selectedDeviceId !== item.value
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
this._selectedDevicesToAdd = selectedDevicesToAdd;
|
||||
}
|
||||
|
||||
private async _addMembersToGroup(): Promise<void> {
|
||||
this._processingAdd = true;
|
||||
try {
|
||||
const members = this._selectedDevicesToAdd.map((member) => {
|
||||
const memberParts = member.split("_");
|
||||
return { ieee: memberParts[0], endpoint_id: memberParts[1] };
|
||||
});
|
||||
const group = await addMembersToGroup(
|
||||
this.hass,
|
||||
this._params!.groupId,
|
||||
members
|
||||
);
|
||||
this._params!.devicesAddedCallback(group);
|
||||
this._processingAdd = false;
|
||||
this.closeDialog();
|
||||
} finally {
|
||||
this._processingAdd = false;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
ha-dialog {
|
||||
--dialog-content-padding: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: min(520px, calc(100vh - 240px));
|
||||
}
|
||||
|
||||
ha-input-search {
|
||||
display: block;
|
||||
margin: 0 var(--ha-space-4) var(--ha-space-2);
|
||||
}
|
||||
|
||||
ha-list-selectable {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
ha-list-selectable::part(base) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.list-container {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
lit-virtualizer {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
contain: size layout !important;
|
||||
}
|
||||
|
||||
ha-list-item-option {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
box-sizing: border-box;
|
||||
--ha-row-item-min-height: 64px;
|
||||
}
|
||||
|
||||
.spinner-container {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 160px;
|
||||
}
|
||||
|
||||
ha-spinner {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.empty-list {
|
||||
padding: var(--ha-space-6);
|
||||
color: var(--secondary-text-color);
|
||||
text-align: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-zha-add-group-members": DialogZHAAddGroupMembers;
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import type { ZHAGroup } from "../../../../../data/zha";
|
||||
|
||||
export interface ZHAAddGroupMembersDialogParams {
|
||||
groupId: number;
|
||||
groupName: string;
|
||||
devicesAddedCallback: (group: ZHAGroup) => void;
|
||||
}
|
||||
|
||||
export const loadZHAAddGroupMembersDialog = () =>
|
||||
import("./dialog-zha-add-group-members");
|
||||
|
||||
export const showZHAAddGroupMembersDialog = (
|
||||
element: HTMLElement,
|
||||
params: ZHAAddGroupMembersDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-zha-add-group-members",
|
||||
dialogImport: loadZHAAddGroupMembersDialog,
|
||||
dialogParams: params,
|
||||
});
|
||||
};
|
||||
@@ -3,16 +3,18 @@ import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state, query } from "lit/decorators";
|
||||
import type { HASSDomEvent } from "../../../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../../../common/navigate";
|
||||
import type { SelectionChangedEvent } from "../../../../../components/data-table/ha-data-table";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/input/ha-input";
|
||||
import type { ZHADeviceEndpoint, ZHAGroup } from "../../../../../data/zha";
|
||||
import { addGroup, fetchGroupableDevices } from "../../../../../data/zha";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../../../ha-config-section";
|
||||
import "../../../../../components/input/ha-input";
|
||||
import "./zha-device-endpoint-data-table";
|
||||
import type { ZHADeviceEndpointDataTable } from "./zha-device-endpoint-data-table";
|
||||
import "./zha-device-endpoint-list";
|
||||
import type {
|
||||
DeviceEndpointSelectionChangedEvent,
|
||||
ZHADeviceEndpointList,
|
||||
} from "./zha-device-endpoint-list";
|
||||
|
||||
@customElement("zha-add-group-page")
|
||||
export class ZHAAddGroupPage extends LitElement {
|
||||
@@ -29,8 +31,8 @@ export class ZHAAddGroupPage extends LitElement {
|
||||
|
||||
@state() private _groupId?: string;
|
||||
|
||||
@query("zha-device-endpoint-data-table", true)
|
||||
private _zhaDevicesDataTable!: ZHADeviceEndpointDataTable;
|
||||
@query("zha-device-endpoint-list", true)
|
||||
private _zhaDeviceEndpointList!: ZHADeviceEndpointList;
|
||||
|
||||
private _firstUpdatedCalled = false;
|
||||
|
||||
@@ -57,59 +59,67 @@ export class ZHAAddGroupPage extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.header=${this.hass.localize("ui.panel.config.zha.groups.create_group")}
|
||||
back-path="/config/zha/groups"
|
||||
>
|
||||
<ha-config-section .isWide=${!this.narrow}>
|
||||
<p slot="introduction">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zha.groups.create_group_details"
|
||||
)}
|
||||
</p>
|
||||
<ha-input
|
||||
type="string"
|
||||
.value=${this._groupName}
|
||||
@change=${this._handleNameChange}
|
||||
.placeholder=${this.hass!.localize(
|
||||
"ui.panel.config.zha.groups.group_name_placeholder"
|
||||
)}
|
||||
></ha-input>
|
||||
<div class="container">
|
||||
<ha-card class="details-card">
|
||||
<div class="card-header">
|
||||
${this.hass.localize("ui.panel.config.zha.groups.group_info")}
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<ha-input
|
||||
type="text"
|
||||
.value=${this._groupName}
|
||||
@change=${this._handleNameChange}
|
||||
.placeholder=${this.hass!.localize(
|
||||
"ui.panel.config.zha.groups.group_name_placeholder"
|
||||
)}
|
||||
></ha-input>
|
||||
|
||||
<ha-input
|
||||
type="number"
|
||||
.value=${this._groupId}
|
||||
@change=${this._handleGroupIdChange}
|
||||
.placeholder=${this.hass!.localize(
|
||||
"ui.panel.config.zha.groups.group_id_placeholder"
|
||||
)}
|
||||
></ha-input>
|
||||
<ha-input
|
||||
type="number"
|
||||
.value=${this._groupId}
|
||||
@change=${this._handleGroupIdChange}
|
||||
.placeholder=${this.hass!.localize(
|
||||
"ui.panel.config.zha.groups.group_id_placeholder"
|
||||
)}
|
||||
></ha-input>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
<div class="header">
|
||||
${this.hass.localize("ui.panel.config.zha.groups.add_members")}
|
||||
</div>
|
||||
<section>
|
||||
<h2>
|
||||
${this.hass.localize("ui.panel.config.zha.groups.add_members")}
|
||||
</h2>
|
||||
|
||||
<zha-device-endpoint-data-table
|
||||
.hass=${this.hass}
|
||||
.deviceEndpoints=${this.deviceEndpoints}
|
||||
.narrow=${this.narrow}
|
||||
selectable
|
||||
@selection-changed=${this._handleAddSelectionChanged}
|
||||
>
|
||||
</zha-device-endpoint-data-table>
|
||||
|
||||
<div class="buttons">
|
||||
<ha-button
|
||||
.disabled=${!this._groupName ||
|
||||
this._groupName === "" ||
|
||||
this._processingAdd}
|
||||
@click=${this._createGroup}
|
||||
class="button"
|
||||
.loading=${this._processingAdd}
|
||||
<zha-device-endpoint-list
|
||||
scrollable
|
||||
show-device-link
|
||||
.deviceEndpoints=${this.deviceEndpoints}
|
||||
.narrow=${this.narrow}
|
||||
.emptyText=${this.hass.localize(
|
||||
"ui.panel.config.zha.groups.no_devices_to_add"
|
||||
)}
|
||||
selectable
|
||||
@selection-changed=${this._handleAddSelectionChanged}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.groups.create"
|
||||
)}</ha-button
|
||||
>
|
||||
</div>
|
||||
</ha-config-section>
|
||||
</zha-device-endpoint-list>
|
||||
|
||||
<div class="buttons">
|
||||
<ha-button
|
||||
.disabled=${!this._groupName ||
|
||||
this._groupName === "" ||
|
||||
this._processingAdd}
|
||||
@click=${this._createGroup}
|
||||
.loading=${this._processingAdd}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.groups.create"
|
||||
)}</ha-button
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
@@ -119,7 +129,7 @@ export class ZHAAddGroupPage extends LitElement {
|
||||
}
|
||||
|
||||
private _handleAddSelectionChanged(
|
||||
ev: HASSDomEvent<SelectionChangedEvent>
|
||||
ev: HASSDomEvent<DeviceEndpointSelectionChangedEvent>
|
||||
): void {
|
||||
this._selectedDevicesToAdd = ev.detail.value;
|
||||
}
|
||||
@@ -142,7 +152,7 @@ export class ZHAAddGroupPage extends LitElement {
|
||||
this._selectedDevicesToAdd = [];
|
||||
this._processingAdd = false;
|
||||
this._groupName = "";
|
||||
this._zhaDevicesDataTable.clearSelection();
|
||||
this._zhaDeviceEndpointList.clearSelection();
|
||||
navigate(`/config/zha/group/${group.group_id}`, { replace: true });
|
||||
}
|
||||
|
||||
@@ -157,29 +167,54 @@ export class ZHAAddGroupPage extends LitElement {
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
css`
|
||||
.header {
|
||||
font-family: var(--ha-font-family-body);
|
||||
-webkit-font-smoothing: var(--ha-font-smoothing);
|
||||
-moz-osx-font-smoothing: var(--ha-moz-osx-font-smoothing);
|
||||
font-size: var(--ha-font-size-4xl);
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
.container {
|
||||
box-sizing: border-box;
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: var(--ha-space-4) var(--ha-space-4)
|
||||
calc(var(--ha-space-20) + var(--safe-area-inset-bottom, 0px));
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: var(--ha-space-4) var(--ha-space-4) 0;
|
||||
font-size: var(--ha-font-size-xl);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
opacity: var(--dark-primary-opacity);
|
||||
}
|
||||
|
||||
.button {
|
||||
float: right;
|
||||
.card-content {
|
||||
display: grid;
|
||||
gap: var(--ha-space-4);
|
||||
padding: var(--ha-space-4);
|
||||
}
|
||||
|
||||
ha-config-section *:last-child {
|
||||
padding-bottom: 24px;
|
||||
section {
|
||||
margin-top: var(--ha-space-8);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0 0 var(--ha-space-3);
|
||||
font-family: var(--ha-font-family-body);
|
||||
font-size: var(--ha-font-size-2xl);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
}
|
||||
|
||||
zha-device-endpoint-list {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
align-items: flex-end;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: var(--ha-space-4) 0 0;
|
||||
}
|
||||
.buttons .warning {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.container {
|
||||
padding-inline: var(--ha-space-2);
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
-186
@@ -1,186 +0,0 @@
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import "../../../../../components/data-table/ha-data-table";
|
||||
import type {
|
||||
DataTableColumnContainer,
|
||||
DataTableRowData,
|
||||
HaDataTable,
|
||||
} from "../../../../../components/data-table/ha-data-table";
|
||||
import type {
|
||||
ZHADeviceEndpoint,
|
||||
ZHAEntityReference,
|
||||
} from "../../../../../data/zha";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { getAreaTableColumn } from "../../../common/data-table-columns";
|
||||
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||
import type { AreaRegistryEntry } from "../../../../../data/area/area_registry";
|
||||
|
||||
export interface DeviceEndpointRowData extends DataTableRowData {
|
||||
id: string;
|
||||
name: string;
|
||||
area: string | undefined;
|
||||
model: string;
|
||||
manufacturer: string;
|
||||
endpoint_id: number;
|
||||
entities: ZHAEntityReference[];
|
||||
}
|
||||
|
||||
@customElement("zha-device-endpoint-data-table")
|
||||
export class ZHADeviceEndpointDataTable extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ type: Boolean }) public selectable = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public deviceEndpoints: ZHADeviceEndpoint[] = [];
|
||||
|
||||
@query("ha-data-table", true) private _dataTable!: HaDataTable;
|
||||
|
||||
private _deviceEndpoints = memoizeOne(
|
||||
(
|
||||
deviceEndpoints: ZHADeviceEndpoint[],
|
||||
areas: Record<string, AreaRegistryEntry>
|
||||
) => {
|
||||
const outputDevices: DeviceEndpointRowData[] = [];
|
||||
deviceEndpoints.forEach((deviceEndpoint) => {
|
||||
outputDevices.push({
|
||||
name:
|
||||
deviceEndpoint.device.user_given_name || deviceEndpoint.device.name,
|
||||
area: deviceEndpoint.device.area_id
|
||||
? areas[deviceEndpoint.device.area_id].name
|
||||
: undefined,
|
||||
model: deviceEndpoint.device.model,
|
||||
manufacturer: deviceEndpoint.device.manufacturer,
|
||||
id: deviceEndpoint.device.ieee + "_" + deviceEndpoint.endpoint_id,
|
||||
ieee: deviceEndpoint.device.ieee,
|
||||
endpoint_id: deviceEndpoint.endpoint_id,
|
||||
entities: deviceEndpoint.entities,
|
||||
dev_id: deviceEndpoint.device.device_reg_id,
|
||||
});
|
||||
});
|
||||
|
||||
return outputDevices;
|
||||
}
|
||||
);
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(localize: LocalizeFunc, narrow: boolean): DataTableColumnContainer =>
|
||||
narrow
|
||||
? {
|
||||
name: {
|
||||
title: localize("ui.panel.config.zha.groups.members"),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
flex: 2,
|
||||
template: (device) => html`
|
||||
<a href=${`/config/devices/device/${device.dev_id}`}>
|
||||
${device.name}
|
||||
${device.area
|
||||
? html` <br />
|
||||
<span
|
||||
style="font-size: var(--ha-font-size-s);color: var(--ha-color-text-secondary);"
|
||||
>
|
||||
${device.area}
|
||||
</span>`
|
||||
: nothing}
|
||||
</a>
|
||||
`,
|
||||
},
|
||||
endpoint_id: {
|
||||
title: localize("ui.panel.config.zha.groups.endpoint"),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
}
|
||||
: {
|
||||
name: {
|
||||
title: localize("ui.panel.config.zha.groups.members"),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
flex: 2,
|
||||
template: (device) => html`
|
||||
<a href=${`/config/devices/device/${device.dev_id}`}>
|
||||
${device.name}
|
||||
</a>
|
||||
`,
|
||||
},
|
||||
area: getAreaTableColumn(localize),
|
||||
endpoint_id: {
|
||||
title: localize("ui.panel.config.zha.groups.endpoint"),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
entities: {
|
||||
title: localize("ui.panel.config.zha.groups.associated_entities"),
|
||||
sortable: false,
|
||||
filterable: false,
|
||||
flex: 2,
|
||||
template: (device) => html`
|
||||
${device.entities.length
|
||||
? device.entities.length > 3
|
||||
? html`${device.entities
|
||||
.slice(0, 2)
|
||||
.map(
|
||||
(entity) =>
|
||||
html`<div
|
||||
style="overflow: hidden; text-overflow: ellipsis;"
|
||||
>
|
||||
${entity.name || entity.original_name}
|
||||
</div>`
|
||||
)}
|
||||
<div>+${device.entities.length - 2}</div>`
|
||||
: device.entities.map(
|
||||
(entity) =>
|
||||
html`<div
|
||||
style="overflow: hidden; text-overflow: ellipsis;"
|
||||
>
|
||||
${entity.name || entity.original_name}
|
||||
</div>`
|
||||
)
|
||||
: localize(
|
||||
"ui.panel.config.zha.groups.no_associated_entities"
|
||||
)}
|
||||
`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
public clearSelection() {
|
||||
this._dataTable.clearSelection();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-data-table
|
||||
.columns=${this._columns(this.hass.localize, this.narrow)}
|
||||
.data=${this._deviceEndpoints(this.deviceEndpoints, this.hass.areas)}
|
||||
.selectable=${this.selectable}
|
||||
auto-height
|
||||
.searchLabel=${this.hass.localize("ui.components.data-table.search")}
|
||||
.noDataText=${this.hass.localize("ui.components.data-table.no-data")}
|
||||
></ha-data-table>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
css`
|
||||
.table-cell-text {
|
||||
word-break: break-word;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zha-device-endpoint-data-table": ZHADeviceEndpointDataTable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import { mdiOpenInNew } from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-list";
|
||||
import "../../../../../components/input/ha-input-search";
|
||||
import "../../../../../components/item/ha-list-item-base";
|
||||
import "../../../../../components/item/ha-list-item-option";
|
||||
import type { HaListItemOption } from "../../../../../components/item/ha-list-item-option";
|
||||
import "../../../../../components/list/ha-list-selectable";
|
||||
import type { HaListSelectable } from "../../../../../components/list/ha-list-selectable";
|
||||
import type { HaListSelectedDetail } from "../../../../../components/list/types";
|
||||
import {
|
||||
areasContext,
|
||||
internationalizationContext,
|
||||
} from "../../../../../data/context";
|
||||
import type {
|
||||
ZHADeviceEndpoint,
|
||||
ZHAEntityReference,
|
||||
} from "../../../../../data/zha";
|
||||
|
||||
export interface DeviceEndpointRowData {
|
||||
id: string;
|
||||
name: string;
|
||||
area: string | undefined;
|
||||
model: string;
|
||||
manufacturer: string;
|
||||
endpoint_id: number;
|
||||
entities: ZHAEntityReference[];
|
||||
ieee: string;
|
||||
dev_id: string;
|
||||
}
|
||||
|
||||
export interface DeviceEndpointSelectionChangedEvent {
|
||||
value: string[];
|
||||
}
|
||||
|
||||
@customElement("zha-device-endpoint-list")
|
||||
export class ZHADeviceEndpointList extends LitElement {
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ type: Boolean }) public selectable = false;
|
||||
|
||||
@property({ type: Boolean }) public scrollable = false;
|
||||
|
||||
@property({ attribute: false }) public emptyText?: string;
|
||||
|
||||
@property({ attribute: "show-device-link", type: Boolean })
|
||||
public showDeviceLink = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public deviceEndpoints: ZHADeviceEndpoint[] = [];
|
||||
|
||||
@state() private _filter = "";
|
||||
|
||||
@state() private _selectedDeviceIds: string[] = [];
|
||||
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n!: ContextType<typeof internationalizationContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: areasContext, subscribe: true })
|
||||
private _areas!: ContextType<typeof areasContext>;
|
||||
|
||||
@query("ha-list-selectable") private _list?: HaListSelectable;
|
||||
|
||||
public clearSelection() {
|
||||
this._selectedDeviceIds = [];
|
||||
this._list?.clearSelection();
|
||||
this._fireSelectionChanged();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const allDeviceEndpoints = this._deviceEndpointRows;
|
||||
const deviceEndpoints = this._filterDeviceEndpoints(allDeviceEndpoints);
|
||||
const showSearch = allDeviceEndpoints.length > 5 || this._filter;
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
class=${`${showSearch ? "searchable" : ""} ${
|
||||
this.scrollable ? "scrollable" : ""
|
||||
}`}
|
||||
>
|
||||
${showSearch
|
||||
? html`
|
||||
<div class="search">
|
||||
<ha-input-search
|
||||
appearance="outlined"
|
||||
.value=${this._filter}
|
||||
@input=${this._handleFilterChanged}
|
||||
></ha-input-search>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${deviceEndpoints.length
|
||||
? html`
|
||||
${this.selectable
|
||||
? html`
|
||||
<ha-list-selectable
|
||||
multi
|
||||
@ha-list-selected=${this._handleListSelectionChanged}
|
||||
>
|
||||
${repeat(
|
||||
deviceEndpoints,
|
||||
(deviceEndpoint) => deviceEndpoint.id,
|
||||
(deviceEndpoint) =>
|
||||
this._renderSelectableListRow(deviceEndpoint)
|
||||
)}
|
||||
</ha-list-selectable>
|
||||
`
|
||||
: html`
|
||||
<ha-list>
|
||||
${repeat(
|
||||
deviceEndpoints,
|
||||
(deviceEndpoint) => deviceEndpoint.id,
|
||||
(deviceEndpoint) =>
|
||||
this._renderReadonlyListRow(deviceEndpoint)
|
||||
)}
|
||||
</ha-list>
|
||||
`}
|
||||
`
|
||||
: html`
|
||||
<div class="empty-list">
|
||||
${this._filter
|
||||
? this._i18n.localize(
|
||||
"ui.panel.config.zha.groups.no_devices_found"
|
||||
)
|
||||
: this.emptyText ||
|
||||
this._i18n.localize("ui.components.data-table.no-data")}
|
||||
</div>
|
||||
`}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private get _deviceEndpointRows(): DeviceEndpointRowData[] {
|
||||
return this.deviceEndpoints.map((deviceEndpoint) => ({
|
||||
name: deviceEndpoint.device.user_given_name || deviceEndpoint.device.name,
|
||||
area: deviceEndpoint.device.area_id
|
||||
? this._areas[deviceEndpoint.device.area_id]?.name
|
||||
: undefined,
|
||||
model: deviceEndpoint.device.model,
|
||||
manufacturer: deviceEndpoint.device.manufacturer,
|
||||
id: `${deviceEndpoint.device.ieee}_${deviceEndpoint.endpoint_id}`,
|
||||
ieee: deviceEndpoint.device.ieee,
|
||||
endpoint_id: deviceEndpoint.endpoint_id,
|
||||
entities: deviceEndpoint.entities,
|
||||
dev_id: deviceEndpoint.device.device_reg_id,
|
||||
}));
|
||||
}
|
||||
|
||||
private _renderSelectableListRow(
|
||||
deviceEndpoint: DeviceEndpointRowData
|
||||
): TemplateResult {
|
||||
const selected = this._selectedDeviceIds.includes(deviceEndpoint.id);
|
||||
|
||||
return html`
|
||||
<ha-list-item-option
|
||||
appearance="checkbox"
|
||||
class="device-row"
|
||||
.value=${deviceEndpoint.id}
|
||||
.selected=${selected}
|
||||
>
|
||||
<span slot="headline">${deviceEndpoint.name}</span>
|
||||
<span slot="supporting-text">
|
||||
${this._deviceEndpointDetails(deviceEndpoint)}
|
||||
</span>
|
||||
${this.showDeviceLink
|
||||
? html`
|
||||
<ha-icon-button
|
||||
slot="end"
|
||||
.path=${mdiOpenInNew}
|
||||
.href=${`/config/devices/device/${deviceEndpoint.dev_id}`}
|
||||
.label=${this._i18n.localize(
|
||||
"ui.panel.config.zha.groups.open_device"
|
||||
)}
|
||||
@click=${this._stopPropagation}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: nothing}
|
||||
</ha-list-item-option>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderReadonlyListRow(
|
||||
deviceEndpoint: DeviceEndpointRowData
|
||||
): TemplateResult {
|
||||
return html`
|
||||
<ha-list-item-base class="device-row">
|
||||
<span slot="headline">${deviceEndpoint.name}</span>
|
||||
<span slot="supporting-text">
|
||||
${this._deviceEndpointDetails(deviceEndpoint)}
|
||||
</span>
|
||||
${this.showDeviceLink
|
||||
? html`
|
||||
<ha-icon-button
|
||||
slot="end"
|
||||
.path=${mdiOpenInNew}
|
||||
.href=${`/config/devices/device/${deviceEndpoint.dev_id}`}
|
||||
.label=${this._i18n.localize(
|
||||
"ui.panel.config.zha.groups.open_device"
|
||||
)}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: nothing}
|
||||
</ha-list-item-base>
|
||||
`;
|
||||
}
|
||||
|
||||
private _filterDeviceEndpoints(
|
||||
deviceEndpoints: DeviceEndpointRowData[]
|
||||
): DeviceEndpointRowData[] {
|
||||
const normalizedFilter = this._filter.trim().toLowerCase();
|
||||
|
||||
if (!normalizedFilter) {
|
||||
return deviceEndpoints;
|
||||
}
|
||||
|
||||
return deviceEndpoints.filter((deviceEndpoint) =>
|
||||
[
|
||||
deviceEndpoint.name,
|
||||
this._deviceEndpointDetails(deviceEndpoint),
|
||||
deviceEndpoint.ieee,
|
||||
deviceEndpoint.manufacturer,
|
||||
deviceEndpoint.model,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.some((value) => value!.toLowerCase().includes(normalizedFilter))
|
||||
);
|
||||
}
|
||||
|
||||
private _deviceEndpointDetails(
|
||||
deviceEndpoint: DeviceEndpointRowData
|
||||
): string {
|
||||
const entityNames = deviceEndpoint.entities.map(
|
||||
(entity) => entity.name || entity.original_name || entity.entity_id
|
||||
);
|
||||
const entitySummary = entityNames.length
|
||||
? entityNames.length > 2
|
||||
? `${entityNames.slice(0, 2).join(", ")} +${entityNames.length - 2}`
|
||||
: entityNames.join(", ")
|
||||
: this._i18n.localize("ui.panel.config.zha.groups.no_entities");
|
||||
|
||||
return [
|
||||
deviceEndpoint.area,
|
||||
`${this._i18n.localize("ui.panel.config.zha.groups.endpoint")} ${
|
||||
deviceEndpoint.endpoint_id
|
||||
}`,
|
||||
entitySummary,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
}
|
||||
|
||||
private _handleFilterChanged(ev: Event): void {
|
||||
this._filter = (ev.currentTarget as HTMLInputElement).value;
|
||||
}
|
||||
|
||||
private _handleListSelectionChanged(
|
||||
ev: CustomEvent<HaListSelectedDetail>
|
||||
): void {
|
||||
const list = ev.currentTarget as HaListSelectable;
|
||||
let selectedDeviceIds = this._selectedDeviceIds;
|
||||
|
||||
ev.detail.diff?.added.forEach((index) => {
|
||||
const item = list.items[index] as HaListItemOption | undefined;
|
||||
if (item?.value) {
|
||||
selectedDeviceIds = this._setSelectedDeviceId(
|
||||
selectedDeviceIds,
|
||||
item.value,
|
||||
true
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
ev.detail.diff?.removed.forEach((index) => {
|
||||
const item = list.items[index] as HaListItemOption | undefined;
|
||||
if (item?.value) {
|
||||
selectedDeviceIds = this._setSelectedDeviceId(
|
||||
selectedDeviceIds,
|
||||
item.value,
|
||||
false
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
this._selectedDeviceIds = selectedDeviceIds;
|
||||
this._fireSelectionChanged();
|
||||
}
|
||||
|
||||
private _setSelectedDeviceId(
|
||||
selectedDeviceIds: string[],
|
||||
deviceId: string,
|
||||
selected: boolean
|
||||
): string[] {
|
||||
if (selected) {
|
||||
return selectedDeviceIds.includes(deviceId)
|
||||
? selectedDeviceIds
|
||||
: [...selectedDeviceIds, deviceId];
|
||||
}
|
||||
|
||||
return selectedDeviceIds.filter((selectedDeviceId) => {
|
||||
return selectedDeviceId !== deviceId;
|
||||
});
|
||||
}
|
||||
|
||||
private _fireSelectionChanged(): void {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent<DeviceEndpointSelectionChangedEvent>(
|
||||
"selection-changed",
|
||||
{
|
||||
detail: { value: this._selectedDeviceIds },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _stopPropagation(ev: Event): void {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
css`
|
||||
ha-card.scrollable {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
ha-card.searchable.scrollable {
|
||||
height: min(520px, calc(100vh - 360px));
|
||||
}
|
||||
|
||||
.search {
|
||||
padding: var(--ha-space-4) var(--ha-space-4) var(--ha-space-2);
|
||||
}
|
||||
|
||||
ha-list,
|
||||
ha-list-selectable {
|
||||
display: block;
|
||||
width: 100%;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ha-list-selectable::part(base) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
ha-card.scrollable ha-list,
|
||||
ha-card.scrollable ha-list-selectable {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.device-row {
|
||||
width: 100%;
|
||||
--ha-row-item-min-height: 64px;
|
||||
--ha-row-item-gap: var(--ha-space-3);
|
||||
}
|
||||
|
||||
[slot="headline"],
|
||||
[slot="supporting-text"] {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.empty-list {
|
||||
padding: var(--ha-space-6);
|
||||
color: var(--secondary-text-color);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
ha-card.searchable.scrollable {
|
||||
height: 440px;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"zha-device-endpoint-list": ZHADeviceEndpointList;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,29 @@
|
||||
import { mdiDelete } from "@mdi/js";
|
||||
import { mdiDelete, mdiPlus } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import type { HASSDomEvent } from "../../../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../../../common/navigate";
|
||||
import type { SelectionChangedEvent } from "../../../../../components/data-table/ha-data-table";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-list";
|
||||
import "../../../../../components/ha-list-item";
|
||||
import type { ZHADeviceEndpoint, ZHAGroup } from "../../../../../data/zha";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import type { ZHAGroup } from "../../../../../data/zha";
|
||||
import {
|
||||
addMembersToGroup,
|
||||
fetchGroup,
|
||||
fetchGroupableDevices,
|
||||
removeGroups,
|
||||
removeMembersFromGroup,
|
||||
} from "../../../../../data/zha";
|
||||
import "../../../../../layouts/hass-error-screen";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../../../ha-config-section";
|
||||
import { formatAsPaddedHex } from "./functions";
|
||||
import "./zha-device-endpoint-data-table";
|
||||
import type { ZHADeviceEndpointDataTable } from "./zha-device-endpoint-data-table";
|
||||
import "./zha-device-endpoint-list";
|
||||
import type {
|
||||
DeviceEndpointSelectionChangedEvent,
|
||||
ZHADeviceEndpointList,
|
||||
} from "./zha-device-endpoint-list";
|
||||
import { showZHAAddGroupMembersDialog } from "./show-dialog-zha-add-group-members";
|
||||
|
||||
@customElement("zha-group-page")
|
||||
export class ZHAGroupPage extends LitElement {
|
||||
@@ -38,25 +37,12 @@ export class ZHAGroupPage extends LitElement {
|
||||
|
||||
@property({ attribute: "is-wide", type: Boolean }) public isWide = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public deviceEndpoints: ZHADeviceEndpoint[] = [];
|
||||
|
||||
@state() private _processingAdd = false;
|
||||
|
||||
@state() private _processingRemove = false;
|
||||
|
||||
@state()
|
||||
private _filteredDeviceEndpoints: ZHADeviceEndpoint[] = [];
|
||||
|
||||
@state() private _selectedDevicesToAdd: string[] = [];
|
||||
|
||||
@state() private _selectedDevicesToRemove: string[] = [];
|
||||
|
||||
@query("#addMembers", true)
|
||||
private _zhaAddMembersDataTable!: ZHADeviceEndpointDataTable;
|
||||
|
||||
@query("#removeMembers")
|
||||
private _zhaRemoveMembersDataTable!: ZHADeviceEndpointDataTable;
|
||||
private _zhaRemoveMembersList!: ZHADeviceEndpointList;
|
||||
|
||||
private _firstUpdatedCalled = false;
|
||||
|
||||
@@ -69,12 +55,8 @@ export class ZHAGroupPage extends LitElement {
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._processingAdd = false;
|
||||
this._processingRemove = false;
|
||||
this._selectedDevicesToRemove = [];
|
||||
this._selectedDevicesToAdd = [];
|
||||
this.deviceEndpoints = [];
|
||||
this._filteredDeviceEndpoints = [];
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues<this>): void {
|
||||
@@ -102,6 +84,7 @@ export class ZHAGroupPage extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.header=${this.group.name}
|
||||
back-path="/config/zha/groups"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
@@ -109,158 +92,115 @@ export class ZHAGroupPage extends LitElement {
|
||||
@click=${this._deleteGroup}
|
||||
.label=${this.hass.localize("ui.panel.config.zha.groups.delete")}
|
||||
></ha-icon-button>
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<div class="header">
|
||||
${this.hass.localize("ui.panel.config.zha.groups.group_info")}
|
||||
</div>
|
||||
|
||||
<p slot="introduction">
|
||||
${this.hass.localize("ui.panel.config.zha.groups.group_details")}
|
||||
</p>
|
||||
|
||||
<p><b>Name:</b> ${this.group.name}</p>
|
||||
<p><b>Group Id:</b> ${formatAsPaddedHex(this.group.group_id)}</p>
|
||||
|
||||
<div class="header">
|
||||
${this.hass.localize("ui.panel.config.zha.groups.members")}
|
||||
</div>
|
||||
<div class="container">
|
||||
<ha-card>
|
||||
<ha-list>
|
||||
${this.group.members.length
|
||||
? this.group.members.map(
|
||||
(member) =>
|
||||
html`<a
|
||||
href="/config/devices/device/${member.device
|
||||
.device_reg_id}"
|
||||
>
|
||||
<ha-list-item
|
||||
>${member.device.user_given_name ||
|
||||
member.device.name}</ha-list-item
|
||||
>
|
||||
</a>`
|
||||
)
|
||||
: html`
|
||||
<ha-list-item> This group has no members </ha-list-item>
|
||||
`}
|
||||
</ha-list>
|
||||
</ha-card>
|
||||
${this.group.members.length
|
||||
? html`
|
||||
<div class="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zha.groups.remove_members"
|
||||
)}
|
||||
</div>
|
||||
|
||||
<zha-device-endpoint-data-table
|
||||
id="removeMembers"
|
||||
.hass=${this.hass}
|
||||
.deviceEndpoints=${this.group.members}
|
||||
.narrow=${this.narrow}
|
||||
selectable
|
||||
@selection-changed=${this._handleRemoveSelectionChanged}
|
||||
<div class="card-header">
|
||||
${this.hass.localize("ui.panel.config.zha.groups.group_info")}
|
||||
</div>
|
||||
<div class="summary-grid">
|
||||
<div>
|
||||
<span class="summary-label"
|
||||
>${this.hass.localize("ui.common.name")}</span
|
||||
>
|
||||
</zha-device-endpoint-data-table>
|
||||
<span class="summary-value">${this.group.name}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="summary-label"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zha.groups.group_id"
|
||||
)}</span
|
||||
>
|
||||
<span class="summary-value"
|
||||
>${formatAsPaddedHex(this.group.group_id)}</span
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<span class="summary-label"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.zha.groups.members"
|
||||
)}</span
|
||||
>
|
||||
<span class="summary-value">${this.group.members.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
<div class="buttons">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
variant="danger"
|
||||
.disabled=${!this._selectedDevicesToRemove.length ||
|
||||
this._processingRemove}
|
||||
@click=${this._removeMembersFromGroup}
|
||||
class="button"
|
||||
.loading=${this._processingRemove}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.groups.remove_members"
|
||||
)}</ha-button
|
||||
>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<div class="header">
|
||||
${this.hass.localize("ui.panel.config.zha.groups.add_members")}
|
||||
<div class="members-section">
|
||||
<h2>${this.hass.localize("ui.panel.config.zha.groups.members")}</h2>
|
||||
${this.group.members.length
|
||||
? html`
|
||||
<zha-device-endpoint-list
|
||||
id="removeMembers"
|
||||
scrollable
|
||||
show-device-link
|
||||
selectable
|
||||
.deviceEndpoints=${this.group.members}
|
||||
.narrow=${this.narrow}
|
||||
.emptyText=${this.hass.localize(
|
||||
"ui.panel.config.zha.groups.no_members"
|
||||
)}
|
||||
@selection-changed=${this._handleRemoveSelectionChanged}
|
||||
></zha-device-endpoint-list>
|
||||
`
|
||||
: html`
|
||||
<ha-card class="empty-card">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zha.groups.no_members"
|
||||
)}
|
||||
</ha-card>
|
||||
`}
|
||||
<div class="buttons">
|
||||
${this.group.members.length
|
||||
? html`
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
variant="danger"
|
||||
.disabled=${!this._selectedDevicesToRemove.length ||
|
||||
this._processingRemove}
|
||||
@click=${this._removeMembersFromGroup}
|
||||
.loading=${this._processingRemove}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zha.groups.remove_members"
|
||||
)}
|
||||
</ha-button>
|
||||
`
|
||||
: nothing}
|
||||
<ha-button @click=${this._showAddMembersDialog}>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
|
||||
${this.hass.localize("ui.panel.config.zha.groups.add_members")}
|
||||
</ha-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<zha-device-endpoint-data-table
|
||||
id="addMembers"
|
||||
.hass=${this.hass}
|
||||
.deviceEndpoints=${this._filteredDeviceEndpoints}
|
||||
.narrow=${this.narrow}
|
||||
selectable
|
||||
@selection-changed=${this._handleAddSelectionChanged}
|
||||
>
|
||||
</zha-device-endpoint-data-table>
|
||||
|
||||
<div class="buttons">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
.disabled=${!this._selectedDevicesToAdd.length ||
|
||||
this._processingAdd}
|
||||
@click=${this._addMembersToGroup}
|
||||
class="button"
|
||||
.loading=${this._processingAdd}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.groups.add_members"
|
||||
)}</ha-button
|
||||
>
|
||||
</div>
|
||||
</ha-config-section>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private _showAddMembersDialog(): void {
|
||||
showZHAAddGroupMembersDialog(this, {
|
||||
groupId: this.groupId,
|
||||
groupName: this.group!.name,
|
||||
devicesAddedCallback: (group) => {
|
||||
this.group = group;
|
||||
this._selectedDevicesToRemove = [];
|
||||
this._zhaRemoveMembersList?.clearSelection();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async _fetchData() {
|
||||
if (this.groupId !== null && this.groupId !== undefined) {
|
||||
this.group = await fetchGroup(this.hass!, this.groupId);
|
||||
this.group = await fetchGroup(this.hass, this.groupId);
|
||||
}
|
||||
this.deviceEndpoints = await fetchGroupableDevices(this.hass!);
|
||||
// filter the groupable devices so we only show devices that aren't already in the group
|
||||
this._filterDevices();
|
||||
}
|
||||
|
||||
private _filterDevices() {
|
||||
// filter the groupable devices so we only show devices that aren't already in the group
|
||||
this._filteredDeviceEndpoints = this.deviceEndpoints.filter(
|
||||
(deviceEndpoint) =>
|
||||
!this.group!.members.some(
|
||||
(member) =>
|
||||
member.device.ieee === deviceEndpoint.device.ieee &&
|
||||
member.endpoint_id === deviceEndpoint.endpoint_id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _handleAddSelectionChanged(
|
||||
ev: HASSDomEvent<SelectionChangedEvent>
|
||||
): void {
|
||||
this._selectedDevicesToAdd = ev.detail.value;
|
||||
}
|
||||
|
||||
private _handleRemoveSelectionChanged(
|
||||
ev: HASSDomEvent<SelectionChangedEvent>
|
||||
ev: HASSDomEvent<DeviceEndpointSelectionChangedEvent>
|
||||
): void {
|
||||
this._selectedDevicesToRemove = ev.detail.value;
|
||||
}
|
||||
|
||||
private async _addMembersToGroup(): Promise<void> {
|
||||
this._processingAdd = true;
|
||||
const members = this._selectedDevicesToAdd.map((member) => {
|
||||
const memberParts = member.split("_");
|
||||
return { ieee: memberParts[0], endpoint_id: memberParts[1] };
|
||||
});
|
||||
this.group = await addMembersToGroup(this.hass, this.groupId, members);
|
||||
this._filterDevices();
|
||||
this._selectedDevicesToAdd = [];
|
||||
this._zhaAddMembersDataTable.clearSelection();
|
||||
this._processingAdd = false;
|
||||
}
|
||||
|
||||
private async _removeMembersFromGroup(): Promise<void> {
|
||||
this._processingRemove = true;
|
||||
const members = this._selectedDevicesToRemove.map((member) => {
|
||||
@@ -268,9 +208,8 @@ export class ZHAGroupPage extends LitElement {
|
||||
return { ieee: memberParts[0], endpoint_id: memberParts[1] };
|
||||
});
|
||||
this.group = await removeMembersFromGroup(this.hass, this.groupId, members);
|
||||
this._filterDevices();
|
||||
this._selectedDevicesToRemove = [];
|
||||
this._zhaRemoveMembersDataTable.clearSelection();
|
||||
this._zhaRemoveMembersList.clearSelection();
|
||||
this._processingRemove = false;
|
||||
}
|
||||
|
||||
@@ -285,30 +224,78 @@ export class ZHAGroupPage extends LitElement {
|
||||
hass-subpage {
|
||||
--app-header-text-color: var(--sidebar-icon-color);
|
||||
}
|
||||
.header {
|
||||
font-family: var(--ha-font-family-body);
|
||||
-webkit-font-smoothing: var(--ha-font-smoothing);
|
||||
-moz-osx-font-smoothing: var(--ha-moz-osx-font-smoothing);
|
||||
font-size: var(--ha-font-size-4xl);
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
|
||||
.container {
|
||||
box-sizing: border-box;
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: var(--ha-space-4) var(--ha-space-4)
|
||||
calc(var(--ha-space-20) + var(--safe-area-inset-bottom, 0px));
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: var(--ha-space-4) var(--ha-space-4) 0;
|
||||
font-size: var(--ha-font-size-xl);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
opacity: var(--dark-primary-opacity);
|
||||
}
|
||||
|
||||
.button {
|
||||
float: right;
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: var(--ha-space-4);
|
||||
padding: var(--ha-space-4);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
.summary-label,
|
||||
.summary-value {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: var(--ha-font-size-s);
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
margin-top: var(--ha-space-1);
|
||||
font-size: var(--ha-font-size-l);
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
}
|
||||
|
||||
.members-section {
|
||||
margin-top: var(--ha-space-6);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0 0 var(--ha-space-3);
|
||||
font-size: var(--ha-font-size-2xl);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
}
|
||||
|
||||
.buttons {
|
||||
align-items: flex-end;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
gap: var(--ha-space-2);
|
||||
justify-content: flex-end;
|
||||
padding: var(--ha-space-4) 0 0;
|
||||
}
|
||||
.buttons .warning {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
|
||||
.empty-card {
|
||||
padding: var(--ha-space-6);
|
||||
color: var(--secondary-text-color);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.summary-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--ha-space-2);
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ifDefined } from "lit/directives/if-defined";
|
||||
import "../../layouts/hass-error-screen";
|
||||
import "../../layouts/hass-subpage";
|
||||
import type { HomeAssistant, PanelInfo } from "../../types";
|
||||
import { IFRAME_SANDBOX } from "../../util/iframe";
|
||||
import { IFRAME_SANDBOX_SAME_ORIGIN } from "../../util/iframe";
|
||||
|
||||
@customElement("ha-panel-iframe")
|
||||
class HaPanelIframe extends LitElement {
|
||||
@@ -41,7 +41,7 @@ class HaPanelIframe extends LitElement {
|
||||
this.panel.title === null ? undefined : this.panel.title
|
||||
)}
|
||||
src=${this.panel.config.url}
|
||||
.sandbox=${IFRAME_SANDBOX}
|
||||
.sandbox=${IFRAME_SANDBOX_SAME_ORIGIN}
|
||||
allow="fullscreen"
|
||||
></iframe>
|
||||
</hass-subpage>
|
||||
|
||||
@@ -13,7 +13,7 @@ import type {
|
||||
LovelaceGridOptions,
|
||||
} from "../types";
|
||||
import type { IframeCardConfig } from "./types";
|
||||
import { IFRAME_SANDBOX } from "../../../util/iframe";
|
||||
import { IFRAME_SANDBOX_SAME_ORIGIN } from "../../../util/iframe";
|
||||
|
||||
@customElement("hui-iframe-card")
|
||||
export class HuiIframeCard extends LitElement implements LovelaceCard {
|
||||
@@ -95,7 +95,7 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
const sandbox_params = this._config.disable_sandbox
|
||||
? undefined
|
||||
: `${sandbox_user_params} ${IFRAME_SANDBOX}`;
|
||||
: `${sandbox_user_params} ${IFRAME_SANDBOX_SAME_ORIGIN}`;
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
|
||||
@@ -229,9 +229,6 @@ export class HuiEntityEditor extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-entity-picker {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.entity {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -253,6 +250,11 @@ export class HuiEntityEditor extends LitElement {
|
||||
ha-md-list {
|
||||
gap: 8px;
|
||||
padding-top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
ha-md-list:has(> *) {
|
||||
margin-bottom: var(--ha-space-2);
|
||||
}
|
||||
ha-md-list-item {
|
||||
border: 1px solid var(--divider-color);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiChevronDown,
|
||||
mdiChevronRight,
|
||||
mdiChevronLeft,
|
||||
mdiMagnify,
|
||||
mdiTextureBox,
|
||||
} from "@mdi/js";
|
||||
@@ -16,6 +17,7 @@ import { computeEntityName } from "../../../../common/entity/compute_entity_name
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import { computeRTL } from "../../../../common/util/compute_rtl";
|
||||
import { debounce } from "../../../../common/util/debounce";
|
||||
import { mainWindow } from "../../../../common/dom/get_main_window";
|
||||
import "../../../../components/entity/state-badge";
|
||||
import "../../../../components/ha-combo-box-item";
|
||||
import "../../../../components/ha-domain-icon";
|
||||
@@ -294,7 +296,11 @@ export class HuiSuggestionEntityTree extends LitElement {
|
||||
private _renderChevron(expanded: boolean): TemplateResult {
|
||||
return html`<ha-svg-icon
|
||||
class="chevron"
|
||||
.path=${expanded ? mdiChevronDown : mdiChevronRight}
|
||||
.path=${expanded
|
||||
? mdiChevronDown
|
||||
: mainWindow.document.dir === "rtl"
|
||||
? mdiChevronLeft
|
||||
: mdiChevronRight}
|
||||
></ha-svg-icon>`;
|
||||
}
|
||||
|
||||
|
||||
@@ -313,8 +313,7 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
|
||||
});
|
||||
clearInterval(this.__backendPingInterval);
|
||||
|
||||
// Fetch the brands access token on initial connect and schedule refresh
|
||||
fetchAndScheduleBrandsAccessToken(this.hass!);
|
||||
this._refreshBrandsAccessToken();
|
||||
|
||||
this.__backendPingInterval = setInterval(() => {
|
||||
if (this.hass?.connected) {
|
||||
@@ -340,8 +339,7 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
|
||||
this._updateHass({ connected: true });
|
||||
broadcastConnectionStatus("connected");
|
||||
|
||||
// Refresh the brands access token on reconnect and restart refresh schedule
|
||||
fetchAndScheduleBrandsAccessToken(this.hass!);
|
||||
this._refreshBrandsAccessToken();
|
||||
|
||||
// on reconnect always fetch config as we might miss an update while we were disconnected
|
||||
// @ts-ignore
|
||||
@@ -362,4 +360,15 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
|
||||
clearInterval(this.__backendPingInterval);
|
||||
clearBrandsTokenRefresh();
|
||||
}
|
||||
|
||||
private async _refreshBrandsAccessToken() {
|
||||
// The brands WS handler may not be registered yet after a server restart;
|
||||
// fetchAndScheduleBrandsAccessToken retries internally. If the token
|
||||
// changed, re-render so any brand <img> elements that rendered against a
|
||||
// different (or missing) token recompute their src and re-fetch.
|
||||
const changed = await fetchAndScheduleBrandsAccessToken(this.hass!);
|
||||
if (changed) {
|
||||
this._updateHass({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1891,6 +1891,7 @@
|
||||
"default_code": "Default code",
|
||||
"default_code_error": "Code does not match code format",
|
||||
"calendar_color": "Calendar color",
|
||||
"associated_zone": "Associated zone",
|
||||
"entity_id": "Entity ID",
|
||||
"unit_of_measurement": "Unit of measurement",
|
||||
"precipitation_unit": "Precipitation unit",
|
||||
@@ -7132,6 +7133,7 @@
|
||||
"scanning_mode_passive": "passive",
|
||||
"scanning_mode_active_label": "Active scanning",
|
||||
"scanning_mode_passive_label": "Passive scanning",
|
||||
"scanning_mode_auto_with_current": "Auto ({current})",
|
||||
"scanning_mode_none_label": "No scanning",
|
||||
"scanner_mode_mismatch": "{name} requested {requested} mode but is operating in {current} mode. The scanner is in a bad state and needs to be power cycled.",
|
||||
"scanner_mode_mismatch_remote": "For proxies: reboot the device",
|
||||
@@ -7149,6 +7151,7 @@
|
||||
"manufacturer_data": "Manufacturer data",
|
||||
"service_data": "Service data",
|
||||
"service_uuids": "Service UUIDs",
|
||||
"raw_advertisement": "Raw advertisement",
|
||||
"copy_to_clipboard": "[%key:ui::panel::config::automation::editor::copy_to_clipboard%]",
|
||||
"area": "Area",
|
||||
"scanners": "Scanners",
|
||||
@@ -7350,8 +7353,15 @@
|
||||
"group_details": "Here are all the details for the selected Zigbee group.",
|
||||
"group_not_found": "Group not found!",
|
||||
"add_members": "Add devices",
|
||||
"remove_members": "Remove device",
|
||||
"remove_members": "Remove devices",
|
||||
"removing_members": "Removing devices",
|
||||
"no_members": "This group has no devices",
|
||||
"no_devices_found": "No devices found",
|
||||
"no_devices_to_add": "No devices to add",
|
||||
"no_entities": "No entities",
|
||||
"entity_count": "{count} entity",
|
||||
"entity_count_plural": "{count} entities",
|
||||
"open_device": "Open device",
|
||||
"create_group_details": "Enter the required details to create a new Zigbee group",
|
||||
"group_name_placeholder": "Group name",
|
||||
"group_id_placeholder": "Group ID (optional)",
|
||||
|
||||
+28
-7
@@ -1,3 +1,4 @@
|
||||
import { waitForMs } from "../common/util/wait";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export interface BrandsOptions {
|
||||
@@ -20,15 +21,35 @@ let _brandsRefreshInterval: ReturnType<typeof setInterval> | undefined;
|
||||
// Re-fetch every 30 minutes to always have a valid token.
|
||||
const TOKEN_REFRESH_MS = 30 * 60 * 1000;
|
||||
|
||||
export const fetchAndScheduleBrandsAccessToken = (
|
||||
// Delays before each attempt. The first attempt fires immediately; subsequent
|
||||
// ones back off to ride through the window after a Home Assistant restart
|
||||
// where the WebSocket server accepts connections but the brands integration
|
||||
// hasn't registered its WS handler yet. On older backends without the command,
|
||||
// every attempt fails and we give up.
|
||||
const FETCH_DELAYS_MS = [0, 500, 1000, 2000, 5000, 10000, 15000];
|
||||
|
||||
// Returns true if the cached token changed as a result of this call, so
|
||||
// callers can decide whether they need to trigger a re-render.
|
||||
export const fetchAndScheduleBrandsAccessToken = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<void> =>
|
||||
fetchBrandsAccessToken(hass).then(
|
||||
() => scheduleBrandsTokenRefresh(hass),
|
||||
() => {
|
||||
// Ignore failures; older backends may not support this command
|
||||
): Promise<boolean> => {
|
||||
const previousToken = _brandsAccessToken;
|
||||
/* eslint-disable no-await-in-loop -- retries are intentionally sequential */
|
||||
for (const delay of FETCH_DELAYS_MS) {
|
||||
if (delay) {
|
||||
await waitForMs(delay);
|
||||
}
|
||||
);
|
||||
try {
|
||||
await fetchBrandsAccessToken(hass);
|
||||
scheduleBrandsTokenRefresh(hass);
|
||||
return _brandsAccessToken !== previousToken;
|
||||
} catch {
|
||||
// try next delay
|
||||
}
|
||||
}
|
||||
/* eslint-enable no-await-in-loop */
|
||||
return false;
|
||||
};
|
||||
|
||||
export const fetchBrandsAccessToken = async (
|
||||
hass: HomeAssistant
|
||||
|
||||
+3
-1
@@ -1,2 +1,4 @@
|
||||
export const IFRAME_SANDBOX =
|
||||
"allow-forms allow-popups allow-pointer-lock allow-same-origin allow-scripts allow-modals allow-downloads";
|
||||
"allow-forms allow-popups allow-pointer-lock allow-scripts allow-modals allow-downloads";
|
||||
|
||||
export const IFRAME_SANDBOX_SAME_ORIGIN = `${IFRAME_SANDBOX} allow-same-origin`;
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { BluetoothScannerState } from "../../src/data/bluetooth";
|
||||
import { isScannerStateMismatch } from "../../src/data/bluetooth";
|
||||
|
||||
const state = (
|
||||
overrides: Partial<BluetoothScannerState>
|
||||
): BluetoothScannerState => ({
|
||||
source: "AA:BB:CC:DD:EE:FF",
|
||||
adapter: "hci0",
|
||||
current_mode: null,
|
||||
requested_mode: null,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe("isScannerStateMismatch", () => {
|
||||
it("is never a mismatch when requested mode is auto", () => {
|
||||
expect(
|
||||
isScannerStateMismatch(
|
||||
state({ requested_mode: "auto", current_mode: "passive" })
|
||||
)
|
||||
).toBe(false);
|
||||
expect(
|
||||
isScannerStateMismatch(
|
||||
state({ requested_mode: "auto", current_mode: "active" })
|
||||
)
|
||||
).toBe(false);
|
||||
expect(
|
||||
isScannerStateMismatch(
|
||||
state({ requested_mode: "auto", current_mode: null })
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("flags a mismatch when requested and current differ", () => {
|
||||
expect(
|
||||
isScannerStateMismatch(
|
||||
state({ requested_mode: "active", current_mode: "passive" })
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
isScannerStateMismatch(
|
||||
state({ requested_mode: "passive", current_mode: "active" })
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
isScannerStateMismatch(
|
||||
state({ requested_mode: "active", current_mode: null })
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("is not a mismatch when requested and current agree", () => {
|
||||
expect(
|
||||
isScannerStateMismatch(
|
||||
state({ requested_mode: "active", current_mode: "active" })
|
||||
)
|
||||
).toBe(false);
|
||||
expect(
|
||||
isScannerStateMismatch(
|
||||
state({ requested_mode: "passive", current_mode: "passive" })
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
addBrandsAuth,
|
||||
brandsUrl,
|
||||
clearBrandsTokenRefresh,
|
||||
fetchAndScheduleBrandsAccessToken,
|
||||
fetchBrandsAccessToken,
|
||||
scheduleBrandsTokenRefresh,
|
||||
} from "../../src/util/brands-url";
|
||||
@@ -169,3 +170,83 @@ describe("scheduleBrandsTokenRefresh", () => {
|
||||
assert.strictEqual(callCount, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fetchAndScheduleBrandsAccessToken", () => {
|
||||
afterEach(() => {
|
||||
clearBrandsTokenRefresh();
|
||||
vi.restoreAllMocks();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("retries with backoff until the WS call succeeds, returns true when the token changed", async () => {
|
||||
vi.useFakeTimers();
|
||||
let callCount = 0;
|
||||
const mockHass = {
|
||||
callWS: async () => {
|
||||
callCount++;
|
||||
if (callCount < 3) {
|
||||
throw new Error("unknown_command");
|
||||
}
|
||||
return { token: `retry-token-${callCount}` };
|
||||
},
|
||||
} as unknown as HomeAssistant;
|
||||
|
||||
const promise = fetchAndScheduleBrandsAccessToken(mockHass);
|
||||
|
||||
// First attempt fires immediately, fails
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
assert.strictEqual(callCount, 1);
|
||||
|
||||
// 500ms backoff → second attempt fails
|
||||
await vi.advanceTimersByTimeAsync(500);
|
||||
assert.strictEqual(callCount, 2);
|
||||
|
||||
// 1000ms backoff → third attempt succeeds
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
|
||||
const changed = await promise;
|
||||
assert.strictEqual(changed, true);
|
||||
assert.strictEqual(callCount, 3);
|
||||
assert.strictEqual(
|
||||
brandsUrl(
|
||||
{ domain: "test", type: "icon" },
|
||||
"http://homeassistant.local:8123"
|
||||
),
|
||||
"http://homeassistant.local:8123/api/brands/integration/test/icon.png?token=retry-token-3"
|
||||
);
|
||||
});
|
||||
|
||||
it("returns false when the backend returns the same token (no UI change needed)", async () => {
|
||||
const mockHass = {
|
||||
callWS: async () => ({ token: "stable-token" }),
|
||||
} as unknown as HomeAssistant;
|
||||
|
||||
// Prime the cached token
|
||||
await fetchBrandsAccessToken(mockHass);
|
||||
|
||||
// Same token returned → no change
|
||||
const changed = await fetchAndScheduleBrandsAccessToken(mockHass);
|
||||
assert.strictEqual(changed, false);
|
||||
});
|
||||
|
||||
it("returns false after all retries fail (e.g. older backend)", async () => {
|
||||
vi.useFakeTimers();
|
||||
let callCount = 0;
|
||||
const mockHass = {
|
||||
callWS: async () => {
|
||||
callCount++;
|
||||
throw new Error("unknown_command");
|
||||
},
|
||||
} as unknown as HomeAssistant;
|
||||
|
||||
const promise = fetchAndScheduleBrandsAccessToken(mockHass);
|
||||
|
||||
// Exhaust all retry delays: 500 + 1000 + 2000 + 5000 + 10000 + 15000
|
||||
await vi.advanceTimersByTimeAsync(33500);
|
||||
|
||||
const changed = await promise;
|
||||
assert.strictEqual(changed, false);
|
||||
// 1 immediate attempt + 6 retries = 7 attempts
|
||||
assert.strictEqual(callCount, 7);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Manual changes might be lost - proceed with caution!
|
||||
|
||||
__metadata:
|
||||
version: 9
|
||||
version: 10
|
||||
cacheKey: 10
|
||||
|
||||
"@apideck/better-ajv-errors@npm:^0.3.1":
|
||||
@@ -1469,10 +1469,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@date-fns/tz@npm:1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "@date-fns/tz@npm:1.4.1"
|
||||
checksum: 10/062097590005cce3da4c7d9880f9c77d386cff5b4dd58fa3dde3c346a8b2e4f4a8025a613306351a7cad8eb71178a0f67b4840d5884f73aa4c759085fac92063
|
||||
"@date-fns/tz@npm:1.5.0":
|
||||
version: 1.5.0
|
||||
resolution: "@date-fns/tz@npm:1.5.0"
|
||||
checksum: 10/a629879b1f307429e0a00a92dea71e19f63398ba304ee0f58f4d141d50c3058d678c4095b25962f7e5fda508c70502811dd565e58df99e5632dc16097da184a8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1660,12 +1660,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/icu-messageformat-parser@npm:3.5.9":
|
||||
version: 3.5.9
|
||||
resolution: "@formatjs/icu-messageformat-parser@npm:3.5.9"
|
||||
"@formatjs/icu-messageformat-parser@npm:3.5.10":
|
||||
version: 3.5.10
|
||||
resolution: "@formatjs/icu-messageformat-parser@npm:3.5.10"
|
||||
dependencies:
|
||||
"@formatjs/icu-skeleton-parser": "npm:2.1.9"
|
||||
checksum: 10/b2543274b8359873ea279139c9da3ab0f42421651b28855c63d2ca7768a747e662f30ff3d296a1807425d08f1b3ae84376372289749da2fb17ba342e9686673a
|
||||
checksum: 10/44392248b9247cf83a21b43c749025bfbc23acd63782b9a1b7dc47bf5520b686f8a5dccfa56716bc81fe0680000029aba22f5eb5c821ec529646758bd2d6af79
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3676,51 +3676,51 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-darwin-arm64@npm:2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "@rspack/binding-darwin-arm64@npm:2.0.3"
|
||||
"@rspack/binding-darwin-arm64@npm:2.0.4":
|
||||
version: 2.0.4
|
||||
resolution: "@rspack/binding-darwin-arm64@npm:2.0.4"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-darwin-x64@npm:2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "@rspack/binding-darwin-x64@npm:2.0.3"
|
||||
"@rspack/binding-darwin-x64@npm:2.0.4":
|
||||
version: 2.0.4
|
||||
resolution: "@rspack/binding-darwin-x64@npm:2.0.4"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-arm64-gnu@npm:2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "@rspack/binding-linux-arm64-gnu@npm:2.0.3"
|
||||
"@rspack/binding-linux-arm64-gnu@npm:2.0.4":
|
||||
version: 2.0.4
|
||||
resolution: "@rspack/binding-linux-arm64-gnu@npm:2.0.4"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-arm64-musl@npm:2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "@rspack/binding-linux-arm64-musl@npm:2.0.3"
|
||||
"@rspack/binding-linux-arm64-musl@npm:2.0.4":
|
||||
version: 2.0.4
|
||||
resolution: "@rspack/binding-linux-arm64-musl@npm:2.0.4"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-x64-gnu@npm:2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "@rspack/binding-linux-x64-gnu@npm:2.0.3"
|
||||
"@rspack/binding-linux-x64-gnu@npm:2.0.4":
|
||||
version: 2.0.4
|
||||
resolution: "@rspack/binding-linux-x64-gnu@npm:2.0.4"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-x64-musl@npm:2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "@rspack/binding-linux-x64-musl@npm:2.0.3"
|
||||
"@rspack/binding-linux-x64-musl@npm:2.0.4":
|
||||
version: 2.0.4
|
||||
resolution: "@rspack/binding-linux-x64-musl@npm:2.0.4"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-wasm32-wasi@npm:2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "@rspack/binding-wasm32-wasi@npm:2.0.3"
|
||||
"@rspack/binding-wasm32-wasi@npm:2.0.4":
|
||||
version: 2.0.4
|
||||
resolution: "@rspack/binding-wasm32-wasi@npm:2.0.4"
|
||||
dependencies:
|
||||
"@emnapi/core": "npm:1.10.0"
|
||||
"@emnapi/runtime": "npm:1.10.0"
|
||||
@@ -3729,41 +3729,41 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-win32-arm64-msvc@npm:2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "@rspack/binding-win32-arm64-msvc@npm:2.0.3"
|
||||
"@rspack/binding-win32-arm64-msvc@npm:2.0.4":
|
||||
version: 2.0.4
|
||||
resolution: "@rspack/binding-win32-arm64-msvc@npm:2.0.4"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-win32-ia32-msvc@npm:2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "@rspack/binding-win32-ia32-msvc@npm:2.0.3"
|
||||
"@rspack/binding-win32-ia32-msvc@npm:2.0.4":
|
||||
version: 2.0.4
|
||||
resolution: "@rspack/binding-win32-ia32-msvc@npm:2.0.4"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-win32-x64-msvc@npm:2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "@rspack/binding-win32-x64-msvc@npm:2.0.3"
|
||||
"@rspack/binding-win32-x64-msvc@npm:2.0.4":
|
||||
version: 2.0.4
|
||||
resolution: "@rspack/binding-win32-x64-msvc@npm:2.0.4"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding@npm:2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "@rspack/binding@npm:2.0.3"
|
||||
"@rspack/binding@npm:2.0.4":
|
||||
version: 2.0.4
|
||||
resolution: "@rspack/binding@npm:2.0.4"
|
||||
dependencies:
|
||||
"@rspack/binding-darwin-arm64": "npm:2.0.3"
|
||||
"@rspack/binding-darwin-x64": "npm:2.0.3"
|
||||
"@rspack/binding-linux-arm64-gnu": "npm:2.0.3"
|
||||
"@rspack/binding-linux-arm64-musl": "npm:2.0.3"
|
||||
"@rspack/binding-linux-x64-gnu": "npm:2.0.3"
|
||||
"@rspack/binding-linux-x64-musl": "npm:2.0.3"
|
||||
"@rspack/binding-wasm32-wasi": "npm:2.0.3"
|
||||
"@rspack/binding-win32-arm64-msvc": "npm:2.0.3"
|
||||
"@rspack/binding-win32-ia32-msvc": "npm:2.0.3"
|
||||
"@rspack/binding-win32-x64-msvc": "npm:2.0.3"
|
||||
"@rspack/binding-darwin-arm64": "npm:2.0.4"
|
||||
"@rspack/binding-darwin-x64": "npm:2.0.4"
|
||||
"@rspack/binding-linux-arm64-gnu": "npm:2.0.4"
|
||||
"@rspack/binding-linux-arm64-musl": "npm:2.0.4"
|
||||
"@rspack/binding-linux-x64-gnu": "npm:2.0.4"
|
||||
"@rspack/binding-linux-x64-musl": "npm:2.0.4"
|
||||
"@rspack/binding-wasm32-wasi": "npm:2.0.4"
|
||||
"@rspack/binding-win32-arm64-msvc": "npm:2.0.4"
|
||||
"@rspack/binding-win32-ia32-msvc": "npm:2.0.4"
|
||||
"@rspack/binding-win32-x64-msvc": "npm:2.0.4"
|
||||
dependenciesMeta:
|
||||
"@rspack/binding-darwin-arm64":
|
||||
optional: true
|
||||
@@ -3785,15 +3785,15 @@ __metadata:
|
||||
optional: true
|
||||
"@rspack/binding-win32-x64-msvc":
|
||||
optional: true
|
||||
checksum: 10/21b7510a6945ebab50db8bd54ae5b1e19fb3caf51016c2c21238fe37beb30eacc569f84d17cca924955a9a3b7e6dbed2818b6246b00e7dffbd6d30b164c45874
|
||||
checksum: 10/55832bec03a4c94d6c60a16ebb6484e9c32a6e88cf820874f9873293787d1cbb95ee4276acf5dbf5b6bf6e63d6c9f8e3023efd40f8c2526a52324927b41a9316
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/core@npm:2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "@rspack/core@npm:2.0.3"
|
||||
"@rspack/core@npm:2.0.4":
|
||||
version: 2.0.4
|
||||
resolution: "@rspack/core@npm:2.0.4"
|
||||
dependencies:
|
||||
"@rspack/binding": "npm:2.0.3"
|
||||
"@rspack/binding": "npm:2.0.4"
|
||||
peerDependencies:
|
||||
"@module-federation/runtime-tools": ^0.24.1 || ^2.0.0
|
||||
"@swc/helpers": ">=0.5.1"
|
||||
@@ -3802,7 +3802,7 @@ __metadata:
|
||||
optional: true
|
||||
"@swc/helpers":
|
||||
optional: true
|
||||
checksum: 10/71da00e09299a65c7503f775380c66126dfcfbc9a03efb50fab10573db6009b90cccd824fdbbf39c1dc141cb61cd1b66e68b06cfba6abf913b1fba7c860bd02d
|
||||
checksum: 10/49249480a403259e5ee862cbd4694920da1b6ca5aba82146b31e4868430954eccb8a81e93fa730514dd3a39b65aa3fd52dbf0583bce1213086e6aec1a23ff9d1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4126,161 +4126,161 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/basic@npm:4.0.4":
|
||||
version: 4.0.4
|
||||
resolution: "@tsparticles/basic@npm:4.0.4"
|
||||
"@tsparticles/basic@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/basic@npm:4.0.5"
|
||||
dependencies:
|
||||
"@tsparticles/engine": "npm:4.0.4"
|
||||
"@tsparticles/plugin-blend": "npm:4.0.4"
|
||||
"@tsparticles/plugin-hex-color": "npm:4.0.4"
|
||||
"@tsparticles/plugin-hsl-color": "npm:4.0.4"
|
||||
"@tsparticles/plugin-move": "npm:4.0.4"
|
||||
"@tsparticles/plugin-rgb-color": "npm:4.0.4"
|
||||
"@tsparticles/shape-circle": "npm:4.0.4"
|
||||
"@tsparticles/updater-opacity": "npm:4.0.4"
|
||||
"@tsparticles/updater-out-modes": "npm:4.0.4"
|
||||
"@tsparticles/updater-paint": "npm:4.0.4"
|
||||
"@tsparticles/updater-size": "npm:4.0.4"
|
||||
checksum: 10/8c4d2177e7b3c371aa3c4b2070c0dd19a893cefebc768f4c5c294014458397b1c2775b6c9118a3452cc982c766b77a75957ecbb9d57b828ddcaef21075a51736
|
||||
"@tsparticles/engine": "npm:4.0.5"
|
||||
"@tsparticles/plugin-blend": "npm:4.0.5"
|
||||
"@tsparticles/plugin-hex-color": "npm:4.0.5"
|
||||
"@tsparticles/plugin-hsl-color": "npm:4.0.5"
|
||||
"@tsparticles/plugin-move": "npm:4.0.5"
|
||||
"@tsparticles/plugin-rgb-color": "npm:4.0.5"
|
||||
"@tsparticles/shape-circle": "npm:4.0.5"
|
||||
"@tsparticles/updater-opacity": "npm:4.0.5"
|
||||
"@tsparticles/updater-out-modes": "npm:4.0.5"
|
||||
"@tsparticles/updater-paint": "npm:4.0.5"
|
||||
"@tsparticles/updater-size": "npm:4.0.5"
|
||||
checksum: 10/6e96ffa802235784699af074255cce1cdcf8e236b71e13cfa3c7a34bd4d90206b6cb078f8e6a5584f9e2027f2e02336f35fe3488bd5b407216416ec782566fa5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/canvas-utils@npm:4.0.4":
|
||||
version: 4.0.4
|
||||
resolution: "@tsparticles/canvas-utils@npm:4.0.4"
|
||||
"@tsparticles/canvas-utils@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/canvas-utils@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.4
|
||||
checksum: 10/9d5668423fa53dc508239013d13c22872b164acd1d7d3ab6ed30e1095f3f48048667b6381fe8a6177a330cc9feaa9b07f93b16b9339574915f409d857e44397d
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/7ba361a2c4a1636e1f4594ed2937a7b469ada5c26cfd487dabc6f0fac6dadad906b7c32fb598cef09de3fdba6346a2fed6b49fd830edbdd31bb92249febf93e4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/engine@npm:4.0.4":
|
||||
version: 4.0.4
|
||||
resolution: "@tsparticles/engine@npm:4.0.4"
|
||||
checksum: 10/cec1655b7be31bac572e73b9941a9d4c53cd7fa1ae343aacc08bbb3157665158f542d7e4df127deb478d5370c7d5cac58b72ac52beb959db445cab4c4e8eabc2
|
||||
"@tsparticles/engine@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/engine@npm:4.0.5"
|
||||
checksum: 10/e780c10943a10559bf9132186f4512fbd54934afc50b5c229d635e13d28a135a5b3c680bf3150c166a56ec0fe7542cb644fd8816cb75fff52fab33c81908ae49
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/interaction-particles-links@npm:4.0.4":
|
||||
version: 4.0.4
|
||||
resolution: "@tsparticles/interaction-particles-links@npm:4.0.4"
|
||||
"@tsparticles/interaction-particles-links@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/interaction-particles-links@npm:4.0.5"
|
||||
dependencies:
|
||||
"@tsparticles/canvas-utils": "npm:4.0.4"
|
||||
"@tsparticles/canvas-utils": "npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.4
|
||||
"@tsparticles/plugin-interactivity": 4.0.4
|
||||
checksum: 10/8b4b903e011d44d0842ac03c76e09ca9718032fd2aabbbe76311eacf9fb9b6f50dd47f2516a67bd20a5b3fb93ebe4726fb6395e95f6fd0205f9f466d50ece9b5
|
||||
"@tsparticles/engine": 4.0.5
|
||||
"@tsparticles/plugin-interactivity": 4.0.5
|
||||
checksum: 10/6ba65cec3c5ff7ca6fc85a38b2ef4451307d5004b1a6fdbfa77828cda824357f2a322ec1a0a4074413c793383670095adfef720846f286116b4c2921e6010d52
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-blend@npm:4.0.4":
|
||||
version: 4.0.4
|
||||
resolution: "@tsparticles/plugin-blend@npm:4.0.4"
|
||||
"@tsparticles/plugin-blend@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/plugin-blend@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.4
|
||||
checksum: 10/35027829cad2c042e31085fd5ef63eb1c7747a842729257dc863c4e4c402fcf32644fd6343d2391c1d0de82aba1972d4474130b827e40892eb699eae1517260e
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/13d68a23303e37538ea4ed8066adea5f0506bb3c5916d3016ee00f2fa356ca927ed7f538b33f84fc737fe0f3d346dd53420c01a35e17474408b3484634617e09
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-hex-color@npm:4.0.4":
|
||||
version: 4.0.4
|
||||
resolution: "@tsparticles/plugin-hex-color@npm:4.0.4"
|
||||
"@tsparticles/plugin-hex-color@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/plugin-hex-color@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.4
|
||||
checksum: 10/63dc67ba5929120523e6a53f8ed20f519f15f45e98ed6a2075f629e7d0570e864e655b965e248d3ab72810fd8a554b24c33f2e36140f7c6e3d1540808ff9e881
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/4fbc636fbfa96bb2912a78927522d993e158159ac78b823ffaefd35bd04af84a9195ba53f4a31e46b6cb7844e831eaeed1b59e4b0ac9e343a66ccc1e6c47816a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-hsl-color@npm:4.0.4":
|
||||
version: 4.0.4
|
||||
resolution: "@tsparticles/plugin-hsl-color@npm:4.0.4"
|
||||
"@tsparticles/plugin-hsl-color@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/plugin-hsl-color@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.4
|
||||
checksum: 10/a934d6d7a2fdd179525d7a0cefeeea44964621e0abc3d01d150d82eede67cef18e545ab58dd1f57f047120067761c5c65c07896f74e2b38f4c6c1e020fd70c51
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/66875e741f9bcce4ac8a9804889a653f75977a503d158128bab9f13addd1b5a16e2d5b18e86619ff8d8caaf196094dc54356452539b0d0c4eb003cd666023859
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-interactivity@npm:4.0.4":
|
||||
version: 4.0.4
|
||||
resolution: "@tsparticles/plugin-interactivity@npm:4.0.4"
|
||||
"@tsparticles/plugin-interactivity@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/plugin-interactivity@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.4
|
||||
checksum: 10/f32a7f3b3884bef0d78c72a2843b871b47ae6328608b0f2b348b6f67cded6f5f8d3dce6aa79eb609f7f33671c8e7e024eb9a6bc399ce62ec707de77e18213e64
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/81bf958ae357a6d3aec00c7c16ac67cb4b438cf010e6e6e9ef1d851a02592f6849ff412d19048b3178f04a0957bfed74e1cbd48fe60ffc229664bad0f3e3470b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-move@npm:4.0.4":
|
||||
version: 4.0.4
|
||||
resolution: "@tsparticles/plugin-move@npm:4.0.4"
|
||||
"@tsparticles/plugin-move@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/plugin-move@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.4
|
||||
checksum: 10/cb1e0e96b88b0da439b16be6199fa06c894026e3d8a7afd9b983f14bbb7ccce28ed2ee1c1186c25a6e776c91d863c8e151aaaac2e186e46806de1af562c236ce
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/6a7ee5bfa70cbbe228ad199a18ad5ae038d08823052cb47b5f00382c184367c0f78242a66837549194b3d627ec37ac2118d94d87817ca6a2494f237c3c066f3e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-rgb-color@npm:4.0.4":
|
||||
version: 4.0.4
|
||||
resolution: "@tsparticles/plugin-rgb-color@npm:4.0.4"
|
||||
"@tsparticles/plugin-rgb-color@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/plugin-rgb-color@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.4
|
||||
checksum: 10/6795578853546eeb1fe83dcc6f3c70547d259e5f1fad39f069d900ecc1cd2b14486918b35f066f50dd944a047b8a242487e7f1cd449b7d64a21fb0c3a2efbee5
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/c7428953d2674233414a094c24d656ed4a43fa084f082f302f2d70cf54aa9a3bf913f1397879919cbf84ec02850a3e0d9ec85f5a56cc6c3b49fef6d5accd87f7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/preset-links@npm:4.0.4":
|
||||
version: 4.0.4
|
||||
resolution: "@tsparticles/preset-links@npm:4.0.4"
|
||||
"@tsparticles/preset-links@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/preset-links@npm:4.0.5"
|
||||
dependencies:
|
||||
"@tsparticles/basic": "npm:4.0.4"
|
||||
"@tsparticles/engine": "npm:4.0.4"
|
||||
"@tsparticles/interaction-particles-links": "npm:4.0.4"
|
||||
"@tsparticles/plugin-interactivity": "npm:4.0.4"
|
||||
checksum: 10/07be4c9b61593662286d10ee554d2bcf870b7e90689a46c560f7b285f43d27450e62ac9ae7fbfc436769ba98c799a1ed976f2523e2f32ac572d43acc95394e65
|
||||
"@tsparticles/basic": "npm:4.0.5"
|
||||
"@tsparticles/engine": "npm:4.0.5"
|
||||
"@tsparticles/interaction-particles-links": "npm:4.0.5"
|
||||
"@tsparticles/plugin-interactivity": "npm:4.0.5"
|
||||
checksum: 10/a9a2f82bc8725d0cb22d78c0ff2c32ce91078769b9326f0145c6590ef80055f130573dd811ca523e6f48c106143644d6e3633dec40af8504d5bd07558a8a2c09
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/shape-circle@npm:4.0.4":
|
||||
version: 4.0.4
|
||||
resolution: "@tsparticles/shape-circle@npm:4.0.4"
|
||||
"@tsparticles/shape-circle@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/shape-circle@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.4
|
||||
checksum: 10/af56b1ed4e3b3157d68952ed3799f32e88ffd7678938fdf2d9fc3ba1948e91f2e9ff1ca859e48086867d9bb6acd5652419dc2fdcff41423f2e5b93a040d72b01
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/d655f9b26307185404e2a11f9d951e7d191ad8e951706b91be1c99a5aac6a27cb07d4cbfbbbe926221c267df8913a6548edb6e7af0348dff4ff71ab1f5cced0e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-opacity@npm:4.0.4":
|
||||
version: 4.0.4
|
||||
resolution: "@tsparticles/updater-opacity@npm:4.0.4"
|
||||
"@tsparticles/updater-opacity@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/updater-opacity@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.4
|
||||
checksum: 10/6af76aa779b82003329cb241d13eb6f72de167363f9cb51ea18811797bcdfccb064368e8892cb45d16a9ce1df2f579d5552e2d1e9d5d8134cdc9d775bdd2ecfe
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/a4a71c0683602faf939898e1541d4b110f758ea2eeaf9c32bcfe256e6142646a15058bd343d5df902ce6624f17b701738dd308360cc992fa3c3972dc2141fb0b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-out-modes@npm:4.0.4":
|
||||
version: 4.0.4
|
||||
resolution: "@tsparticles/updater-out-modes@npm:4.0.4"
|
||||
"@tsparticles/updater-out-modes@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/updater-out-modes@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.4
|
||||
checksum: 10/83f5a27aa413b1e3fc833c6d460bb65f22eb65970104be5645d5424c8534e315cedac6fcee16b35a2717b20d18ed68d1e274fb1f3ed9a40b3a665521c672ff2d
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/4973ffd626dad38428b0f02d29f1569d1ee02e319262c656e2e502b618b6bd28881a803c06f8fc1b291bd7e9cdd11eab2d0fea5a35e80940a1557f37c6c0af97
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-paint@npm:4.0.4":
|
||||
version: 4.0.4
|
||||
resolution: "@tsparticles/updater-paint@npm:4.0.4"
|
||||
"@tsparticles/updater-paint@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/updater-paint@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.4
|
||||
checksum: 10/878842e3172e02e363edbe1f19ec29251352bb1a13cc489457410bc8036fe057fdf4f5c0c06b64f1f1855ef36dc3e22965cf71ef832cfd7e7425e024102cbec7
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/9933878bb03fa4ebb95ed6c820f684845a5e2fb3ef743779a325bfb92eaeb24dc228a3dfc3c68ba7842b8b93355555743cae540ab8f23506a0c2b953f207ee77
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-size@npm:4.0.4":
|
||||
version: 4.0.4
|
||||
resolution: "@tsparticles/updater-size@npm:4.0.4"
|
||||
"@tsparticles/updater-size@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/updater-size@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.0.4
|
||||
checksum: 10/211cd64448ec1112d062dcef2f56bf5c8e8d3cf575e52f6e9b7f859cc8e15cc2a9134d81b17836a4cef8b6809070df127156bf54a54a90c7cd0ff4a8f16f6de1
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/560086273877b30ce6265785173c5865cd72a6c7524222ce8ff15f026c54d5ffaba7fb111c3c0fb875e21aadcc84776f3efea8dcba3e96b46a4343f69a89fedc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5032,12 +5032,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vitest/coverage-v8@npm:4.1.6":
|
||||
version: 4.1.6
|
||||
resolution: "@vitest/coverage-v8@npm:4.1.6"
|
||||
"@vitest/coverage-v8@npm:4.1.7":
|
||||
version: 4.1.7
|
||||
resolution: "@vitest/coverage-v8@npm:4.1.7"
|
||||
dependencies:
|
||||
"@bcoe/v8-coverage": "npm:^1.0.2"
|
||||
"@vitest/utils": "npm:4.1.6"
|
||||
"@vitest/utils": "npm:4.1.7"
|
||||
ast-v8-to-istanbul: "npm:^1.0.0"
|
||||
istanbul-lib-coverage: "npm:^3.2.2"
|
||||
istanbul-lib-report: "npm:^3.0.1"
|
||||
@@ -5047,34 +5047,34 @@ __metadata:
|
||||
std-env: "npm:^4.0.0-rc.1"
|
||||
tinyrainbow: "npm:^3.1.0"
|
||||
peerDependencies:
|
||||
"@vitest/browser": 4.1.6
|
||||
vitest: 4.1.6
|
||||
"@vitest/browser": 4.1.7
|
||||
vitest: 4.1.7
|
||||
peerDependenciesMeta:
|
||||
"@vitest/browser":
|
||||
optional: true
|
||||
checksum: 10/351ddb5ccebc57ba290b669676db1e24960e4becd9c776a49e2a1ddb02cc2c644870a88010ff044f557fd9082dbe291b8c5e868d562fac93bd02c40d4bedf6bd
|
||||
checksum: 10/ebfe69453f635946449303356fd7b41d6db5ef2449c7e50fe4789930d4b386685c5d8e3587c0fb8ce4010463371dad195471dda2efad673ee26b58d6ff5b7fbe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vitest/expect@npm:4.1.6":
|
||||
version: 4.1.6
|
||||
resolution: "@vitest/expect@npm:4.1.6"
|
||||
"@vitest/expect@npm:4.1.7":
|
||||
version: 4.1.7
|
||||
resolution: "@vitest/expect@npm:4.1.7"
|
||||
dependencies:
|
||||
"@standard-schema/spec": "npm:^1.1.0"
|
||||
"@types/chai": "npm:^5.2.2"
|
||||
"@vitest/spy": "npm:4.1.6"
|
||||
"@vitest/utils": "npm:4.1.6"
|
||||
"@vitest/spy": "npm:4.1.7"
|
||||
"@vitest/utils": "npm:4.1.7"
|
||||
chai: "npm:^6.2.2"
|
||||
tinyrainbow: "npm:^3.1.0"
|
||||
checksum: 10/20de26292c543f7f5076b59fd50a5fa89217755402de89b62e5d8c104c90441413b87b5c1d310a682a310418c76c0d4bd309dd1faf13b1b2dec79dc3bb90fef0
|
||||
checksum: 10/a609af6c0497cd510ce8aed099f18faf6d6642bc8eb3432b688f2b39d7354a04d1c4ee9dc28bcfb9d4be701ceac88384d586592a520a324b3773ea43e8a1e677
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vitest/mocker@npm:4.1.6":
|
||||
version: 4.1.6
|
||||
resolution: "@vitest/mocker@npm:4.1.6"
|
||||
"@vitest/mocker@npm:4.1.7":
|
||||
version: 4.1.7
|
||||
resolution: "@vitest/mocker@npm:4.1.7"
|
||||
dependencies:
|
||||
"@vitest/spy": "npm:4.1.6"
|
||||
"@vitest/spy": "npm:4.1.7"
|
||||
estree-walker: "npm:^3.0.3"
|
||||
magic-string: "npm:^0.30.21"
|
||||
peerDependencies:
|
||||
@@ -5085,56 +5085,56 @@ __metadata:
|
||||
optional: true
|
||||
vite:
|
||||
optional: true
|
||||
checksum: 10/d0669d0b1a8822ec3bc83b5261ead6b05a7e5d8c2077d1f8b9eb0c8507967e54347f16027894be28ca26cf8993e544b8269230a3b78c4eb50c8feb780cb4c688
|
||||
checksum: 10/124d0ec9cc099fde1fca4b065b81a389e9ba2204ecba9729751a0a022d0ffaa34609d9dc60c1f8494ee972c2209035a4476ff1dddc1790e07d1ca28a1103b30d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vitest/pretty-format@npm:4.1.6":
|
||||
version: 4.1.6
|
||||
resolution: "@vitest/pretty-format@npm:4.1.6"
|
||||
"@vitest/pretty-format@npm:4.1.7":
|
||||
version: 4.1.7
|
||||
resolution: "@vitest/pretty-format@npm:4.1.7"
|
||||
dependencies:
|
||||
tinyrainbow: "npm:^3.1.0"
|
||||
checksum: 10/28dc121181fdf619e4a9ea4a3279a63974e54567fc59f82462d3b11d4b72d893cd7966f8a7c1a9365c62eae6dee4c6fb08353074486f708aee50b80462d0bd37
|
||||
checksum: 10/79c86c39173577250955744c3444d8c0c9304c95c7d351b91a916229252c3733a0e969741a8f3441a5c4777b5a4371707ecb747ea4bfd2c07e72ddf1ef621293
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vitest/runner@npm:4.1.6":
|
||||
version: 4.1.6
|
||||
resolution: "@vitest/runner@npm:4.1.6"
|
||||
"@vitest/runner@npm:4.1.7":
|
||||
version: 4.1.7
|
||||
resolution: "@vitest/runner@npm:4.1.7"
|
||||
dependencies:
|
||||
"@vitest/utils": "npm:4.1.6"
|
||||
"@vitest/utils": "npm:4.1.7"
|
||||
pathe: "npm:^2.0.3"
|
||||
checksum: 10/0e175bb61b10ca6cb79a0734a45b3d8b1570806078d53b4f2aa7dbfabd10307c9566460ee8f263a34ac909e8481da614551eee28eaff834fbecd86b4902b845b
|
||||
checksum: 10/429f1e0cc93f66a681d8acc816e21ac41258b07550f9139d004aab103bb06be53e3d91fc66886cef1ba1460a120f5fe4b12d6fe32dafdb1b06740dd119d70f7e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vitest/snapshot@npm:4.1.6":
|
||||
version: 4.1.6
|
||||
resolution: "@vitest/snapshot@npm:4.1.6"
|
||||
"@vitest/snapshot@npm:4.1.7":
|
||||
version: 4.1.7
|
||||
resolution: "@vitest/snapshot@npm:4.1.7"
|
||||
dependencies:
|
||||
"@vitest/pretty-format": "npm:4.1.6"
|
||||
"@vitest/utils": "npm:4.1.6"
|
||||
"@vitest/pretty-format": "npm:4.1.7"
|
||||
"@vitest/utils": "npm:4.1.7"
|
||||
magic-string: "npm:^0.30.21"
|
||||
pathe: "npm:^2.0.3"
|
||||
checksum: 10/167b96971ae6e31a8a7c42063abf3d48590908bdea8ae24d9e5035cd08690e47e15a12ab96cc017e5ddd6324a994b8096c901c8e87ac6e5e617910a2814717fd
|
||||
checksum: 10/ef7001add6724c025772891616338e6081ecdb11a92c084ca1d09c4662cf632e5877bec4cb38056aabc311f29fbe149c89fbf332975829087f3817554fe92cde
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vitest/spy@npm:4.1.6":
|
||||
version: 4.1.6
|
||||
resolution: "@vitest/spy@npm:4.1.6"
|
||||
checksum: 10/6c1bddbf1eaae42af96d66e31f8c14837203707552f60e7a0f512dc2513d285e3de1fdcf057a79a5588fd20ee382ce5a53c1a69430b2a79eb623fd3517d54878
|
||||
"@vitest/spy@npm:4.1.7":
|
||||
version: 4.1.7
|
||||
resolution: "@vitest/spy@npm:4.1.7"
|
||||
checksum: 10/49a9959c615f45ec593379a6d1a238190d08524857a6c4819b724134ce8a1a96d94e20144723d245941ce1ada54d8b00552573810d629880ecb8c3ff03b6d1ad
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vitest/utils@npm:4.1.6":
|
||||
version: 4.1.6
|
||||
resolution: "@vitest/utils@npm:4.1.6"
|
||||
"@vitest/utils@npm:4.1.7":
|
||||
version: 4.1.7
|
||||
resolution: "@vitest/utils@npm:4.1.7"
|
||||
dependencies:
|
||||
"@vitest/pretty-format": "npm:4.1.6"
|
||||
"@vitest/pretty-format": "npm:4.1.7"
|
||||
convert-source-map: "npm:^2.0.0"
|
||||
tinyrainbow: "npm:^3.1.0"
|
||||
checksum: 10/a81506e9f167389e771503ba5bee91a61cd4f09ac386867815b65c12c9c236051fab6450d686c69b41e3fd028461d0195ee4c4ae47fd22ead649716ddb7777b3
|
||||
checksum: 10/9cc729618dade24de3ad6862c288c22e9daac3fda5cae0abc9b6ce87035cc8e7efa2b66c3c124ae08beef462b36761b062e792bbc619798b832a7ea9382ed12a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -6677,10 +6677,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"date-fns@npm:4.2.1":
|
||||
version: 4.2.1
|
||||
resolution: "date-fns@npm:4.2.1"
|
||||
checksum: 10/c2731946a2d022935b3641062cd64bcdd62a65066115fee3490ba360a17fbf42134918493c43acb2c5c995b9ea4a2c7f505f0d9c02e73351cd2899b13e081729
|
||||
"date-fns@npm:4.3.0":
|
||||
version: 4.3.0
|
||||
resolution: "date-fns@npm:4.3.0"
|
||||
checksum: 10/be129ab084a43cb06d2f598008fc3816b9c3767f0e9b7f08c19981b99f361cdee623c63d89e8eb305ebb488778027358127284a2f2aca6da258b3cd33d7366fe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8070,9 +8070,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"generate-license-file@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "generate-license-file@npm:4.1.1"
|
||||
"generate-license-file@npm:4.2.1":
|
||||
version: 4.2.1
|
||||
resolution: "generate-license-file@npm:4.2.1"
|
||||
dependencies:
|
||||
"@commander-js/extra-typings": "npm:^14.0.0"
|
||||
"@npmcli/arborist": "npm:^9.0.0"
|
||||
@@ -8080,14 +8080,14 @@ __metadata:
|
||||
commander: "npm:^14.0.2"
|
||||
cosmiconfig: "npm:^9.0.0"
|
||||
enquirer: "npm:^2.3.6"
|
||||
glob: "npm:^11.0.0"
|
||||
glob: "npm:^13.0.0"
|
||||
json5: "npm:^2.2.3"
|
||||
ora: "npm:^5.4.1"
|
||||
tslib: "npm:^2.3.0"
|
||||
zod: "npm:^3.21.4"
|
||||
zod: "npm:^4.0.0"
|
||||
bin:
|
||||
generate-license-file: bin/generate-license-file
|
||||
checksum: 10/8a9ed962a5cc8f4851d79df7cd8babbc7dec8fbcc2fe25b6a85babfb497572d933ef5189d6bffc204e31e74aa131760ca0b8a90039997cb9a11df7e948b3edf8
|
||||
checksum: 10/03876abeba28efea39a0fbd4d048e2c0fe1f59477c14e3a5e9f7022235f9197a3f451b7f26124dc09da5cc63140376f53b4ee9167f238eee58241ab954e62efd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8272,7 +8272,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"glob@npm:^11.0.0, glob@npm:^11.0.1":
|
||||
"glob@npm:^11.0.1":
|
||||
version: 11.1.0
|
||||
resolution: "glob@npm:11.1.0"
|
||||
dependencies:
|
||||
@@ -8568,7 +8568,7 @@ __metadata:
|
||||
"@codemirror/search": "npm:6.7.0"
|
||||
"@codemirror/state": "npm:6.6.0"
|
||||
"@codemirror/view": "npm:6.43.0"
|
||||
"@date-fns/tz": "npm:1.4.1"
|
||||
"@date-fns/tz": "npm:1.5.0"
|
||||
"@egjs/hammerjs": "npm:2.0.17"
|
||||
"@eslint/js": "npm:10.0.1"
|
||||
"@formatjs/intl-datetimeformat": "npm:7.4.6"
|
||||
@@ -8609,12 +8609,12 @@ __metadata:
|
||||
"@octokit/rest": "npm:22.0.1"
|
||||
"@replit/codemirror-indentation-markers": "npm:6.5.3"
|
||||
"@rsdoctor/rspack-plugin": "npm:1.5.11"
|
||||
"@rspack/core": "npm:2.0.3"
|
||||
"@rspack/core": "npm:2.0.4"
|
||||
"@rspack/dev-server": "npm:2.0.1"
|
||||
"@swc/helpers": "npm:0.5.21"
|
||||
"@thomasloven/round-slider": "npm:0.6.0"
|
||||
"@tsparticles/engine": "npm:4.0.4"
|
||||
"@tsparticles/preset-links": "npm:4.0.4"
|
||||
"@tsparticles/engine": "npm:4.0.5"
|
||||
"@tsparticles/preset-links": "npm:4.0.5"
|
||||
"@types/babel__plugin-transform-runtime": "npm:7.9.5"
|
||||
"@types/chromecast-caf-receiver": "npm:6.0.26"
|
||||
"@types/chromecast-caf-sender": "npm:1.0.11"
|
||||
@@ -8633,7 +8633,7 @@ __metadata:
|
||||
"@types/tar": "npm:7.0.87"
|
||||
"@types/webspeechapi": "npm:0.0.29"
|
||||
"@vibrant/color": "npm:4.0.4"
|
||||
"@vitest/coverage-v8": "npm:4.1.6"
|
||||
"@vitest/coverage-v8": "npm:4.1.7"
|
||||
"@webcomponents/scoped-custom-element-registry": "npm:0.0.10"
|
||||
"@webcomponents/webcomponentsjs": "npm:2.8.0"
|
||||
babel-loader: "npm:10.1.1"
|
||||
@@ -8646,7 +8646,7 @@ __metadata:
|
||||
core-js: "npm:3.49.0"
|
||||
cropperjs: "npm:1.6.2"
|
||||
culori: "npm:4.0.2"
|
||||
date-fns: "npm:4.2.1"
|
||||
date-fns: "npm:4.3.0"
|
||||
deep-clone-simple: "npm:1.1.1"
|
||||
deep-freeze: "npm:0.0.1"
|
||||
del: "npm:8.0.1"
|
||||
@@ -8664,7 +8664,7 @@ __metadata:
|
||||
fancy-log: "npm:2.0.0"
|
||||
fs-extra: "npm:11.3.5"
|
||||
fuse.js: "npm:7.3.0"
|
||||
generate-license-file: "npm:4.1.1"
|
||||
generate-license-file: "npm:4.2.1"
|
||||
glob: "npm:13.0.6"
|
||||
globals: "npm:17.6.0"
|
||||
google-timezones-json: "npm:1.2.0"
|
||||
@@ -8677,8 +8677,8 @@ __metadata:
|
||||
home-assistant-js-websocket: "npm:9.6.0"
|
||||
html-minifier-terser: "npm:7.2.0"
|
||||
husky: "npm:9.1.7"
|
||||
idb-keyval: "npm:6.2.2"
|
||||
intl-messageformat: "npm:11.2.6"
|
||||
idb-keyval: "npm:6.2.4"
|
||||
intl-messageformat: "npm:11.2.7"
|
||||
js-yaml: "npm:4.1.1"
|
||||
jsdom: "npm:29.1.1"
|
||||
jszip: "npm:3.10.1"
|
||||
@@ -8713,12 +8713,12 @@ __metadata:
|
||||
superstruct: "npm:2.0.2"
|
||||
tar: "npm:7.5.15"
|
||||
terser-webpack-plugin: "npm:5.6.0"
|
||||
tinykeys: "npm:3.0.0"
|
||||
tinykeys: "npm:4.0.0"
|
||||
ts-lit-plugin: "npm:2.0.2"
|
||||
typescript: "npm:6.0.3"
|
||||
typescript-eslint: "npm:8.59.4"
|
||||
vite-tsconfig-paths: "npm:6.1.1"
|
||||
vitest: "npm:4.1.6"
|
||||
vitest: "npm:4.1.7"
|
||||
webpack-stats-plugin: "npm:1.1.3"
|
||||
webpackbar: "npm:7.0.0"
|
||||
weekstart: "npm:2.0.0"
|
||||
@@ -8900,10 +8900,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"idb-keyval@npm:6.2.2":
|
||||
version: 6.2.2
|
||||
resolution: "idb-keyval@npm:6.2.2"
|
||||
checksum: 10/8c22342d94deba01066460fe6593f29c78027d0935a6ed7a1da68b28a7a68b89d0830dada4b30d51b779ed1dc1e362007a8939cda651b9ad0807176bd841e8cb
|
||||
"idb-keyval@npm:6.2.4":
|
||||
version: 6.2.4
|
||||
resolution: "idb-keyval@npm:6.2.4"
|
||||
checksum: 10/b1bc874eb582c6bed89dd40a07fe5ca593238b37cded9c604e0cb74b396d2b8caa850519af4467e5ca1b4628682a6102150299db69a393702d0a0718945bc5ec
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9023,13 +9023,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"intl-messageformat@npm:11.2.6":
|
||||
version: 11.2.6
|
||||
resolution: "intl-messageformat@npm:11.2.6"
|
||||
"intl-messageformat@npm:11.2.7":
|
||||
version: 11.2.7
|
||||
resolution: "intl-messageformat@npm:11.2.7"
|
||||
dependencies:
|
||||
"@formatjs/fast-memoize": "npm:3.1.5"
|
||||
"@formatjs/icu-messageformat-parser": "npm:3.5.9"
|
||||
checksum: 10/a93a33c607be110715d76f532f74c0f34f1a4e39e28822333d8dd801d0e5e3f4f9a82e2d88895c179b7583d4a9135b1e6bb4044a8ce84c17fd67f2d78cfd84e1
|
||||
"@formatjs/icu-messageformat-parser": "npm:3.5.10"
|
||||
checksum: 10/ccd566358c90c7d33fbc71206d46501c18e66dac10cfdab1ea226bc16c3b31d25d987619ef355ca24c5b9f415075d2abe5cf383c69dbbe758bd78e7f72017ab9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -13434,10 +13434,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tinykeys@npm:3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "tinykeys@npm:3.0.0"
|
||||
checksum: 10/9ad9bfe3a45366ca77567a23018c8b24dbde4a66050c439f5aa439c66ca578102af0762ee5f7256ee4739b8c1ce64f076fcb8001f867fe842b4271d2bbe9de76
|
||||
"tinykeys@npm:4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "tinykeys@npm:4.0.0"
|
||||
checksum: 10/e31dd0345098bffc87c4df2469f9dcc9cf9a519c592f0d774592f1eeee46798adc66e40058dcaad0c2697eec0f3212c09876c480f3b0ae32e938cb080e664651
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -14191,17 +14191,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vitest@npm:4.1.6":
|
||||
version: 4.1.6
|
||||
resolution: "vitest@npm:4.1.6"
|
||||
"vitest@npm:4.1.7":
|
||||
version: 4.1.7
|
||||
resolution: "vitest@npm:4.1.7"
|
||||
dependencies:
|
||||
"@vitest/expect": "npm:4.1.6"
|
||||
"@vitest/mocker": "npm:4.1.6"
|
||||
"@vitest/pretty-format": "npm:4.1.6"
|
||||
"@vitest/runner": "npm:4.1.6"
|
||||
"@vitest/snapshot": "npm:4.1.6"
|
||||
"@vitest/spy": "npm:4.1.6"
|
||||
"@vitest/utils": "npm:4.1.6"
|
||||
"@vitest/expect": "npm:4.1.7"
|
||||
"@vitest/mocker": "npm:4.1.7"
|
||||
"@vitest/pretty-format": "npm:4.1.7"
|
||||
"@vitest/runner": "npm:4.1.7"
|
||||
"@vitest/snapshot": "npm:4.1.7"
|
||||
"@vitest/spy": "npm:4.1.7"
|
||||
"@vitest/utils": "npm:4.1.7"
|
||||
es-module-lexer: "npm:^2.0.0"
|
||||
expect-type: "npm:^1.3.0"
|
||||
magic-string: "npm:^0.30.21"
|
||||
@@ -14219,12 +14219,12 @@ __metadata:
|
||||
"@edge-runtime/vm": "*"
|
||||
"@opentelemetry/api": ^1.9.0
|
||||
"@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0
|
||||
"@vitest/browser-playwright": 4.1.6
|
||||
"@vitest/browser-preview": 4.1.6
|
||||
"@vitest/browser-webdriverio": 4.1.6
|
||||
"@vitest/coverage-istanbul": 4.1.6
|
||||
"@vitest/coverage-v8": 4.1.6
|
||||
"@vitest/ui": 4.1.6
|
||||
"@vitest/browser-playwright": 4.1.7
|
||||
"@vitest/browser-preview": 4.1.7
|
||||
"@vitest/browser-webdriverio": 4.1.7
|
||||
"@vitest/coverage-istanbul": 4.1.7
|
||||
"@vitest/coverage-v8": 4.1.7
|
||||
"@vitest/ui": 4.1.7
|
||||
happy-dom: "*"
|
||||
jsdom: "*"
|
||||
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
@@ -14255,7 +14255,7 @@ __metadata:
|
||||
optional: false
|
||||
bin:
|
||||
vitest: vitest.mjs
|
||||
checksum: 10/3816121537930455e5338b5b3305179fa6c68d6cbba50e5d8ca8dcb2c0410887ed38aca61e0d2da9f673af1cc1f20278eef941fc4756644e6f8f96366822b8e6
|
||||
checksum: 10/23ce0ce8bf81856c1acf983c6138efda5d01b60cbdc5734abd0948f3b39cde14ea7bf0981a2ec8a6b05fe7f3658b211116997fd658fcd20c2f5740b5465502ca
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -15099,10 +15099,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:^3.21.4":
|
||||
version: 3.25.76
|
||||
resolution: "zod@npm:3.25.76"
|
||||
checksum: 10/f0c963ec40cd96858451d1690404d603d36507c1fc9682f2dae59ab38b578687d542708a7fdbf645f77926f78c9ed558f57c3d3aa226c285f798df0c4da16995
|
||||
"zod@npm:^4.0.0":
|
||||
version: 4.4.3
|
||||
resolution: "zod@npm:4.4.3"
|
||||
checksum: 10/804b9a42aa8f35f2b3c5a8dff906291cb749115f83ee2afe3576d70b5b5c53c965365c7f4967690647a9c54af9838ff232a85ff9577a0a36c44b68bc6cdefe36
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user