mirror of
https://github.com/home-assistant/frontend.git
synced 2026-06-02 06:21:53 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a3e9c89486 | |||
| fb2888469d | |||
| df639a3f34 |
@@ -57,9 +57,7 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
|
||||
if (descriptionContent === "") {
|
||||
hasDescription = false;
|
||||
} else {
|
||||
descriptionContent = marked(descriptionContent)
|
||||
.replace(/\\/g, "\\\\")
|
||||
.replace(/`/g, "\\`");
|
||||
descriptionContent = marked(descriptionContent).replace(/`/g, "\\`");
|
||||
fs.mkdirSync(path.resolve(galleryBuild, category), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.resolve(galleryBuild, `${pageId}-description.ts`),
|
||||
|
||||
@@ -13,7 +13,7 @@ Our dialogs are based on the latest version of Material Design. Please note that
|
||||
|
||||
- Dialogs have a max width of 560px. Alert and confirmation dialogs have a fixed width of 320px. If you need more width, consider a dedicated page instead.
|
||||
- The close X-icon is on the top left, on all screen sizes. Except for alert and confirmation dialogs, they only have buttons and no X-icon. This is different compared to the Material guidelines.
|
||||
- Dialogs can't be closed with ESC or clicked outside of the dialog when there is a form that the user needs to fill out. Instead it will animate "no" by a little shake.
|
||||
- Dialogs can't be closed with ESC or clicked outside of the dialog when there is a form that the user **has made changes to**. Instead it will animate "no" by a little shake.
|
||||
- Extra icon buttons are on the top right, for example help, settings and expand dialog. More than 2 icon buttons, they will be in an overflow menu.
|
||||
- The submit button is grouped with a cancel button at the bottom right, on all screen sizes. Fullscreen mobile dialogs have them sticky at the bottom.
|
||||
- Keep the labels short, for example `Save`, `Delete`, `Enable`.
|
||||
|
||||
+5
-5
@@ -70,8 +70,8 @@
|
||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||
"@swc/helpers": "0.5.23",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@tsparticles/engine": "4.1.0",
|
||||
"@tsparticles/preset-links": "4.1.0",
|
||||
"@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",
|
||||
@@ -82,7 +82,7 @@
|
||||
"core-js": "3.49.0",
|
||||
"cropperjs": "1.6.2",
|
||||
"culori": "4.0.2",
|
||||
"date-fns": "4.4.0",
|
||||
"date-fns": "4.3.0",
|
||||
"deep-clone-simple": "1.1.1",
|
||||
"deep-freeze": "0.0.1",
|
||||
"dialog-polyfill": "0.5.6",
|
||||
@@ -138,7 +138,7 @@
|
||||
"@octokit/rest": "22.0.1",
|
||||
"@rsdoctor/rspack-plugin": "1.5.12",
|
||||
"@rspack/core": "2.0.5",
|
||||
"@rspack/dev-server": "2.0.3",
|
||||
"@rspack/dev-server": "2.0.1",
|
||||
"@types/chromecast-caf-receiver": "6.0.26",
|
||||
"@types/chromecast-caf-sender": "1.0.11",
|
||||
"@types/color-name": "2.0.0",
|
||||
@@ -158,7 +158,7 @@
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"browserslist-useragent-regexp": "4.1.4",
|
||||
"del": "8.0.1",
|
||||
"eslint": "10.4.1",
|
||||
"eslint": "10.4.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-import-resolver-webpack": "0.13.11",
|
||||
"eslint-plugin-import-x": "4.16.2",
|
||||
|
||||
@@ -17,19 +17,6 @@ export interface NavigateOptions {
|
||||
// max time to wait for dialogs to close before navigating
|
||||
const DIALOG_WAIT_TIMEOUT = 500;
|
||||
|
||||
/**
|
||||
* Stash a destination URL in the current history entry's state. If the page
|
||||
* is refreshed while a dialog is open, urlSyncMixin will navigate to this URL
|
||||
* on load instead of cleaning up the stale dialog state by going back.
|
||||
* The current URL is not changed.
|
||||
*/
|
||||
export const setRefreshUrl = (path: string) => {
|
||||
mainWindow.history.replaceState(
|
||||
{ ...mainWindow.history.state, refreshUrl: path },
|
||||
""
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensures all dialogs are closed before navigation.
|
||||
* Returns true if navigation can proceed, false if a dialog refused to close.
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import type { PickerComboBoxItem } from "../../components/ha-picker-combo-box";
|
||||
import type { RelatedResult } from "../../data/search";
|
||||
|
||||
export interface RelatedIdSets {
|
||||
areas: Set<string>;
|
||||
devices: Set<string>;
|
||||
entities: Set<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a set of related IDs for a given related result.
|
||||
* @param related - The related result to build the sets from.
|
||||
* @returns The related ID sets.
|
||||
*/
|
||||
export const buildRelatedIdSets = (related?: RelatedResult): RelatedIdSets => ({
|
||||
areas: new Set(related?.area || []),
|
||||
devices: new Set(related?.device || []),
|
||||
entities: new Set(related?.entity || []),
|
||||
});
|
||||
|
||||
/**
|
||||
* Stable partition sort: related items float to the top,
|
||||
* preserving relative order (e.g. Fuse score) within each group.
|
||||
* @param items - The items to sort.
|
||||
* @returns The sorted items.
|
||||
*/
|
||||
export const sortRelatedFirst = (
|
||||
items: PickerComboBoxItem[]
|
||||
): PickerComboBoxItem[] =>
|
||||
[...items].sort((a, b) => {
|
||||
const aRelated = Boolean(a.isRelated);
|
||||
const bRelated = Boolean(b.isRelated);
|
||||
if (aRelated === bRelated) {
|
||||
return 0;
|
||||
}
|
||||
return aRelated ? -1 : 1;
|
||||
});
|
||||
@@ -77,7 +77,7 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
| "bottom-start"
|
||||
| "bottom-end"
|
||||
| "left-start"
|
||||
| "left-end" = "bottom";
|
||||
| "left-end" = "bottom-start";
|
||||
|
||||
/** If set picker shows an add button instead of textbox when value isn't set */
|
||||
@property({ attribute: "add-button-label" }) public addButtonLabel?: string;
|
||||
|
||||
@@ -121,6 +121,7 @@ export class HaIconPicker extends LitElement {
|
||||
.label=${this.label}
|
||||
.value=${this._value}
|
||||
.searchFn=${this._filterIcons}
|
||||
popover-placement="bottom-start"
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
<slot name="start"></slot>
|
||||
|
||||
@@ -152,6 +152,7 @@ export class HaLanguagePicker extends LitElement {
|
||||
<ha-generic-picker
|
||||
.hass=${this.hass}
|
||||
.autofocus=${this.autofocus}
|
||||
popover-placement="bottom-end"
|
||||
.notFoundLabel=${this._notFoundLabel}
|
||||
.emptyLabel=${this.hass?.localize(
|
||||
"ui.components.language-picker.no_languages"
|
||||
|
||||
@@ -82,6 +82,7 @@ export class HaThemePicker extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
@value-changed=${this._changed}
|
||||
popover-placement="bottom"
|
||||
></ha-generic-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { createContext } from "@lit/context";
|
||||
import type { HassConfig } from "home-assistant-js-websocket";
|
||||
import type { HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import type {
|
||||
HomeAssistant,
|
||||
HomeAssistantApi,
|
||||
@@ -11,12 +10,10 @@ import type {
|
||||
HomeAssistantRegistries,
|
||||
HomeAssistantUI,
|
||||
} from "../../types";
|
||||
import type { RelatedIdSets } from "../../common/search/related-context";
|
||||
import type { ConfigEntry } from "../config_entries";
|
||||
import type { EntityRegistryEntry } from "../entity/entity_registry";
|
||||
import type { DomainManifestLookup } from "../integration";
|
||||
import type { LabelRegistryEntry } from "../label/label_registry";
|
||||
import type { ItemType } from "../search";
|
||||
|
||||
/**
|
||||
* Entity, device, area, and floor registries
|
||||
@@ -97,11 +94,6 @@ export const areasContext = createContext<HomeAssistant["areas"]>("areas");
|
||||
*/
|
||||
export const floorsContext = createContext<HomeAssistant["floors"]>("floors");
|
||||
|
||||
/**
|
||||
* Whether the main Home Assistant viewport is using the narrow layout.
|
||||
*/
|
||||
export const narrowViewportContext = createContext<boolean>("narrowViewport");
|
||||
|
||||
// #region lazy-contexts
|
||||
|
||||
/**
|
||||
@@ -170,30 +162,3 @@ export const panelsContext = createContext<HomeAssistant["panels"]>("panels");
|
||||
export const authContext = createContext<HomeAssistant["auth"]>("auth");
|
||||
|
||||
// #endregion deprecated-contexts
|
||||
|
||||
// #region related-context
|
||||
|
||||
export interface RelatedContextItem {
|
||||
itemType: ItemType;
|
||||
itemId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolved related entities/devices/areas for the current page context.
|
||||
* Set by `RelatedContextProvider` when a page fires `hass-related-context`.
|
||||
* Cleared on navigation.
|
||||
*/
|
||||
export const relatedContext = createContext<RelatedIdSets | undefined>(
|
||||
"related"
|
||||
);
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"hass-related-context": RelatedContextItem | undefined;
|
||||
}
|
||||
interface HTMLElementEventMap {
|
||||
"hass-related-context": HASSDomEvent<RelatedContextItem | undefined>;
|
||||
}
|
||||
}
|
||||
|
||||
// #endregion related-context
|
||||
|
||||
+2
-2
@@ -36,11 +36,11 @@ export type ItemType =
|
||||
| "script_blueprint";
|
||||
|
||||
export const findRelated = (
|
||||
hass: Pick<HomeAssistant, "callWS">,
|
||||
hass: HomeAssistant,
|
||||
itemType: ItemType,
|
||||
itemId: string
|
||||
): Promise<RelatedResult> =>
|
||||
hass.callWS<RelatedResult>({
|
||||
hass.callWS({
|
||||
type: "search/related",
|
||||
item_type: itemType,
|
||||
item_id: itemId,
|
||||
|
||||
@@ -87,19 +87,6 @@ const HOME_ASSISTANT_CORE_TITLE = "Home Assistant Core";
|
||||
const HOME_ASSISTANT_SUPERVISOR_TITLE = "Home Assistant Supervisor";
|
||||
const HOME_ASSISTANT_OS_TITLE = "Home Assistant Operating System";
|
||||
|
||||
// The hassio integration sets these as hard-coded `_attr_title` on the Core,
|
||||
// Operating System, and Supervisor update entities. They are not translated,
|
||||
// so a title comparison is the reliable way to identify them without depending
|
||||
// on the (lazily-fetched) entity sources.
|
||||
export const isSystemUpdate = (entity: UpdateEntity): boolean => {
|
||||
const title = entity.attributes.title || "";
|
||||
return (
|
||||
title === HOME_ASSISTANT_CORE_TITLE ||
|
||||
title === HOME_ASSISTANT_OS_TITLE ||
|
||||
title === HOME_ASSISTANT_SUPERVISOR_TITLE
|
||||
);
|
||||
};
|
||||
|
||||
export const filterUpdateEntities = (
|
||||
entities: HassEntities,
|
||||
language?: string
|
||||
@@ -146,11 +133,6 @@ export const filterUpdateEntitiesParameterized = (
|
||||
return updateCanInstall(entity, showSkipped);
|
||||
});
|
||||
|
||||
export const installUpdates = (hass: HomeAssistant, entityIds: string[]) =>
|
||||
hass.callService("update", "install", {
|
||||
entity_id: entityIds,
|
||||
});
|
||||
|
||||
export const checkForEntityUpdates = async (
|
||||
element: HTMLElement,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { mdiDevices } from "@mdi/js";
|
||||
import { consume } from "@lit/context";
|
||||
import Fuse from "fuse.js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
@@ -48,9 +47,7 @@ import {
|
||||
type ActionCommandComboBoxItem,
|
||||
type NavigationComboBoxItem,
|
||||
} from "../../data/quick_bar";
|
||||
import type { RelatedIdSets } from "../../common/search/related-context";
|
||||
import { sortRelatedFirst } from "../../common/search/related-context";
|
||||
import { relatedContext } from "../../data/context";
|
||||
import type { RelatedResult } from "../../data/search";
|
||||
import {
|
||||
multiTermSortedSearch,
|
||||
type FuseWeightedKey,
|
||||
@@ -73,10 +70,6 @@ const SEPARATOR = "________";
|
||||
export class QuickBar extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state()
|
||||
@consume({ context: relatedContext, subscribe: true })
|
||||
private _relatedIdSets?: RelatedIdSets;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
@state() private _loading = true;
|
||||
@@ -87,6 +80,8 @@ export class QuickBar extends LitElement {
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
@state() private _relatedResult?: RelatedResult;
|
||||
|
||||
@query("ha-picker-combo-box") private _comboBox?: HaPickerComboBox;
|
||||
|
||||
private get _showEntityId() {
|
||||
@@ -113,6 +108,8 @@ export class QuickBar extends LitElement {
|
||||
this._selectedSection = effectiveQuickBarMode(this.hass.user, params.mode);
|
||||
this._showHint = params.showHint ?? false;
|
||||
|
||||
this._relatedResult = params.contextItem ? params.related : undefined;
|
||||
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
@@ -435,7 +432,7 @@ export class QuickBar extends LitElement {
|
||||
this._selectedSection = section as QuickBarSection | undefined;
|
||||
return this._getItemsMemoized(
|
||||
this._configEntryLookup,
|
||||
this._relatedIdSets,
|
||||
this._relatedResult,
|
||||
searchString,
|
||||
this._selectedSection
|
||||
);
|
||||
@@ -444,11 +441,12 @@ export class QuickBar extends LitElement {
|
||||
private _getItemsMemoized = memoizeOne(
|
||||
(
|
||||
configEntryLookup: Record<string, ConfigEntry>,
|
||||
relatedIdSets: RelatedIdSets | undefined,
|
||||
relatedResult: RelatedResult | undefined,
|
||||
filter?: string,
|
||||
section?: QuickBarSection
|
||||
) => {
|
||||
const items: (string | PickerComboBoxItem)[] = [];
|
||||
const relatedIdSets = this._getRelatedIdSets(relatedResult);
|
||||
|
||||
if (!section || section === "navigate") {
|
||||
let navigateItems = this._generateNavigationCommandsMemoized(
|
||||
@@ -500,7 +498,7 @@ export class QuickBar extends LitElement {
|
||||
let entityItems = this._getEntitiesMemoized(this.hass);
|
||||
|
||||
// Mark related items
|
||||
if (relatedIdSets?.entities.size) {
|
||||
if (relatedIdSets.entities.size > 0) {
|
||||
entityItems = entityItems.map((item) => ({
|
||||
...item,
|
||||
isRelated: relatedIdSets.entities.has(
|
||||
@@ -510,7 +508,7 @@ export class QuickBar extends LitElement {
|
||||
}
|
||||
|
||||
if (filter) {
|
||||
entityItems = sortRelatedFirst(
|
||||
entityItems = this._sortRelatedFirst(
|
||||
this._filterGroup(
|
||||
"entity",
|
||||
entityItems,
|
||||
@@ -539,7 +537,7 @@ export class QuickBar extends LitElement {
|
||||
);
|
||||
|
||||
// Mark related items
|
||||
if (relatedIdSets?.devices.size) {
|
||||
if (relatedIdSets.devices.size > 0) {
|
||||
deviceItems = deviceItems.map((item) => {
|
||||
const deviceId = item.id.split(SEPARATOR)[1];
|
||||
return {
|
||||
@@ -550,7 +548,7 @@ export class QuickBar extends LitElement {
|
||||
}
|
||||
|
||||
if (filter) {
|
||||
deviceItems = sortRelatedFirst(
|
||||
deviceItems = this._sortRelatedFirst(
|
||||
this._filterGroup("device", deviceItems, filter, deviceComboBoxKeys)
|
||||
);
|
||||
} else {
|
||||
@@ -571,7 +569,7 @@ export class QuickBar extends LitElement {
|
||||
let areaItems = this._getAreasMemoized(this.hass);
|
||||
|
||||
// Mark related items
|
||||
if (relatedIdSets?.areas.size) {
|
||||
if (relatedIdSets.areas.size > 0) {
|
||||
areaItems = areaItems.map((item) => {
|
||||
const areaId = item.id.split(SEPARATOR)[1];
|
||||
return {
|
||||
@@ -582,7 +580,7 @@ export class QuickBar extends LitElement {
|
||||
}
|
||||
|
||||
if (filter) {
|
||||
areaItems = sortRelatedFirst(
|
||||
areaItems = this._sortRelatedFirst(
|
||||
this._filterGroup("area", areaItems, filter, areaComboBoxKeys)
|
||||
);
|
||||
} else {
|
||||
@@ -603,6 +601,12 @@ export class QuickBar extends LitElement {
|
||||
}
|
||||
);
|
||||
|
||||
private _getRelatedIdSets = memoizeOne((related?: RelatedResult) => ({
|
||||
entities: new Set(related?.entity || []),
|
||||
devices: new Set(related?.device || []),
|
||||
areas: new Set(related?.area || []),
|
||||
}));
|
||||
|
||||
private _getEntitiesMemoized = memoizeOne((hass: HomeAssistant) =>
|
||||
getEntities(
|
||||
hass,
|
||||
@@ -701,13 +705,10 @@ export class QuickBar extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private _sortBySortingLabel = (
|
||||
entityA: PickerComboBoxItem,
|
||||
entityB: PickerComboBoxItem
|
||||
) =>
|
||||
private _sortBySortingLabel = (entityA, entityB) =>
|
||||
caseInsensitiveStringCompare(
|
||||
entityA.sorting_label!,
|
||||
entityB.sorting_label!,
|
||||
(entityA as PickerComboBoxItem).sorting_label!,
|
||||
(entityB as PickerComboBoxItem).sorting_label!,
|
||||
this.hass.locale.language
|
||||
);
|
||||
|
||||
@@ -718,6 +719,16 @@ export class QuickBar extends LitElement {
|
||||
return this._sortBySortingLabel(a, b);
|
||||
});
|
||||
|
||||
private _sortRelatedFirst = (items: PickerComboBoxItem[]) =>
|
||||
[...items].sort((a, b) => {
|
||||
const aRelated = Boolean(a.isRelated);
|
||||
const bRelated = Boolean(b.isRelated);
|
||||
if (aRelated === bRelated) {
|
||||
return 0;
|
||||
}
|
||||
return aRelated ? -1 : 1;
|
||||
});
|
||||
|
||||
// #endregion data
|
||||
|
||||
// #region interaction
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { ItemType, RelatedResult } from "../../data/search";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { closeDialog } from "../make-dialog-manager";
|
||||
|
||||
@@ -9,10 +10,17 @@ export type QuickBarSection =
|
||||
| "navigate"
|
||||
| "command";
|
||||
|
||||
export interface QuickBarContextItem {
|
||||
itemType: ItemType;
|
||||
itemId: string;
|
||||
}
|
||||
|
||||
export interface QuickBarParams {
|
||||
entityFilter?: string;
|
||||
mode?: QuickBarSection;
|
||||
showHint?: boolean;
|
||||
contextItem?: QuickBarContextItem;
|
||||
related?: RelatedResult;
|
||||
}
|
||||
|
||||
/** Non-admin users cannot scope the bar to command, device, or area (those sections are admin-only). */
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ContextProvider } from "@lit/context";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@@ -8,7 +7,6 @@ import { listenMediaQuery } from "../common/dom/media_query";
|
||||
import { toggleAttribute } from "../common/dom/toggle_attribute";
|
||||
import { computeRTLDirection } from "../common/util/compute_rtl";
|
||||
import "../components/ha-drawer";
|
||||
import { narrowViewportContext } from "../data/context";
|
||||
import { showNotificationDrawer } from "../dialogs/notifications/show-notification-drawer";
|
||||
import type { HomeAssistant, Route } from "../types";
|
||||
import "./partial-panel-resolver";
|
||||
@@ -38,11 +36,6 @@ export class HomeAssistantMain extends LitElement {
|
||||
|
||||
@state() private _drawerOpen = false;
|
||||
|
||||
private _narrowViewportProvider = new ContextProvider(this, {
|
||||
context: narrowViewportContext,
|
||||
initialValue: this.narrow,
|
||||
});
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
listenMediaQuery("(max-width: 870px)", (matches) => {
|
||||
@@ -73,6 +66,7 @@ export class HomeAssistantMain extends LitElement {
|
||||
></ha-sidebar>
|
||||
${isPanelReady
|
||||
? html`<partial-panel-resolver
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
.route=${this.route}
|
||||
slot="appContent"
|
||||
@@ -127,10 +121,6 @@ export class HomeAssistantMain extends LitElement {
|
||||
}
|
||||
|
||||
public willUpdate(changedProps: PropertyValues<this>) {
|
||||
if (changedProps.has("narrow")) {
|
||||
this._narrowViewportProvider.setValue(this.narrow);
|
||||
}
|
||||
|
||||
if (changedProps.has("route") && this._sidebarNarrow) {
|
||||
this._drawerOpen = false;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
STATE_NOT_RUNNING,
|
||||
STATE_RUNNING,
|
||||
STATE_STARTING,
|
||||
} from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { deepActiveElement } from "../common/dom/deep-active-element";
|
||||
import { deepEqual } from "../common/util/deep-equal";
|
||||
import { narrowViewportContext } from "../data/context";
|
||||
import { getDefaultPanel } from "../data/panel";
|
||||
import type { CustomPanelInfo } from "../data/panel_custom";
|
||||
import type { HomeAssistant, Panels } from "../types";
|
||||
@@ -45,9 +43,7 @@ const COMPONENTS = {
|
||||
class PartialPanelResolver extends HassRouterPage {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state()
|
||||
@consume({ context: narrowViewportContext, subscribe: true })
|
||||
private _narrow = false;
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
private _waitForStart = false;
|
||||
|
||||
@@ -96,7 +92,7 @@ class PartialPanelResolver extends HassRouterPage {
|
||||
const el = super.createLoadingScreen();
|
||||
el.rootnav = true;
|
||||
el.hass = this.hass;
|
||||
el.narrow = this._narrow;
|
||||
el.narrow = this.narrow;
|
||||
return el;
|
||||
}
|
||||
|
||||
@@ -104,7 +100,7 @@ class PartialPanelResolver extends HassRouterPage {
|
||||
const hass = this.hass;
|
||||
|
||||
el.hass = hass;
|
||||
el.narrow = this._narrow;
|
||||
el.narrow = this.narrow;
|
||||
el.route = this.routeTail;
|
||||
el.panel = hass.panels[this._currentPage];
|
||||
}
|
||||
|
||||
@@ -198,12 +198,10 @@ class SupervisorAppInfo extends MobileAwareMixin(LitElement) {
|
||||
/>
|
||||
`
|
||||
: nothing}
|
||||
${!this.narrow
|
||||
? getAppDisplayName(
|
||||
this._currentAddon.name,
|
||||
this._currentAddon.stage
|
||||
)
|
||||
: nothing}
|
||||
${getAppDisplayName(
|
||||
this._currentAddon.name,
|
||||
this._currentAddon.stage
|
||||
)}
|
||||
<div class="description">
|
||||
${this._currentAddon.version
|
||||
? html`
|
||||
|
||||
@@ -20,10 +20,6 @@ import type {
|
||||
LocalizeKeys,
|
||||
} from "../../../../common/translations/localize";
|
||||
import { computeRTL } from "../../../../common/util/compute_rtl";
|
||||
import {
|
||||
sortRelatedFirst,
|
||||
type RelatedIdSets,
|
||||
} from "../../../../common/search/related-context";
|
||||
import "../../../../components/chips/ha-chip-set";
|
||||
import "../../../../components/chips/ha-filter-chip";
|
||||
import "../../../../components/entity/state-badge";
|
||||
@@ -44,7 +40,7 @@ import {
|
||||
} from "../../../../data/area_floor_picker";
|
||||
import { CONDITION_BUILDING_BLOCKS_GROUP } from "../../../../data/condition";
|
||||
import type { ConfigEntry } from "../../../../data/config_entries";
|
||||
import { labelsContext, relatedContext } from "../../../../data/context";
|
||||
import { labelsContext } from "../../../../data/context";
|
||||
import {
|
||||
deviceComboBoxKeys,
|
||||
getDevices,
|
||||
@@ -133,10 +129,6 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
| "condition"
|
||||
| "action";
|
||||
|
||||
@state()
|
||||
@consume({ context: relatedContext, subscribe: true })
|
||||
private _relatedIdSets?: RelatedIdSets;
|
||||
|
||||
@state() private _searchSectionTitle?: string;
|
||||
|
||||
@state() private _selectedSearchSection?: SearchSection;
|
||||
@@ -202,8 +194,7 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
this.configEntryLookup,
|
||||
this.items,
|
||||
this.newTriggersAndConditions,
|
||||
this._selectedSearchSection,
|
||||
this._relatedIdSets
|
||||
this._selectedSearchSection
|
||||
);
|
||||
|
||||
let emptySearchTranslation: string | undefined;
|
||||
@@ -496,8 +487,7 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
configEntryLookup: Record<string, ConfigEntry>,
|
||||
automationItems: AddAutomationElementListItem[],
|
||||
newTriggersAndConditions: boolean,
|
||||
selectedSection?: SearchSection,
|
||||
relatedIdSets?: RelatedIdSets
|
||||
selectedSection?: SearchSection
|
||||
) => {
|
||||
const resultItems: (
|
||||
| string
|
||||
@@ -578,26 +568,13 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
`entity${TARGET_SEPARATOR}`
|
||||
);
|
||||
|
||||
if (relatedIdSets?.entities.size) {
|
||||
entityItems = entityItems.map((item) => ({
|
||||
...item,
|
||||
isRelated: relatedIdSets.entities.has(
|
||||
(item as EntityComboBoxItem).stateObj?.entity_id || ""
|
||||
),
|
||||
})) as EntityComboBoxItem[];
|
||||
}
|
||||
|
||||
if (searchTerm) {
|
||||
entityItems = sortRelatedFirst(
|
||||
this._filterGroup(
|
||||
"entity",
|
||||
entityItems,
|
||||
searchTerm,
|
||||
entityComboBoxKeys
|
||||
)
|
||||
entityItems = this._filterGroup(
|
||||
"entity",
|
||||
entityItems,
|
||||
searchTerm,
|
||||
entityComboBoxKeys
|
||||
) as EntityComboBoxItem[];
|
||||
} else if (relatedIdSets?.entities.size) {
|
||||
entityItems = sortRelatedFirst(entityItems) as EntityComboBoxItem[];
|
||||
}
|
||||
|
||||
if (!selectedSection && entityItems.length) {
|
||||
@@ -624,26 +601,13 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
`device${TARGET_SEPARATOR}`
|
||||
);
|
||||
|
||||
if (relatedIdSets?.devices.size) {
|
||||
deviceItems = deviceItems.map((item) => ({
|
||||
...item,
|
||||
isRelated: relatedIdSets.devices.has(
|
||||
item.id.split(TARGET_SEPARATOR)[1] || ""
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
if (searchTerm) {
|
||||
deviceItems = sortRelatedFirst(
|
||||
this._filterGroup(
|
||||
"device",
|
||||
deviceItems,
|
||||
searchTerm,
|
||||
deviceComboBoxKeys
|
||||
)
|
||||
deviceItems = this._filterGroup(
|
||||
"device",
|
||||
deviceItems,
|
||||
searchTerm,
|
||||
deviceComboBoxKeys
|
||||
);
|
||||
} else if (relatedIdSets?.devices.size) {
|
||||
deviceItems = sortRelatedFirst(deviceItems);
|
||||
}
|
||||
|
||||
if (!selectedSection && deviceItems.length) {
|
||||
@@ -675,31 +639,13 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
undefined
|
||||
);
|
||||
|
||||
if (relatedIdSets?.areas.size) {
|
||||
areasAndFloors = areasAndFloors.map((item) => ({
|
||||
...item,
|
||||
isRelated:
|
||||
item.type === "area"
|
||||
? relatedIdSets.areas.has(
|
||||
item.id.split(TARGET_SEPARATOR)[1] || ""
|
||||
)
|
||||
: false,
|
||||
})) as FloorComboBoxItem[];
|
||||
}
|
||||
|
||||
if (searchTerm) {
|
||||
areasAndFloors = sortRelatedFirst(
|
||||
this._filterGroup(
|
||||
"area",
|
||||
areasAndFloors,
|
||||
searchTerm,
|
||||
areaFloorComboBoxKeys,
|
||||
false
|
||||
)
|
||||
) as FloorComboBoxItem[];
|
||||
} else if (relatedIdSets?.areas.size) {
|
||||
areasAndFloors = sortRelatedFirst(
|
||||
areasAndFloors
|
||||
areasAndFloors = this._filterGroup(
|
||||
"area",
|
||||
areasAndFloors,
|
||||
searchTerm,
|
||||
areaFloorComboBoxKeys,
|
||||
false
|
||||
) as FloorComboBoxItem[];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import { consume } from "@lit/context";
|
||||
import type {
|
||||
CSSResult,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import type { CSSResult, LitElement, TemplateResult } from "lit";
|
||||
import { css, html } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { transform } from "../../../common/decorators/transform";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { goBack, navigate } from "../../../common/navigate";
|
||||
import { afterNextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/animation/ha-fade-in";
|
||||
@@ -142,21 +136,6 @@ export const AutomationScriptEditorMixin = <TConfig extends BaseEditorConfig>(
|
||||
value: PromiseLike<EntityRegistryEntry> | EntityRegistryEntry
|
||||
) => void;
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
if (changedProps.has("registryEntry")) {
|
||||
const areaId = this.registryEntry?.area_id;
|
||||
if (areaId) {
|
||||
fireEvent(this, "hass-related-context", {
|
||||
itemType: "area",
|
||||
itemId: areaId,
|
||||
});
|
||||
} else {
|
||||
fireEvent(this, "hass-related-context", undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected renderLoading(): TemplateResult {
|
||||
return html`
|
||||
<ha-fade-in .delay=${500}>
|
||||
|
||||
@@ -11,15 +11,10 @@ import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-dropdown";
|
||||
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import type { EntitySources } from "../../../data/entity/entity_sources";
|
||||
import { fetchEntitySourcesWithCache } from "../../../data/entity/entity_sources";
|
||||
import { extractApiErrorMessage } from "../../../data/hassio/common";
|
||||
import type {
|
||||
HassioSupervisorInfo,
|
||||
@@ -30,16 +25,9 @@ import {
|
||||
reloadSupervisor,
|
||||
setSupervisorOption,
|
||||
} from "../../../data/hassio/supervisor";
|
||||
import { domainToName } from "../../../data/integration";
|
||||
import type { UpdateEntity } from "../../../data/update";
|
||||
import {
|
||||
checkForEntityUpdates,
|
||||
filterUpdateEntities,
|
||||
filterUpdateEntitiesParameterized,
|
||||
installUpdates,
|
||||
isSystemUpdate,
|
||||
latestVersionIsSkipped,
|
||||
UpdateEntityFeature,
|
||||
} from "../../../data/update";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-subpage";
|
||||
@@ -47,17 +35,6 @@ import type { HomeAssistant } from "../../../types";
|
||||
import "../dashboard/ha-config-updates";
|
||||
import { showJoinBetaDialog } from "./updates/show-dialog-join-beta";
|
||||
|
||||
interface UpdateGroup {
|
||||
key: string;
|
||||
title: string;
|
||||
entities: UpdateEntity[];
|
||||
showUpdateAll: boolean;
|
||||
}
|
||||
|
||||
const SYSTEM_KEY = "__system__";
|
||||
const APPS_KEY = "__apps__";
|
||||
const INTEGRATIONS_KEY = "__integrations__";
|
||||
|
||||
@customElement("ha-config-section-updates")
|
||||
class HaConfigSectionUpdates extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -70,86 +47,23 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
|
||||
@state() private _supervisorInfo?: HassioSupervisorInfo;
|
||||
|
||||
@state() private _entitySources?: EntitySources;
|
||||
|
||||
@state() private _loadedIntegrationTitles = new Set<string>();
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues<this>) {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
if (isComponentLoaded(this.hass.config, "hassio")) {
|
||||
this._refreshSupervisorInfo();
|
||||
}
|
||||
|
||||
this._loadEntitySources();
|
||||
}
|
||||
|
||||
private async _loadEntitySources() {
|
||||
try {
|
||||
this._entitySources = await fetchEntitySourcesWithCache(this.hass);
|
||||
} catch (_err) {
|
||||
// Non-fatal: grouping falls back to entity registry platform lookup.
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues<this>) {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.has("hass")) {
|
||||
this._loadIntegrationTitles();
|
||||
}
|
||||
}
|
||||
|
||||
private _collectUpdateDomains = memoizeOne(
|
||||
(states: HassEntities, entities: HomeAssistant["entities"]) => {
|
||||
const domains = new Set<string>();
|
||||
for (const entity of Object.values(states)) {
|
||||
if (!entity.entity_id.startsWith("update.")) continue;
|
||||
const platform = entities[entity.entity_id]?.platform;
|
||||
if (platform) {
|
||||
domains.add(platform);
|
||||
}
|
||||
}
|
||||
return domains;
|
||||
}
|
||||
);
|
||||
|
||||
private async _loadIntegrationTitles() {
|
||||
const domains = this._collectUpdateDomains(
|
||||
this.hass.states,
|
||||
this.hass.entities
|
||||
);
|
||||
const toLoad: string[] = [];
|
||||
for (const domain of domains) {
|
||||
if (!this._loadedIntegrationTitles.has(domain)) {
|
||||
toLoad.push(domain);
|
||||
}
|
||||
}
|
||||
if (!toLoad.length) return;
|
||||
toLoad.forEach((d) => this._loadedIntegrationTitles.add(d));
|
||||
await this.hass.loadBackendTranslation("title", toLoad);
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const installableUpdates = this._filterInstallableUpdateEntities(
|
||||
this.hass.states
|
||||
const canInstallUpdates = this._filterInstallableUpdateEntities(
|
||||
this.hass.states,
|
||||
this._showSkipped
|
||||
);
|
||||
const notInstallableUpdates = this._filterNotInstallableUpdateEntities(
|
||||
this.hass.states,
|
||||
this._showSkipped
|
||||
);
|
||||
const skippedUpdates = this._filterSkippedUpdateEntities(
|
||||
this.hass.states,
|
||||
this._showSkipped
|
||||
);
|
||||
|
||||
const groups = this._groupUpdates(
|
||||
installableUpdates,
|
||||
this._entitySources,
|
||||
this.hass.localize,
|
||||
this.hass.entities,
|
||||
this.hass.locale.language
|
||||
);
|
||||
|
||||
return html`
|
||||
<hass-subpage
|
||||
@@ -204,57 +118,19 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
</ha-dropdown>
|
||||
</div>
|
||||
<div class="content">
|
||||
${groups.map(
|
||||
(group) => html`
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
<div class="card-header">
|
||||
<div class="title" role="heading" aria-level="2">
|
||||
${group.title}
|
||||
</div>
|
||||
${group.showUpdateAll
|
||||
? html`
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
.group=${group}
|
||||
@click=${this._updateAll}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.updates.update_all"
|
||||
)}
|
||||
</ha-button>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
<ha-config-updates
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.updateEntities=${group.entities}
|
||||
showAll
|
||||
></ha-config-updates>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
)}
|
||||
${skippedUpdates.length
|
||||
${canInstallUpdates.length
|
||||
? html`
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
<div class="card-header">
|
||||
<div class="title" role="heading" aria-level="2">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.updates.title_skipped",
|
||||
{
|
||||
count: skippedUpdates.length,
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
<div class="title" role="heading" aria-level="2">
|
||||
${this.hass.localize("ui.panel.config.updates.title", {
|
||||
count: canInstallUpdates.length,
|
||||
})}
|
||||
</div>
|
||||
<ha-config-updates
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.updateEntities=${skippedUpdates}
|
||||
.updateEntities=${canInstallUpdates}
|
||||
showAll
|
||||
></ha-config-updates>
|
||||
</div>
|
||||
@@ -265,15 +141,13 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
? html`
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
<div class="card-header">
|
||||
<div class="title" role="heading" aria-level="2">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.updates.title_not_installable",
|
||||
{
|
||||
count: notInstallableUpdates.length,
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
<div class="title" role="heading" aria-level="2">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.updates.title_not_installable",
|
||||
{
|
||||
count: notInstallableUpdates.length,
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
<ha-config-updates
|
||||
.hass=${this.hass}
|
||||
@@ -285,7 +159,7 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
</ha-card>
|
||||
`
|
||||
: nothing}
|
||||
${groups.length + notInstallableUpdates.length + skippedUpdates.length
|
||||
${canInstallUpdates.length + notInstallableUpdates.length
|
||||
? nothing
|
||||
: html`
|
||||
<ha-card outlined>
|
||||
@@ -337,25 +211,9 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
checkForEntityUpdates(this, this.hass);
|
||||
}
|
||||
|
||||
private async _updateAll(ev: Event) {
|
||||
const group = (ev.currentTarget as any).group as UpdateGroup;
|
||||
try {
|
||||
await installUpdates(
|
||||
this.hass,
|
||||
group.entities.map((entity) => entity.entity_id)
|
||||
);
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize("ui.panel.config.updates.update_all_failed"),
|
||||
text: extractApiErrorMessage(err),
|
||||
warning: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _filterInstallableUpdateEntities = memoizeOne(
|
||||
(entities: HassEntities) =>
|
||||
filterUpdateEntitiesParameterized(entities, false, false)
|
||||
(entities: HassEntities, showSkipped: boolean) =>
|
||||
filterUpdateEntitiesParameterized(entities, showSkipped, false)
|
||||
);
|
||||
|
||||
private _filterNotInstallableUpdateEntities = memoizeOne(
|
||||
@@ -363,111 +221,6 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
filterUpdateEntitiesParameterized(entities, showSkipped, true)
|
||||
);
|
||||
|
||||
private _filterSkippedUpdateEntities = memoizeOne(
|
||||
(entities: HassEntities, showSkipped: boolean): UpdateEntity[] => {
|
||||
if (!showSkipped) {
|
||||
return [];
|
||||
}
|
||||
return filterUpdateEntities(entities).filter(
|
||||
(entity) =>
|
||||
latestVersionIsSkipped(entity) &&
|
||||
supportsFeature(entity, UpdateEntityFeature.INSTALL)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
private _groupUpdates = memoizeOne(
|
||||
(
|
||||
entities: UpdateEntity[],
|
||||
entitySources: EntitySources | undefined,
|
||||
localize: HomeAssistant["localize"],
|
||||
entityRegistry: HomeAssistant["entities"],
|
||||
language: string
|
||||
): UpdateGroup[] => {
|
||||
if (!entities.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const systemEntities: UpdateEntity[] = [];
|
||||
const appEntities: UpdateEntity[] = [];
|
||||
const byDomain = new Map<string, UpdateEntity[]>();
|
||||
const otherIntegrationEntities: UpdateEntity[] = [];
|
||||
|
||||
for (const entity of entities) {
|
||||
if (isSystemUpdate(entity)) {
|
||||
systemEntities.push(entity);
|
||||
continue;
|
||||
}
|
||||
const domain =
|
||||
entitySources?.[entity.entity_id]?.domain ??
|
||||
entityRegistry[entity.entity_id]?.platform;
|
||||
if (domain === "hassio") {
|
||||
appEntities.push(entity);
|
||||
continue;
|
||||
}
|
||||
if (!domain) {
|
||||
otherIntegrationEntities.push(entity);
|
||||
continue;
|
||||
}
|
||||
if (!byDomain.has(domain)) {
|
||||
byDomain.set(domain, []);
|
||||
}
|
||||
byDomain.get(domain)!.push(entity);
|
||||
}
|
||||
|
||||
const multiInstanceGroups: UpdateGroup[] = [];
|
||||
byDomain.forEach((entries, domain) => {
|
||||
if (entries.length >= 2) {
|
||||
multiInstanceGroups.push({
|
||||
key: domain,
|
||||
title: domainToName(localize, domain),
|
||||
entities: entries,
|
||||
showUpdateAll: true,
|
||||
});
|
||||
} else {
|
||||
otherIntegrationEntities.push(...entries);
|
||||
}
|
||||
});
|
||||
|
||||
multiInstanceGroups.sort((a, b) =>
|
||||
caseInsensitiveStringCompare(a.title, b.title, language)
|
||||
);
|
||||
|
||||
const groups: UpdateGroup[] = [];
|
||||
|
||||
if (systemEntities.length) {
|
||||
groups.push({
|
||||
key: SYSTEM_KEY,
|
||||
title: localize("ui.panel.config.updates.group_system"),
|
||||
entities: systemEntities,
|
||||
showUpdateAll: false,
|
||||
});
|
||||
}
|
||||
|
||||
groups.push(...multiInstanceGroups);
|
||||
|
||||
if (otherIntegrationEntities.length) {
|
||||
groups.push({
|
||||
key: INTEGRATIONS_KEY,
|
||||
title: localize("ui.panel.config.updates.group_integrations"),
|
||||
entities: otherIntegrationEntities,
|
||||
showUpdateAll: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (appEntities.length) {
|
||||
groups.push({
|
||||
key: APPS_KEY,
|
||||
title: localize("ui.panel.config.updates.group_apps"),
|
||||
entities: appEntities,
|
||||
showUpdateAll: true,
|
||||
});
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
);
|
||||
|
||||
static styles = css`
|
||||
.content {
|
||||
padding: 28px 20px 0;
|
||||
@@ -494,15 +247,8 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--ha-space-2);
|
||||
padding: var(--ha-space-4) var(--ha-space-2) 0 var(--ha-space-4);
|
||||
}
|
||||
|
||||
.title {
|
||||
padding: var(--ha-space-4) var(--ha-space-4) 0;
|
||||
font-size: var(--ha-font-size-l);
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,9 @@ class HaConfigUpdates extends SubscribeMixin(LitElement) {
|
||||
|
||||
return html`
|
||||
<ha-list-item-button
|
||||
class=${ifDefined(
|
||||
entity.attributes.skipped_version ? "skipped" : undefined
|
||||
)}
|
||||
.entity_id=${entity.entity_id}
|
||||
.hasMeta=${!this.narrow}
|
||||
@click=${this._openMoreInfo}
|
||||
@@ -163,6 +166,9 @@ class HaConfigUpdates extends SubscribeMixin(LitElement) {
|
||||
static get styles(): CSSResultGroup[] {
|
||||
return [
|
||||
css`
|
||||
.skipped {
|
||||
background: var(--secondary-background-color);
|
||||
}
|
||||
ha-list-item-button {
|
||||
--md-list-item-leading-icon-size: 40px;
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
if (changedProps.has("deviceId")) {
|
||||
this._findRelated();
|
||||
// Broadcast device context for quick bar
|
||||
fireEvent(this, "hass-related-context", {
|
||||
fireEvent(this, "hass-quick-bar-context", {
|
||||
itemType: "device",
|
||||
itemId: this.deviceId,
|
||||
});
|
||||
|
||||
+1
-3
@@ -5,7 +5,7 @@ import { dynamicElement } from "../../../../../common/dom/dynamic-element-direct
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../../../common/entity/compute_domain";
|
||||
import { computeDeviceName } from "../../../../../common/entity/compute_device_name";
|
||||
import { navigate, setRefreshUrl } from "../../../../../common/navigate";
|
||||
import { navigate } from "../../../../../common/navigate";
|
||||
import "../../../../../components/ha-dialog-footer";
|
||||
import "../../../../../components/ha-icon-button-arrow-prev";
|
||||
import "../../../../../components/ha-button";
|
||||
@@ -100,8 +100,6 @@ class DialogMatterAddDevice extends LitElement {
|
||||
public showDialog(): void {
|
||||
this._open = true;
|
||||
this._unsub = watchForNewMatterDevice(this.hass, (device) => {
|
||||
// make sure a refresh of the page will navigate to the device page, old iOS apps will refresh the webview when commissioning is done
|
||||
setRefreshUrl(`/config/devices/device/${device.id}`);
|
||||
this._newDevice = device;
|
||||
this._step = "device_added";
|
||||
this._fetchMainEntity();
|
||||
|
||||
@@ -86,6 +86,7 @@ class HuiCounterActionsCardFeature
|
||||
static getStubConfig(): CounterActionsCardFeatureConfig {
|
||||
return {
|
||||
type: "counter-actions",
|
||||
actions: COUNTER_ACTIONS.map((action) => action),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -110,9 +110,20 @@ class HuiLawnMowerCommandCardFeature
|
||||
| undefined;
|
||||
}
|
||||
|
||||
static getStubConfig(): LawnMowerCommandsCardFeatureConfig {
|
||||
static getStubConfig(
|
||||
hass: HomeAssistant,
|
||||
context: LovelaceCardFeatureContext
|
||||
): LawnMowerCommandsCardFeatureConfig {
|
||||
const stateObj = context.entity_id
|
||||
? hass.states[context.entity_id]
|
||||
: undefined;
|
||||
return {
|
||||
type: "lawn-mower-commands",
|
||||
commands: stateObj
|
||||
? LAWN_MOWER_COMMANDS.filter((c) =>
|
||||
supportsLawnMowerCommand(stateObj, c)
|
||||
).slice(0, 3)
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -151,28 +162,28 @@ class HuiLawnMowerCommandCardFeature
|
||||
|
||||
const stateObj = this._stateObj as LawnMowerEntity;
|
||||
|
||||
const commands = this._config.commands ?? LAWN_MOWER_COMMANDS;
|
||||
|
||||
return html`
|
||||
<ha-control-button-group>
|
||||
${commands
|
||||
.filter((command) => supportsLawnMowerCommand(stateObj, command))
|
||||
.map((command) => {
|
||||
const button = LAWN_MOWER_COMMANDS_BUTTONS[command](stateObj);
|
||||
return html`
|
||||
<ha-control-button
|
||||
.entry=${button}
|
||||
.label=${this.hass!.localize(
|
||||
// @ts-ignore
|
||||
`ui.dialogs.more_info_control.lawn_mower.${button.translationKey}`
|
||||
)}
|
||||
@click=${this._onCommandTap}
|
||||
.disabled=${button.disabled || stateObj.state === UNAVAILABLE}
|
||||
>
|
||||
<ha-svg-icon .path=${button.icon}></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
`;
|
||||
})}
|
||||
${LAWN_MOWER_COMMANDS.filter(
|
||||
(command) =>
|
||||
supportsLawnMowerCommand(stateObj, command) &&
|
||||
this._config?.commands?.includes(command)
|
||||
).map((command) => {
|
||||
const button = LAWN_MOWER_COMMANDS_BUTTONS[command](stateObj);
|
||||
return html`
|
||||
<ha-control-button
|
||||
.entry=${button}
|
||||
.label=${this.hass!.localize(
|
||||
// @ts-ignore
|
||||
`ui.dialogs.more_info_control.lawn_mower.${button.translationKey}`
|
||||
)}
|
||||
@click=${this._onCommandTap}
|
||||
.disabled=${button.disabled || stateObj.state === UNAVAILABLE}
|
||||
>
|
||||
<ha-svg-icon .path=${button.icon}></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
`;
|
||||
})}
|
||||
</ha-control-button-group>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -52,12 +52,6 @@ export const VACUUM_COMMANDS_FEATURES: Record<
|
||||
return_home: [VacuumEntityFeature.RETURN_HOME],
|
||||
};
|
||||
|
||||
export const VACUUM_DEFAULT_COMMANDS: VacuumCommand[] = [
|
||||
"start_pause",
|
||||
"stop",
|
||||
"return_home",
|
||||
];
|
||||
|
||||
export const supportsVacuumCommand = (
|
||||
stateObj: HassEntity,
|
||||
command: VacuumCommand
|
||||
@@ -160,9 +154,20 @@ class HuiVacuumCommandCardFeature
|
||||
| undefined;
|
||||
}
|
||||
|
||||
static getStubConfig(): VacuumCommandsCardFeatureConfig {
|
||||
static getStubConfig(
|
||||
hass: HomeAssistant,
|
||||
context: LovelaceCardFeatureContext
|
||||
): VacuumCommandsCardFeatureConfig {
|
||||
const stateObj = context.entity_id
|
||||
? hass.states[context.entity_id]
|
||||
: undefined;
|
||||
return {
|
||||
type: "vacuum-commands",
|
||||
commands: stateObj
|
||||
? VACUUM_COMMANDS.filter((c) =>
|
||||
supportsVacuumCommand(stateObj, c)
|
||||
).slice(0, 3)
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -199,28 +204,28 @@ class HuiVacuumCommandCardFeature
|
||||
|
||||
const stateObj = this._stateObj as VacuumEntity;
|
||||
|
||||
const commands = this._config.commands ?? VACUUM_DEFAULT_COMMANDS;
|
||||
|
||||
return html`
|
||||
<ha-control-button-group>
|
||||
${commands
|
||||
.filter((command) => supportsVacuumCommand(stateObj, command))
|
||||
.map((command) => {
|
||||
const button = VACUUM_COMMANDS_BUTTONS[command](stateObj);
|
||||
return html`
|
||||
<ha-control-button
|
||||
.entry=${button}
|
||||
.label=${this.hass!.localize(
|
||||
// @ts-ignore
|
||||
`ui.dialogs.more_info_control.vacuum.${button.translationKey}`
|
||||
)}
|
||||
@click=${this._onCommandTap}
|
||||
.disabled=${button.disabled || stateObj.state === UNAVAILABLE}
|
||||
>
|
||||
<ha-svg-icon .path=${button.icon}></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
`;
|
||||
})}
|
||||
${VACUUM_COMMANDS.filter(
|
||||
(command) =>
|
||||
supportsVacuumCommand(stateObj, command) &&
|
||||
this._config?.commands?.includes(command)
|
||||
).map((command) => {
|
||||
const button = VACUUM_COMMANDS_BUTTONS[command](stateObj);
|
||||
return html`
|
||||
<ha-control-button
|
||||
.entry=${button}
|
||||
.label=${this.hass!.localize(
|
||||
// @ts-ignore
|
||||
`ui.dialogs.more_info_control.vacuum.${button.translationKey}`
|
||||
)}
|
||||
@click=${this._onCommandTap}
|
||||
.disabled=${button.disabled || stateObj.state === UNAVAILABLE}
|
||||
>
|
||||
<ha-svg-icon .path=${button.icon}></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
`;
|
||||
})}
|
||||
</ha-control-button-group>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
} from "@mdi/js";
|
||||
import type { FuseIndex } from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
import { getAreasFloorHierarchy } from "../../../../common/areas/areas-floor-hierarchy";
|
||||
import { computeAreaName } from "../../../../common/entity/compute_area_name";
|
||||
import { computeDeviceName } from "../../../../common/entity/compute_device_name";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
@@ -14,6 +13,7 @@ import { computeEntityName } from "../../../../common/entity/compute_entity_name
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import { stringCompare } from "../../../../common/string/compare";
|
||||
import { entityComboBoxKeys } from "../../../../data/entity/entity_picker";
|
||||
import { getFloorAreaLookup } from "../../../../data/floor_registry";
|
||||
import { domainToName } from "../../../../data/integration";
|
||||
import { multiTermSortedSearch } from "../../../../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
@@ -206,24 +206,23 @@ export function buildEntityTree(input: BuildEntityTreeInput): EntityTree {
|
||||
return stringCompare(an, bn, language);
|
||||
};
|
||||
|
||||
const buildDeviceNodes = (source: Map<string, string[]>): DeviceNode[] =>
|
||||
[...source.entries()]
|
||||
.map(([id, ids]) => {
|
||||
const device = deviceReg[id];
|
||||
return {
|
||||
id,
|
||||
name: (device ? computeDeviceName(device) : undefined) ?? id,
|
||||
entityIds: ids.sort(sortByName),
|
||||
};
|
||||
})
|
||||
.sort((a, b) => stringCompare(a.name, b.name, language));
|
||||
|
||||
const buildAreaNode = (areaId: string): AreaNode | undefined => {
|
||||
const area = areaReg[areaId];
|
||||
if (!area) return undefined;
|
||||
const directIds = (areaDirectEntities.get(areaId) ?? []).sort(sortByName);
|
||||
const byDevice = areaDeviceEntities.get(areaId);
|
||||
const devices = byDevice ? buildDeviceNodes(byDevice) : [];
|
||||
const devices: DeviceNode[] = byDevice
|
||||
? [...byDevice.entries()]
|
||||
.map(([id, ids]) => {
|
||||
const device = deviceReg[id];
|
||||
return {
|
||||
id,
|
||||
name: (device ? computeDeviceName(device) : undefined) ?? id,
|
||||
entityIds: ids.sort(sortByName),
|
||||
};
|
||||
})
|
||||
.sort((a, b) => stringCompare(a.name, b.name, language))
|
||||
: [];
|
||||
if (!directIds.length && !devices.length) return undefined;
|
||||
return {
|
||||
id: area.area_id,
|
||||
@@ -236,14 +235,14 @@ export function buildEntityTree(input: BuildEntityTreeInput): EntityTree {
|
||||
|
||||
const areas = Object.values(areaReg);
|
||||
const floors = Object.values(floorReg);
|
||||
const hierarchy = getAreasFloorHierarchy(floors, areas);
|
||||
const floorAreaLookup = getFloorAreaLookup(areas);
|
||||
|
||||
const floorNodes: FloorNode[] = hierarchy.floors
|
||||
.map(({ id, areas: areaIds }) => {
|
||||
const floor = floorReg[id];
|
||||
const areaList = areaIds
|
||||
.map((areaId) => buildAreaNode(areaId))
|
||||
.filter((a): a is AreaNode => !!a);
|
||||
const floorNodes: FloorNode[] = floors
|
||||
.map((floor) => {
|
||||
const areaList = (floorAreaLookup[floor.floor_id] ?? [])
|
||||
.map((a) => buildAreaNode(a.area_id))
|
||||
.filter((a): a is AreaNode => !!a)
|
||||
.sort((a, b) => stringCompare(a.name, b.name, language));
|
||||
if (!areaList.length) return undefined;
|
||||
return {
|
||||
id: floor.floor_id,
|
||||
@@ -253,11 +252,26 @@ export function buildEntityTree(input: BuildEntityTreeInput): EntityTree {
|
||||
areas: areaList,
|
||||
};
|
||||
})
|
||||
.filter((f): f is FloorNode => !!f);
|
||||
.filter((f): f is FloorNode => !!f)
|
||||
.sort((a, b) => stringCompare(a.name, b.name, language));
|
||||
|
||||
const otherAreas = hierarchy.areas
|
||||
.map((areaId) => buildAreaNode(areaId))
|
||||
.filter((a): a is AreaNode => !!a);
|
||||
const otherAreas = areas
|
||||
.filter((a) => !a.floor_id || !floorReg[a.floor_id])
|
||||
.map((a) => buildAreaNode(a.area_id))
|
||||
.filter((a): a is AreaNode => !!a)
|
||||
.sort((a, b) => stringCompare(a.name, b.name, language));
|
||||
|
||||
const buildDeviceNodes = (source: Map<string, string[]>): DeviceNode[] =>
|
||||
[...source.entries()]
|
||||
.map(([id, ids]) => {
|
||||
const device = deviceReg[id];
|
||||
return {
|
||||
id,
|
||||
name: (device ? computeDeviceName(device) : undefined) ?? id,
|
||||
entityIds: ids.sort(sortByName),
|
||||
};
|
||||
})
|
||||
.sort((a, b) => stringCompare(a.name, b.name, language));
|
||||
|
||||
const buildDomainGroups = (source: Map<string, string[]>): DomainGroup[] =>
|
||||
[...source.entries()]
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import type { HaFormSchema } from "../../../../components/ha-form/types";
|
||||
|
||||
interface CustomizableListSchemaParams {
|
||||
field: string;
|
||||
customize: boolean;
|
||||
options: { value: string; label: string }[];
|
||||
}
|
||||
|
||||
export const customizableListSchema = ({
|
||||
field,
|
||||
customize,
|
||||
options,
|
||||
}: CustomizableListSchemaParams) =>
|
||||
[
|
||||
{
|
||||
name: "customize",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
...(customize
|
||||
? ([
|
||||
{
|
||||
name: field,
|
||||
selector: {
|
||||
select: {
|
||||
mode: "list",
|
||||
reorder: true,
|
||||
multiple: true,
|
||||
options,
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const satisfies readonly HaFormSchema[])
|
||||
: []),
|
||||
] as const satisfies readonly HaFormSchema[];
|
||||
|
||||
// `customize` is form-only and never stored in the config.
|
||||
export const customizableListData = <T extends object>(
|
||||
config: T,
|
||||
field: string
|
||||
): T & { customize: boolean } => ({
|
||||
...config,
|
||||
customize: (config as Record<string, unknown>)[field] !== undefined,
|
||||
});
|
||||
|
||||
// Dropping the field lets the feature fall back to its own default.
|
||||
export const processCustomizableListValue = <T extends object>(
|
||||
value: T & { customize?: boolean },
|
||||
field: string,
|
||||
defaults: readonly string[]
|
||||
): T => {
|
||||
const { customize, ...rest } = value;
|
||||
const config = rest as Record<string, unknown>;
|
||||
if (customize && !config[field]) {
|
||||
config[field] = [...defaults];
|
||||
} else if (!customize) {
|
||||
delete config[field];
|
||||
}
|
||||
return config as unknown as T;
|
||||
};
|
||||
+37
-35
@@ -2,20 +2,16 @@ import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type {
|
||||
CounterActionsCardFeatureConfig,
|
||||
LovelaceCardFeatureContext,
|
||||
} from "../../card-features/types";
|
||||
import { COUNTER_ACTIONS } from "../../card-features/types";
|
||||
import type { LovelaceCardFeatureEditor } from "../../types";
|
||||
import {
|
||||
customizableListData,
|
||||
customizableListSchema,
|
||||
processCustomizableListValue,
|
||||
} from "./customizable-list-feature";
|
||||
COUNTER_ACTIONS,
|
||||
type LovelaceCardFeatureContext,
|
||||
type CounterActionsCardFeatureConfig,
|
||||
} from "../../card-features/types";
|
||||
import type { LovelaceCardFeatureEditor } from "../../types";
|
||||
|
||||
@customElement("hui-counter-actions-card-feature-editor")
|
||||
export class HuiCounterActionsCardFeatureEditor
|
||||
@@ -32,17 +28,26 @@ export class HuiCounterActionsCardFeatureEditor
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
private _schema = memoizeOne((customize: boolean) =>
|
||||
customizableListSchema({
|
||||
field: "actions",
|
||||
customize,
|
||||
options: COUNTER_ACTIONS.map((action) => ({
|
||||
value: action,
|
||||
label: this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.features.types.counter-actions.actions_list.${action}`
|
||||
),
|
||||
})),
|
||||
})
|
||||
private _schema = memoizeOne(
|
||||
(localize: LocalizeFunc) =>
|
||||
[
|
||||
{
|
||||
name: "actions",
|
||||
selector: {
|
||||
select: {
|
||||
multiple: true,
|
||||
mode: "list",
|
||||
reorder: true,
|
||||
options: COUNTER_ACTIONS.map((action) => ({
|
||||
value: action,
|
||||
label: `${localize(
|
||||
`ui.panel.lovelace.editor.features.types.counter-actions.actions.${action}`
|
||||
)}`,
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const
|
||||
);
|
||||
|
||||
protected render() {
|
||||
@@ -50,13 +55,12 @@ export class HuiCounterActionsCardFeatureEditor
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const data = customizableListData(this._config, "actions");
|
||||
const schema = this._schema(data.customize);
|
||||
const schema = this._schema(this.hass.localize);
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.data=${this._config}
|
||||
.schema=${schema}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
@@ -65,21 +69,19 @@ export class HuiCounterActionsCardFeatureEditor
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
const config =
|
||||
processCustomizableListValue<CounterActionsCardFeatureConfig>(
|
||||
ev.detail.value,
|
||||
"actions",
|
||||
COUNTER_ACTIONS
|
||||
);
|
||||
fireEvent(this, "config-changed", { config });
|
||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) =>
|
||||
this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.features.types.counter-actions.${schema.name}`
|
||||
);
|
||||
) => {
|
||||
switch (schema.name) {
|
||||
default:
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.generic.${schema.name}`
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
+38
-39
@@ -3,6 +3,7 @@ import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
@@ -13,11 +14,6 @@ import type {
|
||||
} from "../../card-features/types";
|
||||
import { LAWN_MOWER_COMMANDS } from "../../card-features/types";
|
||||
import type { LovelaceCardFeatureEditor } from "../../types";
|
||||
import {
|
||||
customizableListData,
|
||||
customizableListSchema,
|
||||
processCustomizableListValue,
|
||||
} from "./customizable-list-feature";
|
||||
|
||||
@customElement("hui-lawn-mower-commands-card-feature-editor")
|
||||
export class HuiLawnMowerCommandsCardFeatureEditor
|
||||
@@ -35,19 +31,27 @@ export class HuiLawnMowerCommandsCardFeatureEditor
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(stateObj: HassEntity | undefined, customize: boolean) =>
|
||||
customizableListSchema({
|
||||
field: "commands",
|
||||
customize,
|
||||
options: LAWN_MOWER_COMMANDS.filter(
|
||||
(command) => stateObj && supportsLawnMowerCommand(stateObj, command)
|
||||
).map((command) => ({
|
||||
value: command,
|
||||
label: this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.features.types.lawn-mower-commands.commands_list.${command}`
|
||||
),
|
||||
})),
|
||||
})
|
||||
(localize: LocalizeFunc, stateObj?: HassEntity) =>
|
||||
[
|
||||
{
|
||||
name: "commands",
|
||||
selector: {
|
||||
select: {
|
||||
multiple: true,
|
||||
mode: "list",
|
||||
options: LAWN_MOWER_COMMANDS.filter(
|
||||
(command) =>
|
||||
stateObj && supportsLawnMowerCommand(stateObj, command)
|
||||
).map((command) => ({
|
||||
value: command,
|
||||
label: `${localize(
|
||||
`ui.panel.lovelace.editor.features.types.lawn-mower-commands.commands_list.${command}`
|
||||
)}`,
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const
|
||||
);
|
||||
|
||||
protected render() {
|
||||
@@ -56,16 +60,15 @@ export class HuiLawnMowerCommandsCardFeatureEditor
|
||||
}
|
||||
|
||||
const stateObj = this.context?.entity_id
|
||||
? this.hass.states[this.context.entity_id]
|
||||
? this.hass.states[this.context?.entity_id]
|
||||
: undefined;
|
||||
|
||||
const data = customizableListData(this._config, "commands");
|
||||
const schema = this._schema(stateObj, data.customize);
|
||||
const schema = this._schema(this.hass.localize, stateObj);
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.data=${this._config}
|
||||
.schema=${schema}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
@@ -74,27 +77,23 @@ export class HuiLawnMowerCommandsCardFeatureEditor
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
const stateObj = this.context?.entity_id
|
||||
? this.hass!.states[this.context.entity_id]
|
||||
: undefined;
|
||||
const defaults = LAWN_MOWER_COMMANDS.filter(
|
||||
(command) => stateObj && supportsLawnMowerCommand(stateObj, command)
|
||||
);
|
||||
const config =
|
||||
processCustomizableListValue<LawnMowerCommandsCardFeatureConfig>(
|
||||
ev.detail.value,
|
||||
"commands",
|
||||
defaults
|
||||
);
|
||||
fireEvent(this, "config-changed", { config });
|
||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) =>
|
||||
this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.features.types.lawn-mower-commands.${schema.name}`
|
||||
);
|
||||
) => {
|
||||
switch (schema.name) {
|
||||
case "commands":
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.features.types.lawn-mower-commands.${schema.name}`
|
||||
);
|
||||
default:
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.generic.${schema.name}`
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
+39
-43
@@ -3,24 +3,17 @@ import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
supportsVacuumCommand,
|
||||
VACUUM_DEFAULT_COMMANDS,
|
||||
} from "../../card-features/hui-vacuum-commands-card-feature";
|
||||
import { supportsVacuumCommand } from "../../card-features/hui-vacuum-commands-card-feature";
|
||||
import type {
|
||||
LovelaceCardFeatureContext,
|
||||
VacuumCommandsCardFeatureConfig,
|
||||
} from "../../card-features/types";
|
||||
import { VACUUM_COMMANDS } from "../../card-features/types";
|
||||
import type { LovelaceCardFeatureEditor } from "../../types";
|
||||
import {
|
||||
customizableListData,
|
||||
customizableListSchema,
|
||||
processCustomizableListValue,
|
||||
} from "./customizable-list-feature";
|
||||
|
||||
@customElement("hui-vacuum-commands-card-feature-editor")
|
||||
export class HuiVacuumCommandsCardFeatureEditor
|
||||
@@ -38,19 +31,27 @@ export class HuiVacuumCommandsCardFeatureEditor
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(stateObj: HassEntity | undefined, customize: boolean) =>
|
||||
customizableListSchema({
|
||||
field: "commands",
|
||||
customize,
|
||||
options: VACUUM_COMMANDS.filter(
|
||||
(command) => stateObj && supportsVacuumCommand(stateObj, command)
|
||||
).map((command) => ({
|
||||
value: command,
|
||||
label: this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.features.types.vacuum-commands.commands_list.${command}`
|
||||
),
|
||||
})),
|
||||
})
|
||||
(localize: LocalizeFunc, stateObj?: HassEntity) =>
|
||||
[
|
||||
{
|
||||
name: "commands",
|
||||
selector: {
|
||||
select: {
|
||||
multiple: true,
|
||||
mode: "list",
|
||||
options: VACUUM_COMMANDS.filter(
|
||||
(command) =>
|
||||
stateObj && supportsVacuumCommand(stateObj, command)
|
||||
).map((command) => ({
|
||||
value: command,
|
||||
label: `${localize(
|
||||
`ui.panel.lovelace.editor.features.types.vacuum-commands.commands_list.${command}`
|
||||
)}`,
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const
|
||||
);
|
||||
|
||||
protected render() {
|
||||
@@ -59,16 +60,15 @@ export class HuiVacuumCommandsCardFeatureEditor
|
||||
}
|
||||
|
||||
const stateObj = this.context?.entity_id
|
||||
? this.hass.states[this.context.entity_id]
|
||||
? this.hass.states[this.context?.entity_id]
|
||||
: undefined;
|
||||
|
||||
const data = customizableListData(this._config, "commands");
|
||||
const schema = this._schema(stateObj, data.customize);
|
||||
const schema = this._schema(this.hass.localize, stateObj);
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.data=${this._config}
|
||||
.schema=${schema}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
@@ -77,27 +77,23 @@ export class HuiVacuumCommandsCardFeatureEditor
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
const stateObj = this.context?.entity_id
|
||||
? this.hass!.states[this.context.entity_id]
|
||||
: undefined;
|
||||
const defaults = VACUUM_DEFAULT_COMMANDS.filter(
|
||||
(command) => stateObj && supportsVacuumCommand(stateObj, command)
|
||||
);
|
||||
const config =
|
||||
processCustomizableListValue<VacuumCommandsCardFeatureConfig>(
|
||||
ev.detail.value,
|
||||
"commands",
|
||||
defaults
|
||||
);
|
||||
fireEvent(this, "config-changed", { config });
|
||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) =>
|
||||
this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.features.types.vacuum-commands.${schema.name}`
|
||||
);
|
||||
) => {
|
||||
switch (schema.name) {
|
||||
case "commands":
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.features.types.vacuum-commands.${schema.name}`
|
||||
);
|
||||
default:
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.generic.${schema.name}`
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -39,7 +39,6 @@ import { subscribeLabelRegistry } from "../data/label/label_registry";
|
||||
import type { Constructor, HomeAssistant } from "../types";
|
||||
import type { HassBaseEl } from "./hass-base-mixin";
|
||||
import { LazyContextProvider } from "./lazy-context-provider";
|
||||
import { RelatedContextProvider } from "./related-context-provider";
|
||||
|
||||
export const contextMixin = <T extends Constructor<HassBaseEl>>(
|
||||
superClass: T
|
||||
@@ -202,8 +201,6 @@ export const contextMixin = <T extends Constructor<HassBaseEl>>(
|
||||
}),
|
||||
};
|
||||
|
||||
private __relatedContextProvider = new RelatedContextProvider(this);
|
||||
|
||||
protected hassConnected() {
|
||||
super.hassConnected();
|
||||
for (const [key, value] of Object.entries(this.hass!)) {
|
||||
@@ -217,8 +214,6 @@ export const contextMixin = <T extends Constructor<HassBaseEl>>(
|
||||
for (const provider of Object.values(this.__lazyContextProviders)) {
|
||||
provider.setConnection(connection);
|
||||
}
|
||||
|
||||
this.__relatedContextProvider.connect();
|
||||
}
|
||||
|
||||
protected _updateHass(obj: Partial<HomeAssistant>) {
|
||||
@@ -249,6 +244,5 @@ export const contextMixin = <T extends Constructor<HassBaseEl>>(
|
||||
for (const provider of Object.values(this.__lazyContextProviders)) {
|
||||
provider.unsubscribe();
|
||||
}
|
||||
this.__relatedContextProvider.disconnect();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,7 +5,12 @@ import { canOverrideAlphanumericInput } from "../common/dom/can-override-input";
|
||||
import { mainWindow } from "../common/dom/get_main_window";
|
||||
import { ShortcutManager } from "../common/keyboard/shortcuts";
|
||||
import { extractSearchParamsObject } from "../common/url/search-params";
|
||||
import type { QuickBarSection } from "../dialogs/quick-bar/show-dialog-quick-bar";
|
||||
import { findRelated, type RelatedResult } from "../data/search";
|
||||
import type {
|
||||
QuickBarContextItem,
|
||||
QuickBarParams,
|
||||
QuickBarSection,
|
||||
} from "../dialogs/quick-bar/show-dialog-quick-bar";
|
||||
import {
|
||||
closeQuickBar,
|
||||
showQuickBar,
|
||||
@@ -19,8 +24,10 @@ import type { HassElement } from "./hass-element";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"hass-quick-bar": QuickBarParams;
|
||||
"hass-quick-bar-trigger": KeyboardEvent;
|
||||
"hass-enable-shortcuts": HomeAssistant["enableShortcuts"];
|
||||
"hass-quick-bar-context": QuickBarContextItem | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +35,52 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
|
||||
class extends superClass {
|
||||
private _quickBarOpen = false;
|
||||
|
||||
private _quickBarContext?: QuickBarContextItem;
|
||||
|
||||
private _quickBarContextRelated?: RelatedResult;
|
||||
|
||||
private _fetchRelatedMemoized = memoizeOne(
|
||||
(itemType: QuickBarContextItem["itemType"], itemId: string) =>
|
||||
findRelated(this.hass!, itemType, itemId)
|
||||
);
|
||||
|
||||
private _clearQuickBarContext = () => {
|
||||
this._quickBarContext = undefined;
|
||||
this._quickBarContextRelated = undefined;
|
||||
};
|
||||
|
||||
private _contextMatches = (context?: QuickBarContextItem) =>
|
||||
context?.itemType === this._quickBarContext?.itemType &&
|
||||
context?.itemId === this._quickBarContext?.itemId;
|
||||
|
||||
private _prefetchQuickBarContext = async (
|
||||
context?: QuickBarContextItem
|
||||
) => {
|
||||
this._quickBarContextRelated = undefined;
|
||||
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const related = await this._fetchRelatedMemoized(
|
||||
context.itemType,
|
||||
context.itemId
|
||||
);
|
||||
|
||||
if (this._contextMatches(context)) {
|
||||
this._quickBarContextRelated = related;
|
||||
}
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("Error prefetching quick bar related items", err);
|
||||
|
||||
if (this._contextMatches(context)) {
|
||||
this._quickBarContextRelated = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues<this>) {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
@@ -58,6 +111,20 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("hass-quick-bar-context", (ev) => {
|
||||
this._quickBarContext =
|
||||
ev.detail && "itemType" in ev.detail && "itemId" in ev.detail
|
||||
? ev.detail
|
||||
: undefined;
|
||||
this._prefetchQuickBarContext(this._quickBarContext);
|
||||
});
|
||||
|
||||
mainWindow.addEventListener(
|
||||
"location-changed",
|
||||
this._clearQuickBarContext
|
||||
);
|
||||
mainWindow.addEventListener("popstate", this._clearQuickBarContext);
|
||||
|
||||
mainWindow.addEventListener("hass-quick-bar-trigger", (ev) => {
|
||||
switch (ev.detail.key) {
|
||||
case "e":
|
||||
@@ -94,6 +161,15 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
|
||||
}
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
mainWindow.removeEventListener(
|
||||
"location-changed",
|
||||
this._clearQuickBarContext
|
||||
);
|
||||
mainWindow.removeEventListener("popstate", this._clearQuickBarContext);
|
||||
}
|
||||
|
||||
private _registerShortcut() {
|
||||
const shortcutManager = new ShortcutManager();
|
||||
shortcutManager.add({
|
||||
@@ -162,7 +238,11 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
|
||||
}
|
||||
e.preventDefault();
|
||||
|
||||
showQuickBar(this, { mode });
|
||||
showQuickBar(this, {
|
||||
mode,
|
||||
contextItem: this._quickBarContext,
|
||||
related: this._quickBarContextRelated,
|
||||
});
|
||||
}
|
||||
|
||||
private _toggleQuickBar(e: KeyboardEvent, mode?: QuickBarSection) {
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
import { ContextProvider } from "@lit/context";
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { HASSDomEvent } from "../common/dom/fire_event";
|
||||
import { mainWindow } from "../common/dom/get_main_window";
|
||||
import { buildRelatedIdSets } from "../common/search/related-context";
|
||||
import { relatedContext, type RelatedContextItem } from "../data/context";
|
||||
import { findRelated } from "../data/search";
|
||||
import type { HassBaseEl } from "./hass-base-mixin";
|
||||
|
||||
/**
|
||||
* Standalone context provider for `relatedContext`.
|
||||
*
|
||||
* Listens for `hass-related-context` events fired by child components,
|
||||
* resolves the related entities/devices/areas via `findRelated`, and
|
||||
* provides the resolved `RelatedIdSets` to context consumers.
|
||||
*
|
||||
* Clears on actual page navigation (pathname change), not on dialog
|
||||
* history manipulation (`popstate` from dialog close).
|
||||
*
|
||||
* Instantiated from `context-mixin.ts` alongside other providers.
|
||||
*/
|
||||
export class RelatedContextProvider {
|
||||
private _relatedContext?: RelatedContextItem;
|
||||
|
||||
private _provider: ContextProvider<typeof relatedContext>;
|
||||
|
||||
private _contextPathname?: string;
|
||||
|
||||
private _fetchRelatedMemoized = memoizeOne(
|
||||
(itemType: RelatedContextItem["itemType"], itemId: string) =>
|
||||
findRelated(this._host.hass!, itemType, itemId)
|
||||
);
|
||||
|
||||
constructor(private _host: HassBaseEl) {
|
||||
this._provider = new ContextProvider(_host, { context: relatedContext });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up event listeners. Call from `firstUpdated` or `hassConnected`.
|
||||
*/
|
||||
public connect(): void {
|
||||
this._host.addEventListener("hass-related-context", this._onRelatedContext);
|
||||
mainWindow.addEventListener(
|
||||
"location-changed",
|
||||
this._maybeClearRelatedContext
|
||||
);
|
||||
mainWindow.addEventListener("popstate", this._maybeClearRelatedContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up event listeners. Call from `disconnectedCallback`.
|
||||
*/
|
||||
public disconnect(): void {
|
||||
this._host.removeEventListener(
|
||||
"hass-related-context",
|
||||
this._onRelatedContext
|
||||
);
|
||||
mainWindow.removeEventListener(
|
||||
"location-changed",
|
||||
this._maybeClearRelatedContext
|
||||
);
|
||||
mainWindow.removeEventListener("popstate", this._maybeClearRelatedContext);
|
||||
}
|
||||
|
||||
private _onRelatedContext = (
|
||||
ev: HASSDomEvent<RelatedContextItem | undefined>
|
||||
): void => {
|
||||
this._relatedContext = ev.detail;
|
||||
this._contextPathname = mainWindow.location.pathname;
|
||||
this._resolveRelatedContext(this._relatedContext);
|
||||
};
|
||||
|
||||
/**
|
||||
* Only clear context when the actual page pathname changes.
|
||||
* Dialog open/close manipulates history state without changing the URL,
|
||||
* so we ignore those popstate/location-changed events.
|
||||
*/
|
||||
private _maybeClearRelatedContext = (): void => {
|
||||
if (
|
||||
this._contextPathname &&
|
||||
mainWindow.location.pathname === this._contextPathname
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this._relatedContext = undefined;
|
||||
this._contextPathname = undefined;
|
||||
this._provider.setValue(undefined);
|
||||
};
|
||||
|
||||
private _contextMatches = (context?: RelatedContextItem): boolean =>
|
||||
context?.itemType === this._relatedContext?.itemType &&
|
||||
context?.itemId === this._relatedContext?.itemId;
|
||||
|
||||
private _resolveRelatedContext = async (
|
||||
context?: RelatedContextItem
|
||||
): Promise<void> => {
|
||||
if (!context || !this._host.hass) {
|
||||
this._provider.setValue(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const related = await this._fetchRelatedMemoized(
|
||||
context.itemType,
|
||||
context.itemId
|
||||
);
|
||||
if (this._contextMatches(context)) {
|
||||
this._provider.setValue(buildRelatedIdSets(related));
|
||||
}
|
||||
} catch (_err) {
|
||||
if (this._contextMatches(context)) {
|
||||
this._provider.setValue(undefined);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable no-console */
|
||||
import type { ReactiveElement, PropertyValues } from "lit";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { mainWindow } from "../common/dom/get_main_window";
|
||||
import { closeLastDialog } from "../dialogs/make-dialog-manager";
|
||||
import type { ProvideHassElement } from "../mixins/provide-hass-lit-mixin";
|
||||
@@ -39,21 +38,6 @@ export const urlSyncMixin = <
|
||||
protected firstUpdated(changedProperties: PropertyValues<this>): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
if (mainWindow.history.state?.dialog) {
|
||||
const refreshUrl = mainWindow.history.state.refreshUrl;
|
||||
if (typeof refreshUrl === "string") {
|
||||
// Page was refreshed while a dialog had stashed an intended
|
||||
// destination URL. Clean up the stale dialog state and route
|
||||
// to the intended URL. We bypass navigate() because its
|
||||
// ensureDialogsClosed loop would spin until timeout on the
|
||||
// dangling state.dialog with no actual dialog open.
|
||||
mainWindow.history.replaceState(null, "", refreshUrl);
|
||||
// Defer: the host element's firstUpdated registers the
|
||||
// location-changed listener after super.firstUpdated() returns.
|
||||
setTimeout(() => {
|
||||
fireEvent(mainWindow, "location-changed", { replace: true });
|
||||
});
|
||||
return;
|
||||
}
|
||||
// this is a page refresh with a dialog open
|
||||
// the dialog stack must be empty in this case so this state should be cleaned up
|
||||
mainWindow.history.back();
|
||||
|
||||
@@ -2750,11 +2750,6 @@
|
||||
"updates": {
|
||||
"caption": "Updates",
|
||||
"description": "Manage updates of Home Assistant, apps, and devices",
|
||||
"group_system": "Home Assistant",
|
||||
"group_integrations": "Integrations",
|
||||
"group_apps": "Apps",
|
||||
"update_all": "Update all",
|
||||
"update_all_failed": "Failed to start updates",
|
||||
"no_updates": "No updates available",
|
||||
"no_update_entities": {
|
||||
"title": "Unable to check for updates",
|
||||
@@ -2766,7 +2761,6 @@
|
||||
"checking_updates": "Checking for updates...",
|
||||
"title": "{count} {count, plural,\n one {update}\n other {updates}\n}",
|
||||
"title_not_installable": "{count} not installable {count, plural,\n one {update}\n other {updates}\n}",
|
||||
"title_skipped": "{count} skipped {count, plural,\n one {update}\n other {updates}\n}",
|
||||
"unable_to_fetch": "Unable to load updates",
|
||||
"more_updates": "Show all updates",
|
||||
"show": "show",
|
||||
@@ -10157,7 +10151,6 @@
|
||||
},
|
||||
"vacuum-commands": {
|
||||
"label": "Vacuum commands",
|
||||
"customize": "Customize commands",
|
||||
"commands": "Commands",
|
||||
"commands_list": {
|
||||
"start_pause": "[%key:ui::dialogs::more_info_control::vacuum::start_pause%]",
|
||||
@@ -10229,9 +10222,7 @@
|
||||
},
|
||||
"counter-actions": {
|
||||
"label": "Counter actions",
|
||||
"customize": "Customize actions",
|
||||
"actions": "Actions",
|
||||
"actions_list": {
|
||||
"actions": {
|
||||
"increment": "Increment",
|
||||
"decrement": "Decrement",
|
||||
"reset": "Reset"
|
||||
@@ -10294,7 +10285,6 @@
|
||||
},
|
||||
"lawn-mower-commands": {
|
||||
"label": "Lawn mower commands",
|
||||
"customize": "Customize commands",
|
||||
"commands": "Commands",
|
||||
"commands_list": {
|
||||
"start_pause": "Start Pause",
|
||||
|
||||
@@ -1598,13 +1598,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint/plugin-kit@npm:^0.7.2":
|
||||
version: 0.7.2
|
||||
resolution: "@eslint/plugin-kit@npm:0.7.2"
|
||||
"@eslint/plugin-kit@npm:^0.7.1":
|
||||
version: 0.7.1
|
||||
resolution: "@eslint/plugin-kit@npm:0.7.1"
|
||||
dependencies:
|
||||
"@eslint/core": "npm:^1.2.1"
|
||||
levn: "npm:^0.4.1"
|
||||
checksum: 10/ef9fc6f8ca28e132d4c81cfbaa92274800d1d73bb9d6ef2124613dd39b7f09e3592deb64bad10b183bff78db5465d4b100f522d994c8550424526b9ac4a072b0
|
||||
checksum: 10/8f923f4cdadadd215e0c2028e6a53101bb148a7780cdb4dc8cd69b0c77fc88496742e87e0605b12905ff715e2c7ad6cbd2d92c5653cdbf91cca1e229b5022c1f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3733,9 +3733,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/dev-server@npm:2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "@rspack/dev-server@npm:2.0.3"
|
||||
"@rspack/dev-server@npm:2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "@rspack/dev-server@npm:2.0.1"
|
||||
dependencies:
|
||||
"@rspack/dev-middleware": "npm:^2.0.1"
|
||||
peerDependencies:
|
||||
@@ -3744,7 +3744,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
selfsigned:
|
||||
optional: true
|
||||
checksum: 10/39ec36029e849cb5799c5cc8041b14df2732ec701215c25408b32b8e7fd3c7341f3f237edf7969cacaf4953684bd617b91a5730e144a2f21be899ab20f271fb7
|
||||
checksum: 10/e0ee2601158fef2fae9fbbaa4dabea9bda872e5e5655f33333bf54947b437c172d0c1d955e8fcf9661f026638627cbd89ab115547fc917ab2c3e789c7ce3e161
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4041,161 +4041,161 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/basic@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/basic@npm:4.1.0"
|
||||
"@tsparticles/basic@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/basic@npm:4.0.5"
|
||||
dependencies:
|
||||
"@tsparticles/engine": "npm:4.1.0"
|
||||
"@tsparticles/plugin-blend": "npm:4.1.0"
|
||||
"@tsparticles/plugin-hex-color": "npm:4.1.0"
|
||||
"@tsparticles/plugin-hsl-color": "npm:4.1.0"
|
||||
"@tsparticles/plugin-move": "npm:4.1.0"
|
||||
"@tsparticles/plugin-rgb-color": "npm:4.1.0"
|
||||
"@tsparticles/shape-circle": "npm:4.1.0"
|
||||
"@tsparticles/updater-opacity": "npm:4.1.0"
|
||||
"@tsparticles/updater-out-modes": "npm:4.1.0"
|
||||
"@tsparticles/updater-paint": "npm:4.1.0"
|
||||
"@tsparticles/updater-size": "npm:4.1.0"
|
||||
checksum: 10/aae6ebde0377e9f5b2a150cc94c7e140012d61d33ce0b2ab135e2513a106dad923f4704db67e29f912374600dc474c206697a06d846173141e4830f89cff2661
|
||||
"@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.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/canvas-utils@npm:4.1.0"
|
||||
"@tsparticles/canvas-utils@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/canvas-utils@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/bdde613b64665756080e9937bd4013a4e026e23baea1e536a9ae6785ff1b14bef5bbebca4eed6a10404cffcc375737fc101cb27f068146b983a34b7967a1f729
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/7ba361a2c4a1636e1f4594ed2937a7b469ada5c26cfd487dabc6f0fac6dadad906b7c32fb598cef09de3fdba6346a2fed6b49fd830edbdd31bb92249febf93e4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/engine@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/engine@npm:4.1.0"
|
||||
checksum: 10/9b8fc1e8f6ae67541d8c230996af9c27e1da04ba2d8d6f9daf5de4f321a523dfc968a0fab033708c471a9cde6ea0f1c5634cbb5ef3a3d27ef4b182dc193212f1
|
||||
"@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.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/interaction-particles-links@npm:4.1.0"
|
||||
"@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.1.0"
|
||||
"@tsparticles/canvas-utils": "npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.0
|
||||
"@tsparticles/plugin-interactivity": 4.1.0
|
||||
checksum: 10/2f74b25a1e585e6427034e38daba851880e2ae342da4558e3a9910bd37e0fb812438e19252ce11807dd0ed8bd53c3a5e48329b04450eac672310d1e058f720db
|
||||
"@tsparticles/engine": 4.0.5
|
||||
"@tsparticles/plugin-interactivity": 4.0.5
|
||||
checksum: 10/6ba65cec3c5ff7ca6fc85a38b2ef4451307d5004b1a6fdbfa77828cda824357f2a322ec1a0a4074413c793383670095adfef720846f286116b4c2921e6010d52
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-blend@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/plugin-blend@npm:4.1.0"
|
||||
"@tsparticles/plugin-blend@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/plugin-blend@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/e1913556ada7d70bbcd97b9591315fb15dc29edad3e31f50ec26639d225a15b5c237108a06173796f8a8bbb87e5f3f37ed40f3dece77f86bee4bd274600f813e
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/13d68a23303e37538ea4ed8066adea5f0506bb3c5916d3016ee00f2fa356ca927ed7f538b33f84fc737fe0f3d346dd53420c01a35e17474408b3484634617e09
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-hex-color@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/plugin-hex-color@npm:4.1.0"
|
||||
"@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.1.0
|
||||
checksum: 10/611992f350170e97daff323cecfa5f56625fbce0033d94dce5e579a5433ce9c46b1ceeb6381a714b58296fbd2802be806b8505d950af881f02e02ef8aac32f30
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/4fbc636fbfa96bb2912a78927522d993e158159ac78b823ffaefd35bd04af84a9195ba53f4a31e46b6cb7844e831eaeed1b59e4b0ac9e343a66ccc1e6c47816a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-hsl-color@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/plugin-hsl-color@npm:4.1.0"
|
||||
"@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.1.0
|
||||
checksum: 10/6e26d48733df47f5a91315b0144485440530fb5a6250b02254481157f05355ec59ded1fbfff6463d4dac40cb594a5ded660606a07b9376ce5f083eb92ff9aeac
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/66875e741f9bcce4ac8a9804889a653f75977a503d158128bab9f13addd1b5a16e2d5b18e86619ff8d8caaf196094dc54356452539b0d0c4eb003cd666023859
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-interactivity@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/plugin-interactivity@npm:4.1.0"
|
||||
"@tsparticles/plugin-interactivity@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/plugin-interactivity@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/7fe688b0531395e4f2e4f103bdd490471fa3e65c12ea98ac420f5380d5b00d460b99407ada19f3c2f1dbcedaffe061c399e43446cbd684e9e4df5c112e08baee
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/81bf958ae357a6d3aec00c7c16ac67cb4b438cf010e6e6e9ef1d851a02592f6849ff412d19048b3178f04a0957bfed74e1cbd48fe60ffc229664bad0f3e3470b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-move@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/plugin-move@npm:4.1.0"
|
||||
"@tsparticles/plugin-move@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/plugin-move@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/97dc5d4ecde770c1d7fc7b3bb2dc5075eb44405e2c541ab6a64e07c14a6aebf038ad4bf0dd7583606c63908a9e58b92a140acc9e437862376975d8c5b52dfc94
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/6a7ee5bfa70cbbe228ad199a18ad5ae038d08823052cb47b5f00382c184367c0f78242a66837549194b3d627ec37ac2118d94d87817ca6a2494f237c3c066f3e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-rgb-color@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/plugin-rgb-color@npm:4.1.0"
|
||||
"@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.1.0
|
||||
checksum: 10/1635482f8aa8664779fd30eaaab3cfd6d7ac53b2f5dafbbefd26a9151610eb40326c5240ed5439cb3f5e426a7111e80441554fd8a9848b8d6fe5176cb243ed0e
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/c7428953d2674233414a094c24d656ed4a43fa084f082f302f2d70cf54aa9a3bf913f1397879919cbf84ec02850a3e0d9ec85f5a56cc6c3b49fef6d5accd87f7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/preset-links@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/preset-links@npm:4.1.0"
|
||||
"@tsparticles/preset-links@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/preset-links@npm:4.0.5"
|
||||
dependencies:
|
||||
"@tsparticles/basic": "npm:4.1.0"
|
||||
"@tsparticles/engine": "npm:4.1.0"
|
||||
"@tsparticles/interaction-particles-links": "npm:4.1.0"
|
||||
"@tsparticles/plugin-interactivity": "npm:4.1.0"
|
||||
checksum: 10/12e7da30de505a0f10f8caeafebdd6393e998cd3159d1b478cf28720a14f4eadd28ee82630eb08e10962987e8da3114f638dc500994fb45cff8d3c64e75433fb
|
||||
"@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.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/shape-circle@npm:4.1.0"
|
||||
"@tsparticles/shape-circle@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/shape-circle@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/eff8a96c1816e183079b93b5734f7055cf85c4cbc42cc1cdd759ec114303d7c532299827bee6754644387b4894a78b9fb593551a3ef86e39f49eaef943ce856b
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/d655f9b26307185404e2a11f9d951e7d191ad8e951706b91be1c99a5aac6a27cb07d4cbfbbbe926221c267df8913a6548edb6e7af0348dff4ff71ab1f5cced0e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-opacity@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/updater-opacity@npm:4.1.0"
|
||||
"@tsparticles/updater-opacity@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/updater-opacity@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/67394b4e63017db0ca758025ec2283c46ad09689310b6872d486395db77f8432de34a1a145547b8248a279d4becbda8d586684d44ddce8917ef3ac8b4f15a383
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/a4a71c0683602faf939898e1541d4b110f758ea2eeaf9c32bcfe256e6142646a15058bd343d5df902ce6624f17b701738dd308360cc992fa3c3972dc2141fb0b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-out-modes@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/updater-out-modes@npm:4.1.0"
|
||||
"@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.1.0
|
||||
checksum: 10/e308dbf854d65eb87fadd43c80e2c2d578399ea2e446327f2b5956a57d5c135e258251a4bdeaceace44d169abf326eabd8f7c4bcfcb027dcde2942b2a28c9ea4
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/4973ffd626dad38428b0f02d29f1569d1ee02e319262c656e2e502b618b6bd28881a803c06f8fc1b291bd7e9cdd11eab2d0fea5a35e80940a1557f37c6c0af97
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-paint@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/updater-paint@npm:4.1.0"
|
||||
"@tsparticles/updater-paint@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/updater-paint@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/212e389e9e728a2d69a706bc606d6f6e6912ebd562fe4428236e1b72af3b49cf19043a4b72a26091230ab3ec128ed99cd05a8e6b70ad8cff02413d3dcdaabb6d
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/9933878bb03fa4ebb95ed6c820f684845a5e2fb3ef743779a325bfb92eaeb24dc228a3dfc3c68ba7842b8b93355555743cae540ab8f23506a0c2b953f207ee77
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-size@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/updater-size@npm:4.1.0"
|
||||
"@tsparticles/updater-size@npm:4.0.5":
|
||||
version: 4.0.5
|
||||
resolution: "@tsparticles/updater-size@npm:4.0.5"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/8e513044cbc4fa84e00008a77d0069cdebcf5efed0414aad5ad2a09497226182fac9d85037594853468d0ef2ae210616852fecf35abef44d89edf24fedf87c51
|
||||
"@tsparticles/engine": 4.0.5
|
||||
checksum: 10/560086273877b30ce6265785173c5865cd72a6c7524222ce8ff15f026c54d5ffaba7fb111c3c0fb875e21aadcc84776f3efea8dcba3e96b46a4343f69a89fedc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -6571,10 +6571,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"date-fns@npm:4.4.0":
|
||||
version: 4.4.0
|
||||
resolution: "date-fns@npm:4.4.0"
|
||||
checksum: 10/ae702acea42fb8452abe74f6d133c2360de9a14f00985645684b0df9c12280276649e95b52298a4fd7dabcc3b38799eb111107da99320d1cd94ba1d8a3d1c174
|
||||
"date-fns@npm:4.3.0":
|
||||
version: 4.3.0
|
||||
resolution: "date-fns@npm:4.3.0"
|
||||
checksum: 10/be129ab084a43cb06d2f598008fc3816b9c3767f0e9b7f08c19981b99f361cdee623c63d89e8eb305ebb488778027358127284a2f2aca6da258b3cd33d7366fe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -7394,16 +7394,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint@npm:10.4.1":
|
||||
version: 10.4.1
|
||||
resolution: "eslint@npm:10.4.1"
|
||||
"eslint@npm:10.4.0":
|
||||
version: 10.4.0
|
||||
resolution: "eslint@npm:10.4.0"
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils": "npm:^4.8.0"
|
||||
"@eslint-community/regexpp": "npm:^4.12.2"
|
||||
"@eslint/config-array": "npm:^0.23.5"
|
||||
"@eslint/config-helpers": "npm:^0.6.0"
|
||||
"@eslint/core": "npm:^1.2.1"
|
||||
"@eslint/plugin-kit": "npm:^0.7.2"
|
||||
"@eslint/plugin-kit": "npm:^0.7.1"
|
||||
"@humanfs/node": "npm:^0.16.6"
|
||||
"@humanwhocodes/module-importer": "npm:^1.0.1"
|
||||
"@humanwhocodes/retry": "npm:^0.4.2"
|
||||
@@ -7435,7 +7435,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
eslint: bin/eslint.js
|
||||
checksum: 10/5722bd0ec1a87f49ee4511c7549dcfa69df0076927b0290b0a3b145425fd357906ffe6dc1307214a0bd344131bf53795fa2cdebfd36ee9146c01d98147044679
|
||||
checksum: 10/ab20a8fd250ee7c31a162d35a71e534a4f959736a7198661e5c963069b12880a53acfb349f3dc94fa82c91c74dd0594accbd878576bf65c33fa4225342ad7df6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8484,11 +8484,11 @@ __metadata:
|
||||
"@replit/codemirror-indentation-markers": "npm:6.5.3"
|
||||
"@rsdoctor/rspack-plugin": "npm:1.5.12"
|
||||
"@rspack/core": "npm:2.0.5"
|
||||
"@rspack/dev-server": "npm:2.0.3"
|
||||
"@rspack/dev-server": "npm:2.0.1"
|
||||
"@swc/helpers": "npm:0.5.23"
|
||||
"@thomasloven/round-slider": "npm:0.6.0"
|
||||
"@tsparticles/engine": "npm:4.1.0"
|
||||
"@tsparticles/preset-links": "npm:4.1.0"
|
||||
"@tsparticles/engine": "npm:4.0.5"
|
||||
"@tsparticles/preset-links": "npm:4.0.5"
|
||||
"@types/chromecast-caf-receiver": "npm:6.0.26"
|
||||
"@types/chromecast-caf-sender": "npm:1.0.11"
|
||||
"@types/color-name": "npm:2.0.0"
|
||||
@@ -8517,14 +8517,14 @@ __metadata:
|
||||
core-js: "npm:3.49.0"
|
||||
cropperjs: "npm:1.6.2"
|
||||
culori: "npm:4.0.2"
|
||||
date-fns: "npm:4.4.0"
|
||||
date-fns: "npm:4.3.0"
|
||||
deep-clone-simple: "npm:1.1.1"
|
||||
deep-freeze: "npm:0.0.1"
|
||||
del: "npm:8.0.1"
|
||||
dialog-polyfill: "npm:0.5.6"
|
||||
echarts: "npm:6.1.0"
|
||||
element-internals-polyfill: "npm:3.0.2"
|
||||
eslint: "npm:10.4.1"
|
||||
eslint: "npm:10.4.0"
|
||||
eslint-config-prettier: "npm:10.1.8"
|
||||
eslint-import-resolver-webpack: "npm:0.13.11"
|
||||
eslint-plugin-import-x: "npm:4.16.2"
|
||||
|
||||
Reference in New Issue
Block a user