mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-23 16:38:17 +00:00
Compare commits
20 Commits
quick-bar-
...
unassigned
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee82ec4987 | ||
|
|
97d51094df | ||
|
|
0904a1116c | ||
|
|
282458e645 | ||
|
|
063c2d776a | ||
|
|
97e0bc8080 | ||
|
|
21e2c676b8 | ||
|
|
214b8cd5c7 | ||
|
|
3bd5481274 | ||
|
|
ac63f991b2 | ||
|
|
97e9129832 | ||
|
|
cf95bf89c3 | ||
|
|
6180ba0156 | ||
|
|
a11b595435 | ||
|
|
d819f784ac | ||
|
|
c08d229757 | ||
|
|
ad481f0ad0 | ||
|
|
d9d1d8caf0 | ||
|
|
928d989c56 | ||
|
|
e39ad38efb |
24
package.json
24
package.json
@@ -37,15 +37,15 @@
|
||||
"@codemirror/view": "6.39.4",
|
||||
"@date-fns/tz": "1.4.1",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "7.0.2",
|
||||
"@formatjs/intl-displaynames": "7.0.2",
|
||||
"@formatjs/intl-durationformat": "0.8.2",
|
||||
"@formatjs/intl-getcanonicallocales": "3.0.2",
|
||||
"@formatjs/intl-listformat": "8.0.2",
|
||||
"@formatjs/intl-locale": "5.0.2",
|
||||
"@formatjs/intl-numberformat": "9.0.3",
|
||||
"@formatjs/intl-pluralrules": "6.0.2",
|
||||
"@formatjs/intl-relativetimeformat": "12.0.3",
|
||||
"@formatjs/intl-datetimeformat": "7.0.4",
|
||||
"@formatjs/intl-displaynames": "7.0.4",
|
||||
"@formatjs/intl-durationformat": "0.8.4",
|
||||
"@formatjs/intl-getcanonicallocales": "3.0.3",
|
||||
"@formatjs/intl-listformat": "8.0.4",
|
||||
"@formatjs/intl-locale": "5.0.4",
|
||||
"@formatjs/intl-numberformat": "9.0.5",
|
||||
"@formatjs/intl-pluralrules": "6.0.4",
|
||||
"@formatjs/intl-relativetimeformat": "12.0.5",
|
||||
"@fullcalendar/core": "6.1.19",
|
||||
"@fullcalendar/daygrid": "6.1.19",
|
||||
"@fullcalendar/interaction": "6.1.19",
|
||||
@@ -112,7 +112,7 @@
|
||||
"hls.js": "1.6.15",
|
||||
"home-assistant-js-websocket": "9.6.0",
|
||||
"idb-keyval": "6.2.2",
|
||||
"intl-messageformat": "11.0.2",
|
||||
"intl-messageformat": "11.0.4",
|
||||
"js-yaml": "4.1.1",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
|
||||
@@ -151,7 +151,7 @@
|
||||
"@babel/plugin-transform-runtime": "7.28.5",
|
||||
"@babel/preset-env": "7.28.5",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.21.7",
|
||||
"@lokalise/node-api": "15.5.0",
|
||||
"@lokalise/node-api": "15.6.0",
|
||||
"@octokit/auth-oauth-device": "8.0.3",
|
||||
"@octokit/plugin-retry": "8.0.3",
|
||||
"@octokit/rest": "22.0.1",
|
||||
@@ -210,7 +210,7 @@
|
||||
"prettier": "3.7.4",
|
||||
"rspack-manifest-plugin": "5.2.0",
|
||||
"serve": "14.2.5",
|
||||
"sinon": "21.0.0",
|
||||
"sinon": "21.0.1",
|
||||
"tar": "7.5.2",
|
||||
"terser-webpack-plugin": "5.3.16",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
export const THEME_COLORS = new Set([
|
||||
"primary",
|
||||
"accent",
|
||||
"disabled",
|
||||
"red",
|
||||
"pink",
|
||||
"purple",
|
||||
@@ -25,6 +24,9 @@ export const THEME_COLORS = new Set([
|
||||
"blue-grey",
|
||||
"black",
|
||||
"white",
|
||||
"primary-text",
|
||||
"secondary-text",
|
||||
"disabled",
|
||||
]);
|
||||
|
||||
export function computeCssColor(color: string): string {
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface EntityFilter {
|
||||
label?: string | string[];
|
||||
entity_category?: EntityCategory | EntityCategory[];
|
||||
hidden_platform?: string | string[];
|
||||
hidden_domains?: string | string[];
|
||||
}
|
||||
|
||||
export type EntityFilterFunc = (entityId: string) => boolean;
|
||||
@@ -38,6 +39,9 @@ export const generateEntityFilter = (
|
||||
const domains = filter.domain
|
||||
? new Set(ensureArray(filter.domain))
|
||||
: undefined;
|
||||
const hiddenDomains = filter.hidden_domains
|
||||
? new Set(ensureArray(filter.hidden_domains))
|
||||
: undefined;
|
||||
const deviceClasses = filter.device_class
|
||||
? new Set(ensureArray(filter.device_class))
|
||||
: undefined;
|
||||
@@ -57,12 +61,16 @@ export const generateEntityFilter = (
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
if (domains) {
|
||||
if (domains || hiddenDomains) {
|
||||
const domain = computeDomain(entityId);
|
||||
if (!domains.has(domain)) {
|
||||
if (domains && !domains.has(domain)) {
|
||||
return false;
|
||||
}
|
||||
if (hiddenDomains && hiddenDomains.has(domain)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceClasses) {
|
||||
const dc = stateObj.attributes.device_class || "none";
|
||||
if (!deviceClasses.has(dc)) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { mdiInvertColorsOff, mdiPalette } from "@mdi/js";
|
||||
import { html, LitElement } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeCssColor, THEME_COLORS } from "../common/color/compute-color";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { LocalizeKeys } from "../common/translations/localize";
|
||||
@@ -49,11 +50,40 @@ export class HaColorPicker extends LitElement {
|
||||
.rowRenderer=${this._rowRenderer}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
@value-changed=${this._valueChanged}
|
||||
.notFoundLabel=${this.hass.localize(
|
||||
"ui.components.color-picker.no_colors_found"
|
||||
)}
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
private _getAdditionalItems = (
|
||||
searchString?: string
|
||||
): PickerComboBoxItem[] => {
|
||||
if (!searchString || searchString.trim() === "") {
|
||||
return [];
|
||||
}
|
||||
const colors = this._getColors(
|
||||
this.includeNone,
|
||||
this.includeState,
|
||||
this.defaultColor,
|
||||
this.value
|
||||
);
|
||||
const exactMatch = colors.find((color) => color.id === searchString);
|
||||
if (exactMatch) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
id: searchString,
|
||||
primary: this.hass.localize("ui.components.color-picker.custom_color"),
|
||||
secondary: searchString,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
private _getItems = () =>
|
||||
this._getColors(
|
||||
this.includeNone,
|
||||
@@ -62,72 +92,70 @@ export class HaColorPicker extends LitElement {
|
||||
this.value
|
||||
);
|
||||
|
||||
private _getColors = (
|
||||
includeNone: boolean,
|
||||
includeState: boolean,
|
||||
defaultColor: string | undefined,
|
||||
currentValue: string | undefined
|
||||
): PickerComboBoxItem[] => {
|
||||
const items: PickerComboBoxItem[] = [];
|
||||
private _getColors = memoizeOne(
|
||||
(
|
||||
includeNone: boolean,
|
||||
includeState: boolean,
|
||||
defaultColor: string | undefined,
|
||||
currentValue: string | undefined
|
||||
): PickerComboBoxItem[] => {
|
||||
const items: PickerComboBoxItem[] = [];
|
||||
|
||||
const defaultSuffix = this.hass.localize(
|
||||
"ui.components.color-picker.default"
|
||||
);
|
||||
const defaultSuffix = this.hass.localize(
|
||||
"ui.components.color-picker.default"
|
||||
);
|
||||
|
||||
const addDefaultSuffix = (label: string, isDefault: boolean) =>
|
||||
isDefault && defaultSuffix ? `${label} (${defaultSuffix})` : label;
|
||||
const addDefaultSuffix = (label: string, isDefault: boolean) =>
|
||||
isDefault && defaultSuffix ? `${label} (${defaultSuffix})` : label;
|
||||
|
||||
if (includeNone) {
|
||||
const noneLabel =
|
||||
this.hass.localize("ui.components.color-picker.none") || "None";
|
||||
items.push({
|
||||
id: "none",
|
||||
primary: addDefaultSuffix(noneLabel, defaultColor === "none"),
|
||||
icon_path: mdiInvertColorsOff,
|
||||
sorting_label: noneLabel,
|
||||
if (includeNone) {
|
||||
const noneLabel =
|
||||
this.hass.localize("ui.components.color-picker.none") || "None";
|
||||
items.push({
|
||||
id: "none",
|
||||
primary: addDefaultSuffix(noneLabel, defaultColor === "none"),
|
||||
icon_path: mdiInvertColorsOff,
|
||||
});
|
||||
}
|
||||
|
||||
if (includeState) {
|
||||
const stateLabel =
|
||||
this.hass.localize("ui.components.color-picker.state") || "State";
|
||||
items.push({
|
||||
id: "state",
|
||||
primary: addDefaultSuffix(stateLabel, defaultColor === "state"),
|
||||
icon_path: mdiPalette,
|
||||
});
|
||||
}
|
||||
|
||||
Array.from(THEME_COLORS).forEach((color) => {
|
||||
const themeLabel =
|
||||
this.hass.localize(
|
||||
`ui.components.color-picker.colors.${color}` as LocalizeKeys
|
||||
) || color;
|
||||
items.push({
|
||||
id: color,
|
||||
primary: addDefaultSuffix(themeLabel, defaultColor === color),
|
||||
});
|
||||
});
|
||||
|
||||
const isSpecial =
|
||||
currentValue === "none" ||
|
||||
currentValue === "state" ||
|
||||
THEME_COLORS.has(currentValue || "");
|
||||
|
||||
const hasValue = currentValue && currentValue.length > 0;
|
||||
|
||||
if (hasValue && !isSpecial) {
|
||||
items.push({
|
||||
id: currentValue!,
|
||||
primary: currentValue!,
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
if (includeState) {
|
||||
const stateLabel =
|
||||
this.hass.localize("ui.components.color-picker.state") || "State";
|
||||
items.push({
|
||||
id: "state",
|
||||
primary: addDefaultSuffix(stateLabel, defaultColor === "state"),
|
||||
icon_path: mdiPalette,
|
||||
sorting_label: stateLabel,
|
||||
});
|
||||
}
|
||||
|
||||
Array.from(THEME_COLORS).forEach((color) => {
|
||||
const themeLabel =
|
||||
this.hass.localize(
|
||||
`ui.components.color-picker.colors.${color}` as LocalizeKeys
|
||||
) || color;
|
||||
items.push({
|
||||
id: color,
|
||||
primary: addDefaultSuffix(themeLabel, defaultColor === color),
|
||||
sorting_label: themeLabel,
|
||||
});
|
||||
});
|
||||
|
||||
const isSpecial =
|
||||
currentValue === "none" ||
|
||||
currentValue === "state" ||
|
||||
THEME_COLORS.has(currentValue || "");
|
||||
|
||||
const hasValue = currentValue && currentValue.length > 0;
|
||||
|
||||
if (hasValue && !isSpecial) {
|
||||
items.push({
|
||||
id: currentValue!,
|
||||
primary: currentValue!,
|
||||
sorting_label: currentValue!,
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
);
|
||||
|
||||
private _rowRenderer: (
|
||||
item: PickerComboBoxItem,
|
||||
@@ -145,6 +173,9 @@ export class HaColorPicker extends LitElement {
|
||||
${this._renderColorCircle(item.id)}
|
||||
</span>`}
|
||||
<span slot="headline">${item.primary}</span>
|
||||
${item.secondary
|
||||
? html`<span slot="supporting-text">${item.secondary}</span>`
|
||||
: nothing}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
|
||||
|
||||
@@ -14,6 +14,12 @@ import "./ha-generic-picker";
|
||||
import type { HaGenericPicker } from "./ha-generic-picker";
|
||||
import type { PickerComboBoxItem } from "./ha-picker-combo-box";
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "primary", weight: 10 },
|
||||
{ name: "secondary", weight: 8 },
|
||||
{ name: "search_labels.english", weight: 5 },
|
||||
];
|
||||
|
||||
export const getLanguageOptions = (
|
||||
languages: string[],
|
||||
nativeName: boolean,
|
||||
@@ -22,6 +28,7 @@ export const getLanguageOptions = (
|
||||
): PickerComboBoxItem[] => {
|
||||
let options: PickerComboBoxItem[] = [];
|
||||
|
||||
const enLocale = { language: "en" } as FrontendLocaleData;
|
||||
if (nativeName) {
|
||||
const translations = translationMetadata.translations;
|
||||
options = languages.map((lang) => {
|
||||
@@ -37,16 +44,35 @@ export const getLanguageOptions = (
|
||||
primary = lang;
|
||||
}
|
||||
}
|
||||
const currentLang = formatLanguageCode(
|
||||
lang,
|
||||
locale || ({ language: navigator.language } as FrontendLocaleData)
|
||||
);
|
||||
const englishName = formatLanguageCode(lang, enLocale);
|
||||
|
||||
const secondary = currentLang !== primary ? currentLang : undefined;
|
||||
|
||||
return {
|
||||
id: lang,
|
||||
primary,
|
||||
secondary,
|
||||
search_labels: {
|
||||
english: englishName !== primary ? englishName : null,
|
||||
},
|
||||
};
|
||||
});
|
||||
} else if (locale) {
|
||||
options = languages.map((lang) => ({
|
||||
id: lang,
|
||||
primary: formatLanguageCode(lang, locale),
|
||||
}));
|
||||
options = languages.map((lang) => {
|
||||
const primary = formatLanguageCode(lang, locale);
|
||||
const englishName = formatLanguageCode(lang, enLocale);
|
||||
return {
|
||||
id: lang,
|
||||
primary,
|
||||
search_labels: {
|
||||
english: englishName !== primary ? englishName : null,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (!noSort && locale) {
|
||||
@@ -140,6 +166,7 @@ export class HaLanguagePicker extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
.helper=${this.helper}
|
||||
.getItems=${this._getItems}
|
||||
.searchKeys=${SEARCH_KEYS}
|
||||
@value-changed=${this._changed}
|
||||
hide-clear-icon
|
||||
>
|
||||
|
||||
@@ -436,7 +436,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
filteredItems.push(NO_ITEMS_AVAILABLE_ID);
|
||||
}
|
||||
|
||||
const additionalItems = this._getAdditionalItems();
|
||||
const additionalItems = this._getAdditionalItems(searchString);
|
||||
filteredItems.push(...additionalItems);
|
||||
|
||||
if (this.searchFn) {
|
||||
|
||||
@@ -21,7 +21,7 @@ export class HaTab extends LitElement {
|
||||
aria-label=${ifDefined(this.name)}
|
||||
@keydown=${this._handleKeyDown}
|
||||
>
|
||||
${this.narrow ? html`<slot name="icon"></slot>` : ""}
|
||||
<slot name="icon"></slot>
|
||||
<span class="name">${this.name}</span>
|
||||
<ha-ripple></ha-ripple>
|
||||
</div>
|
||||
@@ -50,6 +50,11 @@ export class HaTab extends LitElement {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
:host(:not([narrow])) div {
|
||||
flex-direction: row;
|
||||
gap: var(--ha-space-2);
|
||||
}
|
||||
|
||||
.name {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
@@ -76,6 +81,14 @@ export class HaTab extends LitElement {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
::slotted([slot="icon"]) {
|
||||
margin-bottom: var(--ha-space-1);
|
||||
}
|
||||
|
||||
:host(:not([narrow])) ::slotted([slot="icon"]) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div:focus-visible:before {
|
||||
position: absolute;
|
||||
display: block;
|
||||
|
||||
@@ -13,6 +13,7 @@ import { fireEvent } from "../common/dom/fire_event";
|
||||
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { isIosApp } from "../util/is_ios";
|
||||
import "./ha-dialog-header";
|
||||
import "./ha-icon-button";
|
||||
|
||||
@@ -184,6 +185,21 @@ export class HaWaDialog extends ScrollableFadeMixin(LitElement) {
|
||||
await this.updateComplete;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (isIosApp(this.hass)) {
|
||||
const element = this.querySelector("[autofocus]");
|
||||
if (element !== null) {
|
||||
if (!element.id) {
|
||||
element.id = "ha-wa-dialog-autofocus";
|
||||
}
|
||||
this.hass.auth.external!.fireMessage({
|
||||
type: "focus_element",
|
||||
payload: {
|
||||
element_id: element.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
(this.querySelector("[autofocus]") as HTMLElement | null)?.focus();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -96,6 +96,22 @@ export interface ConfigEntryUpdate {
|
||||
entry: ConfigEntry;
|
||||
}
|
||||
|
||||
export const subscribeAndProcessConfigEntries = (
|
||||
hass: HomeAssistant,
|
||||
callbackFunction: (entries: ConfigEntry[]) => void,
|
||||
filters?: {
|
||||
type?: IntegrationType[];
|
||||
domain?: string;
|
||||
}
|
||||
): Promise<UnsubscribeFunc> => {
|
||||
const stream = new ConfigEntryStream();
|
||||
const processCallback = (messages: ConfigEntryUpdate[]) => {
|
||||
callbackFunction(stream.processMessage(messages));
|
||||
};
|
||||
|
||||
return subscribeConfigEntries(hass, processCallback, filters);
|
||||
};
|
||||
|
||||
export const subscribeConfigEntries = (
|
||||
hass: HomeAssistant,
|
||||
callbackFunction: (message: ConfigEntryUpdate[]) => void,
|
||||
@@ -110,10 +126,9 @@ export const subscribeConfigEntries = (
|
||||
if (filters && filters.type) {
|
||||
params.type_filter = filters.type;
|
||||
}
|
||||
return hass.connection.subscribeMessage<ConfigEntryUpdate[]>(
|
||||
(message) => callbackFunction(message),
|
||||
params
|
||||
);
|
||||
return hass.connection.subscribeMessage<ConfigEntryUpdate[]>((message) => {
|
||||
callbackFunction(message);
|
||||
}, params);
|
||||
};
|
||||
|
||||
export const getConfigEntries = (
|
||||
@@ -206,3 +221,29 @@ export const sortConfigEntries = (
|
||||
);
|
||||
return [primaryEntry, ...otherEntries];
|
||||
};
|
||||
|
||||
export class ConfigEntryStream {
|
||||
private _entries: ConfigEntry[] = [];
|
||||
|
||||
processMessage(message: ConfigEntryUpdate[]) {
|
||||
message.forEach((configEntry) => {
|
||||
if (configEntry.type === null || configEntry.type === "added") {
|
||||
this._entries.push(configEntry.entry);
|
||||
return;
|
||||
}
|
||||
if (configEntry.type === "removed") {
|
||||
this._entries = this._entries.filter(
|
||||
(entry) => entry.entry_id !== configEntry.entry.entry_id
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (configEntry.type === "updated") {
|
||||
const newEntry = configEntry.entry;
|
||||
this._entries = this._entries.map((entry) =>
|
||||
entry.entry_id === newEntry.entry_id ? newEntry : entry
|
||||
);
|
||||
}
|
||||
});
|
||||
return this._entries;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createContext } from "@lit/context";
|
||||
import type { HassConfig } from "home-assistant-js-websocket";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { ConfigEntry } from "./config_entries";
|
||||
import type { EntityRegistryEntry } from "./entity/entity_registry";
|
||||
import type { LabelRegistryEntry } from "./label/label_registry";
|
||||
|
||||
@@ -30,3 +31,6 @@ export const fullEntitiesContext =
|
||||
export const floorsContext = createContext<HomeAssistant["floors"]>("floors");
|
||||
|
||||
export const labelsContext = createContext<LabelRegistryEntry[]>("labels");
|
||||
|
||||
export const configEntriesContext =
|
||||
createContext<ConfigEntry[]>("configEntries");
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { atLeastVersion } from "../common/config/version";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { HassioAddonInfo } from "./hassio/addon";
|
||||
|
||||
export interface LogProvider {
|
||||
key: string;
|
||||
name: string;
|
||||
addon?: HassioAddonInfo;
|
||||
}
|
||||
|
||||
export const fetchErrorLog = (hass: HomeAssistant) =>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ContextProvider } from "@lit/context";
|
||||
import { mdiContentSave, mdiHelpCircle } from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { load } from "js-yaml";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
@@ -46,7 +47,13 @@ import {
|
||||
isTrigger,
|
||||
normalizeAutomationConfig,
|
||||
} from "../../../data/automation";
|
||||
import {
|
||||
subscribeAndProcessConfigEntries,
|
||||
type ConfigEntry,
|
||||
} from "../../../data/config_entries";
|
||||
import { configEntriesContext } from "../../../data/context";
|
||||
import { getActionType, type Action } from "../../../data/script";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { showToast } from "../../../util/toast";
|
||||
@@ -80,7 +87,7 @@ const automationConfigStruct = union([
|
||||
export const SIDEBAR_DEFAULT_WIDTH = 500;
|
||||
|
||||
@customElement("manual-automation-editor")
|
||||
export class HaManualAutomationEditor extends LitElement {
|
||||
export class HaManualAutomationEditor extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: "is-wide", type: Boolean }) public isWide = false;
|
||||
@@ -117,6 +124,11 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
HaAutomationAction | HaAutomationCondition
|
||||
>;
|
||||
|
||||
private _configEntries = new ContextProvider(this, {
|
||||
context: configEntriesContext,
|
||||
initialValue: [],
|
||||
});
|
||||
|
||||
private _prevSidebarWidthPx?: number;
|
||||
|
||||
public connectedCallback() {
|
||||
@@ -124,6 +136,18 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
window.addEventListener("paste", this._handlePaste);
|
||||
}
|
||||
|
||||
public hassSubscribe(): Promise<UnsubscribeFunc>[] {
|
||||
return [
|
||||
subscribeAndProcessConfigEntries(
|
||||
this.hass,
|
||||
(message: ConfigEntry[]) => {
|
||||
this._configEntries.setValue(message);
|
||||
},
|
||||
undefined
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
window.removeEventListener("paste", this._handlePaste);
|
||||
super.disconnectedCallback();
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { consume } from "@lit/context";
|
||||
import { mdiAlert, mdiFormatListBulleted, mdiShape } from "@mdi/js";
|
||||
import type { HassServiceTarget } from "home-assistant-js-websocket";
|
||||
import { LitElement, css, html, nothing, type TemplateResult } from "lit";
|
||||
import { css, html, LitElement, type nothing, type TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { until } from "lit/directives/until";
|
||||
import { ensureArray } from "../../../../common/array/ensure-array";
|
||||
import { transform } from "../../../../common/decorators/transform";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import {
|
||||
getConfigEntries,
|
||||
type ConfigEntry,
|
||||
} from "../../../../data/config_entries";
|
||||
import type { ConfigEntry } from "../../../../data/config_entries";
|
||||
import {
|
||||
areasContext,
|
||||
configEntriesContext,
|
||||
devicesContext,
|
||||
floorsContext,
|
||||
labelsContext,
|
||||
@@ -55,6 +53,13 @@ export class HaAutomationRowTargets extends LitElement {
|
||||
@consume({ context: labelsContext, subscribe: true })
|
||||
private _labelRegistry!: LabelRegistryEntry[];
|
||||
|
||||
@state()
|
||||
@consume({ context: configEntriesContext, subscribe: true })
|
||||
@transform<ConfigEntry[], Record<string, ConfigEntry>>({
|
||||
transformer: function (value) {
|
||||
return Object.fromEntries(value.map((entry) => [entry.entry_id, entry]));
|
||||
},
|
||||
})
|
||||
private _configEntryLookup?: Record<string, ConfigEntry>;
|
||||
|
||||
protected render() {
|
||||
@@ -149,13 +154,6 @@ export class HaAutomationRowTargets extends LitElement {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private async _loadConfigEntries() {
|
||||
const configEntries = await getConfigEntries(this.hass);
|
||||
this._configEntryLookup = Object.fromEntries(
|
||||
configEntries.map((entry) => [entry.entry_id, entry])
|
||||
);
|
||||
}
|
||||
|
||||
private _renderTarget(
|
||||
targetType: "floor" | "area" | "device" | "entity" | "label",
|
||||
targetId: string
|
||||
@@ -178,22 +176,6 @@ export class HaAutomationRowTargets extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
if (targetType === "device" && !this._configEntryLookup) {
|
||||
const loadConfigEntries = this._loadConfigEntries().then(() =>
|
||||
this._renderTargetBadge(
|
||||
getTargetIcon(
|
||||
this.hass,
|
||||
targetType,
|
||||
targetId,
|
||||
this._configEntryLookup!
|
||||
),
|
||||
getTargetText(this.hass, targetType, targetId)
|
||||
)
|
||||
);
|
||||
|
||||
return html`${until(loadConfigEntries, nothing)}`;
|
||||
}
|
||||
|
||||
return this._renderTargetBadge(
|
||||
getTargetIcon(
|
||||
this.hass,
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
import { mdiChevronDown } from "@mdi/js";
|
||||
import {
|
||||
mdiChevronDown,
|
||||
mdiChip,
|
||||
mdiDns,
|
||||
mdiPackageVariant,
|
||||
mdiPuzzle,
|
||||
mdiRadar,
|
||||
mdiVolumeHigh,
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { atLeastVersion } from "../../../common/config/version";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { stringCompare } from "../../../common/string/compare";
|
||||
import { extractSearchParam } from "../../../common/url/search-params";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-generic-picker";
|
||||
import type { HaGenericPicker } from "../../../components/ha-generic-picker";
|
||||
import type { PickerComboBoxItem } from "../../../components/ha-picker-combo-box";
|
||||
import "../../../components/search-input";
|
||||
import type { LogProvider } from "../../../data/error_log";
|
||||
import { fetchHassioAddonsInfo } from "../../../data/hassio/addon";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { mdiHomeAssistant } from "../../../resources/home-assistant-logo-svg";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import "./error-log-card";
|
||||
@@ -64,6 +75,8 @@ export class HaConfigLogs extends LitElement {
|
||||
|
||||
@query("system-log-card") private systemLog?: SystemLogCard;
|
||||
|
||||
@query("ha-generic-picker") private providerPicker?: HaGenericPicker;
|
||||
|
||||
@state() private _selectedLogProvider = "core";
|
||||
|
||||
@state() private _logProviders = logProviders;
|
||||
@@ -109,6 +122,8 @@ export class HaConfigLogs extends LitElement {
|
||||
</div>
|
||||
`;
|
||||
|
||||
const selectedProvider = this._getActiveProvider(this._selectedLogProvider);
|
||||
|
||||
return html`
|
||||
<hass-subpage
|
||||
.hass=${this.hass}
|
||||
@@ -116,33 +131,39 @@ export class HaConfigLogs extends LitElement {
|
||||
.header=${this.hass.localize("ui.panel.config.logs.caption")}
|
||||
back-path="/config/system"
|
||||
>
|
||||
${isComponentLoaded(this.hass, "hassio")
|
||||
${isComponentLoaded(this.hass, "hassio") && this._logProviders
|
||||
? html`
|
||||
<ha-dropdown
|
||||
<ha-generic-picker
|
||||
slot="toolbar-icon"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
.hass=${this.hass}
|
||||
.getItems=${this._getLogProviderItems}
|
||||
value=""
|
||||
.rowRenderer=${this._providerRenderer}
|
||||
@value-changed=${this._handleDropdownSelect}
|
||||
>
|
||||
<ha-button slot="trigger" appearance="filled">
|
||||
<ha-button
|
||||
slot="field"
|
||||
appearance="filled"
|
||||
@click=${this._openPicker}
|
||||
>
|
||||
${selectedProvider?.icon
|
||||
? html`<img
|
||||
src=${selectedProvider.icon}
|
||||
alt=${selectedProvider.primary}
|
||||
slot="start"
|
||||
/>`
|
||||
: selectedProvider?.icon_path
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${selectedProvider.icon_path}
|
||||
></ha-svg-icon>`
|
||||
: nothing}
|
||||
${selectedProvider?.primary}
|
||||
<ha-svg-icon slot="end" .path=${mdiChevronDown}></ha-svg-icon>
|
||||
${this._logProviders.find(
|
||||
(p) => p.key === this._selectedLogProvider
|
||||
)!.name}
|
||||
</ha-button>
|
||||
${this._logProviders.map(
|
||||
(provider) => html`
|
||||
<ha-dropdown-item
|
||||
.value=${provider.key}
|
||||
class=${provider.key === this._selectedLogProvider
|
||||
? "selected"
|
||||
: ""}
|
||||
>
|
||||
${provider.name}
|
||||
</ha-dropdown-item>
|
||||
`
|
||||
)}
|
||||
</ha-dropdown>
|
||||
</ha-generic-picker>
|
||||
`
|
||||
: ""}
|
||||
: nothing}
|
||||
${search}
|
||||
<div class="content">
|
||||
${this._selectedLogProvider === "core" && !this._detail
|
||||
@@ -175,8 +196,13 @@ export class HaConfigLogs extends LitElement {
|
||||
this._detail = !this._detail;
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const provider = ev.detail?.item?.value;
|
||||
private _openPicker(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
this.providerPicker?.open();
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ value: string }>) {
|
||||
const provider = ev.detail?.value;
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
@@ -223,6 +249,7 @@ export class HaConfigLogs extends LitElement {
|
||||
.map((addon) => ({
|
||||
key: addon.slug,
|
||||
name: addon.name,
|
||||
addon,
|
||||
}))
|
||||
.sort((a, b) =>
|
||||
stringCompare(a.name, b.name, this.hass.locale.language)
|
||||
@@ -234,6 +261,78 @@ export class HaConfigLogs extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _getLogProviderItems = (): PickerComboBoxItem[] =>
|
||||
this._logProviders.map((provider) => ({
|
||||
id: provider.key,
|
||||
primary: provider.name,
|
||||
icon: provider.addon
|
||||
? atLeastVersion(this.hass.config.version, 0, 105) &&
|
||||
provider.addon.icon
|
||||
? `/api/hassio/addons/${provider.addon.slug}/icon`
|
||||
: undefined
|
||||
: undefined,
|
||||
icon_path: provider.addon
|
||||
? mdiPuzzle
|
||||
: this._getProviderIconPath(provider.key),
|
||||
}));
|
||||
|
||||
private _providerRenderer = (item: PickerComboBoxItem) => html`
|
||||
<ha-combo-box-item type="button" compact>
|
||||
${item.icon
|
||||
? html`<img src=${item.icon} alt=${item.primary} slot="start" />`
|
||||
: item.icon_path
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${item.icon_path}
|
||||
></ha-svg-icon>`
|
||||
: nothing}
|
||||
<span slot="headline">${item.primary}</span>
|
||||
${item.secondary
|
||||
? html`<span slot="supporting-text">${item.secondary}</span>`
|
||||
: nothing}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
|
||||
private _getActiveProvider = memoizeOne((selectedLogProvider: string) => {
|
||||
const provider = this._logProviders.find(
|
||||
(p) => p.key === selectedLogProvider
|
||||
);
|
||||
if (provider) {
|
||||
return {
|
||||
id: provider.key,
|
||||
primary: provider.name,
|
||||
icon: provider.addon
|
||||
? atLeastVersion(this.hass.config.version, 0, 105) &&
|
||||
provider.addon.icon
|
||||
? `/api/hassio/addons/${provider.addon.slug}/icon`
|
||||
: undefined
|
||||
: undefined,
|
||||
icon_path: provider.addon
|
||||
? mdiPuzzle
|
||||
: this._getProviderIconPath(provider.key),
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
private _getProviderIconPath(providerKey: string): string | undefined {
|
||||
switch (providerKey) {
|
||||
case "core":
|
||||
return mdiHomeAssistant;
|
||||
case "supervisor":
|
||||
return mdiPackageVariant;
|
||||
case "host":
|
||||
return mdiChip;
|
||||
case "dns":
|
||||
return mdiDns;
|
||||
case "audio":
|
||||
return mdiVolumeHigh;
|
||||
case "multicast":
|
||||
return mdiRadar;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -262,8 +361,17 @@ export class HaConfigLogs extends LitElement {
|
||||
.content {
|
||||
direction: ltr;
|
||||
}
|
||||
ha-generic-picker {
|
||||
--md-list-item-leading-icon-color: var(--ha-color-primary-50);
|
||||
--mdc-icon-size: 32px;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
@media all and (max-width: 870px) {
|
||||
ha-dropdown {
|
||||
ha-generic-picker {
|
||||
max-width: 50%;
|
||||
}
|
||||
ha-button {
|
||||
@@ -274,9 +382,6 @@ export class HaConfigLogs extends LitElement {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
ha-dropdown-item.selected {
|
||||
font-weight: var(--ha-font-weight-bold);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { ContextProvider } from "@lit/context";
|
||||
import { mdiContentSave, mdiHelpCircle } from "@mdi/js";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { load } from "js-yaml";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
@@ -35,6 +37,8 @@ import type {
|
||||
ActionSidebarConfig,
|
||||
SidebarConfig,
|
||||
} from "../../../data/automation";
|
||||
import { subscribeAndProcessConfigEntries } from "../../../data/config_entries";
|
||||
import { configEntriesContext } from "../../../data/context";
|
||||
import type {
|
||||
Action,
|
||||
Fields,
|
||||
@@ -46,6 +50,7 @@ import {
|
||||
MODES,
|
||||
normalizeScriptConfig,
|
||||
} from "../../../data/script";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { showToast } from "../../../util/toast";
|
||||
@@ -70,7 +75,7 @@ const scriptConfigStruct = object({
|
||||
});
|
||||
|
||||
@customElement("manual-script-editor")
|
||||
export class HaManualScriptEditor extends LitElement {
|
||||
export class HaManualScriptEditor extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: "is-wide", type: Boolean }) public isWide = false;
|
||||
@@ -108,6 +113,11 @@ export class HaManualScriptEditor extends LitElement {
|
||||
HaAutomationAction | HaScriptFields
|
||||
>;
|
||||
|
||||
private _configEntries = new ContextProvider(this, {
|
||||
context: configEntriesContext,
|
||||
initialValue: [],
|
||||
});
|
||||
|
||||
private _openFields = false;
|
||||
|
||||
private _prevSidebarWidthPx?: number;
|
||||
@@ -129,6 +139,14 @@ export class HaManualScriptEditor extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
public hassSubscribe(): Promise<UnsubscribeFunc>[] {
|
||||
return [
|
||||
subscribeAndProcessConfigEntries(this.hass, (configEntries) => {
|
||||
this._configEntries.setValue(configEntries);
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected updated(changedProps) {
|
||||
if (this._openFields && changedProps.has("config")) {
|
||||
this._openFields = false;
|
||||
|
||||
@@ -52,6 +52,8 @@ const STRATEGIES: Record<LovelaceStrategyConfigType, Record<string, any>> = {
|
||||
"home-media-players": () =>
|
||||
import("./home/home-media-players-view-strategy"),
|
||||
"home-area": () => import("./home/home-area-view-strategy"),
|
||||
"home-other-devices": () =>
|
||||
import("./home/home-other-devices-view-strategy"),
|
||||
light: () => import("../../light/strategies/light-view-strategy"),
|
||||
security: () => import("../../security/strategies/security-view-strategy"),
|
||||
climate: () => import("../../climate/strategies/climate-view-strategy"),
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { ASSIST_ENTITIES } from "../../../../../common/const";
|
||||
import type { EntityFilter } from "../../../../../common/entity/entity_filter";
|
||||
|
||||
export const OTHER_DEVICES_FILTERS: EntityFilter[] = [
|
||||
{
|
||||
area: null,
|
||||
hidden_platform: [
|
||||
"automation",
|
||||
"script",
|
||||
"hassio",
|
||||
"backup",
|
||||
"mobile_app",
|
||||
"zone",
|
||||
"person",
|
||||
],
|
||||
hidden_domains: [
|
||||
"ai_task",
|
||||
"automation",
|
||||
"configurator",
|
||||
"device_tracker",
|
||||
"event",
|
||||
"geo_location",
|
||||
"notify",
|
||||
"persistent_notification",
|
||||
"script",
|
||||
"sun",
|
||||
"tag",
|
||||
"todo",
|
||||
"zone",
|
||||
...ASSIST_ENTITIES,
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -71,6 +71,16 @@ export class HomeDashboardStrategy extends ReactiveElement {
|
||||
icon: HOME_SUMMARIES_ICONS.media_players,
|
||||
} satisfies LovelaceViewRawConfig;
|
||||
|
||||
const otherDevicesView = {
|
||||
title: hass.localize("ui.panel.lovelace.strategy.home.devices"),
|
||||
path: "other-devices",
|
||||
subview: true,
|
||||
strategy: {
|
||||
type: "home-other-devices",
|
||||
},
|
||||
icon: "mdi:devices",
|
||||
} satisfies LovelaceViewRawConfig;
|
||||
|
||||
return {
|
||||
views: [
|
||||
{
|
||||
@@ -83,6 +93,7 @@ export class HomeDashboardStrategy extends ReactiveElement {
|
||||
},
|
||||
...areaViews,
|
||||
mediaPlayersView,
|
||||
otherDevicesView,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { computeDeviceName } from "../../../../common/entity/compute_device_name";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
import { getEntityContext } from "../../../../common/entity/context/get_entity_context";
|
||||
import {
|
||||
findEntities,
|
||||
generateEntityFilter,
|
||||
} from "../../../../common/entity/entity_filter";
|
||||
import { clamp } from "../../../../common/number/clamp";
|
||||
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
|
||||
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { isHelperDomain } from "../../../config/helpers/const";
|
||||
import type { HeadingCardConfig } from "../../cards/types";
|
||||
import { OTHER_DEVICES_FILTERS } from "./helpers/other-devices-filters";
|
||||
|
||||
export interface HomeOtherDevicesViewStrategyConfig {
|
||||
type: "home-other-devices";
|
||||
}
|
||||
|
||||
@customElement("home-other-devices-view-strategy")
|
||||
export class HomeOtherDevicesViewStrategy extends ReactiveElement {
|
||||
static async generate(
|
||||
_config: HomeOtherDevicesViewStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceViewConfig> {
|
||||
const allEntities = Object.keys(hass.states);
|
||||
|
||||
const otherDevicesFilters = OTHER_DEVICES_FILTERS.map((filter) =>
|
||||
generateEntityFilter(hass, filter)
|
||||
);
|
||||
|
||||
const otherDevicesEntities = findEntities(allEntities, otherDevicesFilters);
|
||||
|
||||
const sections: LovelaceSectionRawConfig[] = [];
|
||||
|
||||
const entitiesByDevice: Record<string, string[]> = {};
|
||||
const entitiesWithoutDevices: string[] = [];
|
||||
for (const entityId of otherDevicesEntities) {
|
||||
const stateObj = hass.states[entityId];
|
||||
if (!stateObj) continue;
|
||||
const { device } = getEntityContext(
|
||||
stateObj,
|
||||
hass.entities,
|
||||
hass.devices,
|
||||
hass.areas,
|
||||
hass.floors
|
||||
);
|
||||
if (!device) {
|
||||
entitiesWithoutDevices.push(entityId);
|
||||
continue;
|
||||
}
|
||||
if (!(device.id in entitiesByDevice)) {
|
||||
entitiesByDevice[device.id] = [];
|
||||
}
|
||||
entitiesByDevice[device.id].push(entityId);
|
||||
}
|
||||
|
||||
const devicesEntities = Object.entries(entitiesByDevice).map(
|
||||
([deviceId, entities]) => ({
|
||||
device_id: deviceId,
|
||||
entities: entities,
|
||||
})
|
||||
);
|
||||
|
||||
const helpersEntities = entitiesWithoutDevices.filter((entityId) => {
|
||||
const domain = computeDomain(entityId);
|
||||
return isHelperDomain(domain);
|
||||
});
|
||||
|
||||
const otherEntities = entitiesWithoutDevices.filter((entityId) => {
|
||||
const domain = computeDomain(entityId);
|
||||
return !isHelperDomain(domain);
|
||||
});
|
||||
|
||||
const batteryFilter = generateEntityFilter(hass, {
|
||||
domain: "sensor",
|
||||
device_class: "battery",
|
||||
});
|
||||
|
||||
const energyFilter = generateEntityFilter(hass, {
|
||||
domain: "sensor",
|
||||
device_class: ["energy", "power"],
|
||||
});
|
||||
|
||||
const primaryFilter = generateEntityFilter(hass, {
|
||||
entity_category: "none",
|
||||
});
|
||||
|
||||
for (const deviceEntities of devicesEntities) {
|
||||
if (deviceEntities.entities.length === 0) continue;
|
||||
|
||||
const batteryEntities = deviceEntities.entities.filter((e) =>
|
||||
batteryFilter(e)
|
||||
);
|
||||
const entities = deviceEntities.entities.filter(
|
||||
(e) => !batteryFilter(e) && !energyFilter(e) && primaryFilter(e)
|
||||
);
|
||||
|
||||
if (entities.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const deviceId = deviceEntities.device_id;
|
||||
const device = hass.devices[deviceId];
|
||||
let heading = "";
|
||||
if (device) {
|
||||
heading =
|
||||
computeDeviceName(device) ||
|
||||
hass.localize("ui.panel.lovelace.strategy.home.unamed_device");
|
||||
}
|
||||
|
||||
sections.push({
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: heading,
|
||||
tap_action: device
|
||||
? {
|
||||
action: "navigate",
|
||||
navigation_path: `/config/devices/device/${device.id}`,
|
||||
}
|
||||
: undefined,
|
||||
badges: [
|
||||
...batteryEntities.slice(0, 1).map((e) => ({
|
||||
entity: e,
|
||||
type: "entity",
|
||||
tap_action: {
|
||||
action: "more-info",
|
||||
},
|
||||
})),
|
||||
],
|
||||
} satisfies HeadingCardConfig,
|
||||
...entities.map((e) => ({
|
||||
type: "tile",
|
||||
entity: e,
|
||||
name: {
|
||||
type: "entity",
|
||||
},
|
||||
})),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// Allow between 2 and 3 columns (the max should be set to define the width of the header)
|
||||
const maxColumns = clamp(sections.length, 2, 3);
|
||||
|
||||
if (helpersEntities.length) {
|
||||
sections.push({
|
||||
type: "grid",
|
||||
column_span: maxColumns,
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: hass.localize(
|
||||
"ui.panel.lovelace.strategy.other_devices.helpers"
|
||||
),
|
||||
} satisfies HeadingCardConfig,
|
||||
...helpersEntities.map((e) => ({
|
||||
type: "tile",
|
||||
entity: e,
|
||||
})),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
if (otherEntities.length) {
|
||||
sections.push({
|
||||
type: "grid",
|
||||
column_span: maxColumns,
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: hass.localize(
|
||||
"ui.panel.lovelace.strategy.other_devices.entities"
|
||||
),
|
||||
} satisfies HeadingCardConfig,
|
||||
...otherEntities.map((e) => ({
|
||||
type: "tile",
|
||||
entity: e,
|
||||
})),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// Take the full width if there is only one section to avoid narrow header on desktop
|
||||
if (sections.length === 1) {
|
||||
sections[0].column_span = 2;
|
||||
}
|
||||
|
||||
return {
|
||||
type: "sections",
|
||||
header: {
|
||||
badges_position: "bottom",
|
||||
},
|
||||
max_columns: maxColumns,
|
||||
sections: sections,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"home-other-devices-view-strategy": HomeOtherDevicesViewStrategy;
|
||||
}
|
||||
}
|
||||
@@ -22,10 +22,12 @@ import type {
|
||||
HomeSummaryCard,
|
||||
MarkdownCardConfig,
|
||||
TileCardConfig,
|
||||
WeatherForecastCardConfig,
|
||||
} from "../../cards/types";
|
||||
import type { Condition } from "../../common/validate-condition";
|
||||
import type { CommonControlSectionStrategyConfig } from "../usage_prediction/common-controls-section-strategy";
|
||||
import { HOME_SUMMARIES_FILTERS } from "./helpers/home-summaries";
|
||||
import type { Condition } from "../../common/validate-condition";
|
||||
import { OTHER_DEVICES_FILTERS } from "./helpers/other-devices-filters";
|
||||
|
||||
export interface HomeOverviewViewStrategyConfig {
|
||||
type: "home-overview";
|
||||
@@ -73,16 +75,25 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
|
||||
const maxColumns = 3;
|
||||
|
||||
const allEntities = Object.keys(hass.states);
|
||||
|
||||
const largeScreenCondition: Condition = {
|
||||
condition: "screen",
|
||||
media_query: "(min-width: 871px)",
|
||||
};
|
||||
|
||||
|
||||
const smallScreenCondition: Condition = {
|
||||
condition: "screen",
|
||||
media_query: "(max-width: 870px)",
|
||||
};
|
||||
|
||||
const otherDevicesFilters = OTHER_DEVICES_FILTERS.map((filter) =>
|
||||
generateEntityFilter(hass, filter)
|
||||
);
|
||||
|
||||
const entitiesWithoutAreas = findEntities(allEntities, otherDevicesFilters);
|
||||
|
||||
|
||||
const floorsSections: LovelaceSectionConfig[] = [];
|
||||
for (const floorStructure of home.floors) {
|
||||
const floorId = floorStructure.id;
|
||||
@@ -114,23 +125,57 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
}
|
||||
}
|
||||
|
||||
if (home.areas.length) {
|
||||
if (home.areas.length > 0 || entitiesWithoutAreas.length > 0) {
|
||||
const cards: LovelaceCardConfig[] = [];
|
||||
for (const areaId of home.areas) {
|
||||
cards.push(computeAreaCard(areaId, hass));
|
||||
}
|
||||
|
||||
if (entitiesWithoutAreas.length > 0) {
|
||||
cards.push({
|
||||
type: "tile",
|
||||
entity: "zone.home", // zone entity to represent unassigned area as it always exists
|
||||
vertical: true,
|
||||
name: hass.localize("ui.panel.lovelace.strategy.home.devices"),
|
||||
icon: "mdi:devices",
|
||||
hide_state: true,
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "other-devices",
|
||||
},
|
||||
grid_options: {
|
||||
rows: 2,
|
||||
columns: 4,
|
||||
},
|
||||
} as TileCardConfig);
|
||||
}
|
||||
|
||||
const noOtherAreas = home.areas.length === 0;
|
||||
const noFloor = home.floors.length === 0;
|
||||
|
||||
// Other areas / Areas / Others / nothing
|
||||
const heading =
|
||||
noFloor && noOtherAreas
|
||||
? undefined
|
||||
: noFloor
|
||||
? hass.localize("ui.panel.lovelace.strategy.home.areas")
|
||||
: noOtherAreas
|
||||
? hass.localize("ui.panel.lovelace.strategy.home.devices")
|
||||
: hass.localize("ui.panel.lovelace.strategy.home.other_areas");
|
||||
|
||||
floorsSections.push({
|
||||
type: "grid",
|
||||
column_span: maxColumns,
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading:
|
||||
floorCount > 1
|
||||
? hass.localize("ui.panel.lovelace.strategy.home.other_areas")
|
||||
: hass.localize("ui.panel.lovelace.strategy.home.areas"),
|
||||
heading_style: "title",
|
||||
},
|
||||
...(heading
|
||||
? [
|
||||
{
|
||||
type: "heading",
|
||||
heading: heading,
|
||||
heading_style: "title",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...cards,
|
||||
],
|
||||
});
|
||||
@@ -141,19 +186,18 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
);
|
||||
const maxCommonControls = Math.max(8, favoriteEntities.length);
|
||||
|
||||
const commonControlsSection = {
|
||||
const favoritesSection = {
|
||||
strategy: {
|
||||
type: "common-controls",
|
||||
limit: maxCommonControls,
|
||||
include_entities: favoriteEntities,
|
||||
title: hass.localize("ui.panel.lovelace.strategy.home.favorites"),
|
||||
title_visibilty: [largeScreenCondition],
|
||||
hide_empty: true,
|
||||
} satisfies CommonControlSectionStrategyConfig,
|
||||
column_span: maxColumns,
|
||||
} as LovelaceStrategySectionConfig;
|
||||
|
||||
const allEntities = Object.keys(hass.states);
|
||||
|
||||
const mediaPlayerFilter = HOME_SUMMARIES_FILTERS.media_players.map(
|
||||
(filter) => generateEntityFilter(hass, filter)
|
||||
);
|
||||
@@ -260,10 +304,18 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
}));
|
||||
|
||||
// Build summary cards for mobile section (half width: columns 6)
|
||||
const mobileSummaryCards = summaryCards.map((card) => ({
|
||||
...card,
|
||||
grid_options: { columns: 6 },
|
||||
}));
|
||||
const mobileSummaryCards = [
|
||||
...summaryCards.map((card) => ({
|
||||
...card,
|
||||
grid_options: { columns: 6 },
|
||||
})),
|
||||
];
|
||||
|
||||
const summaryHeadingCard: LovelaceCardConfig = {
|
||||
type: "heading",
|
||||
heading: hass.localize("ui.panel.lovelace.strategy.home.summaries"),
|
||||
heading_style: "title",
|
||||
};
|
||||
|
||||
// Mobile summary section (visible on small screens only)
|
||||
const mobileSummarySection: LovelaceSectionConfig | undefined =
|
||||
@@ -272,7 +324,7 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
type: "grid",
|
||||
column_span: maxColumns,
|
||||
visibility: [smallScreenCondition],
|
||||
cards: mobileSummaryCards,
|
||||
cards: [summaryHeadingCard, ...mobileSummaryCards],
|
||||
}
|
||||
: undefined;
|
||||
|
||||
@@ -281,25 +333,15 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
sidebarSummaryCards.length > 0
|
||||
? {
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: hass.localize(
|
||||
"ui.panel.lovelace.strategy.home.for_you"
|
||||
),
|
||||
heading_style: "title",
|
||||
},
|
||||
...sidebarSummaryCards,
|
||||
],
|
||||
cards: [summaryHeadingCard, ...sidebarSummaryCards],
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const sections = (
|
||||
[
|
||||
mobileSummarySection,
|
||||
commonControlsSection,
|
||||
...floorsSections,
|
||||
] satisfies (LovelaceSectionRawConfig | undefined)[]
|
||||
[favoritesSection, mobileSummarySection, ...floorsSections] satisfies (
|
||||
| LovelaceSectionRawConfig
|
||||
| undefined
|
||||
)[]
|
||||
).filter(Boolean) as LovelaceSectionRawConfig[];
|
||||
|
||||
return {
|
||||
@@ -319,7 +361,7 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
sections: [sidebarSection],
|
||||
content_label: hass.localize("ui.panel.lovelace.strategy.home.home"),
|
||||
sidebar_label: hass.localize(
|
||||
"ui.panel.lovelace.strategy.home.for_you"
|
||||
"ui.panel.lovelace.strategy.home.summaries"
|
||||
),
|
||||
visibility: [largeScreenCondition],
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { LovelaceSectionConfig } from "../../../../data/lovelace/config/sec
|
||||
import { getCommonControlUsagePrediction } from "../../../../data/usage_prediction";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { HeadingCardConfig, TileCardConfig } from "../../cards/types";
|
||||
import type { Condition } from "../../common/validate-condition";
|
||||
|
||||
const DEFAULT_LIMIT = 8;
|
||||
|
||||
@@ -16,6 +17,7 @@ export interface CommonControlSectionStrategyConfig {
|
||||
exclude_entities?: string[];
|
||||
include_entities?: string[];
|
||||
hide_empty?: boolean;
|
||||
title_visibilty?: Condition[];
|
||||
}
|
||||
|
||||
@customElement("common-controls-section-strategy")
|
||||
@@ -34,6 +36,7 @@ export class CommonControlsSectionStrategy extends ReactiveElement {
|
||||
type: "heading",
|
||||
heading: config.title,
|
||||
icon: config.icon,
|
||||
visibility: config.title_visibilty,
|
||||
} satisfies HeadingCardConfig);
|
||||
}
|
||||
|
||||
|
||||
@@ -921,9 +921,13 @@
|
||||
"default": "default",
|
||||
"state": "State color",
|
||||
"none": "No color",
|
||||
"custom_color": "Custom color",
|
||||
"no_colors_found": "No matching colors found",
|
||||
"colors": {
|
||||
"primary": "Primary",
|
||||
"accent": "Accent",
|
||||
"primary-text": "Main text",
|
||||
"secondary-text": "Secondary text",
|
||||
"disabled": "Disabled",
|
||||
"inactive": "Inactive",
|
||||
"red": "Red",
|
||||
@@ -7173,6 +7177,7 @@
|
||||
"home": {
|
||||
"summary_list": {
|
||||
"media_players": "Media players",
|
||||
"other_devices": "Other devices"
|
||||
"weather": "Weather",
|
||||
"energy": "Today's energy"
|
||||
},
|
||||
@@ -7180,6 +7185,7 @@
|
||||
"summaries": "Summaries",
|
||||
"areas": "Areas",
|
||||
"other_areas": "Other areas",
|
||||
"devices": "Devices",
|
||||
"unamed_device": "Unnamed device",
|
||||
"others": "Others",
|
||||
"scenes": "Scenes",
|
||||
@@ -7207,6 +7213,10 @@
|
||||
"home_media_players": {
|
||||
"media_players": "Media players",
|
||||
"other_media_players": "Other media players"
|
||||
},
|
||||
"other_devices": {
|
||||
"helpers": "Helpers",
|
||||
"entities": "Entities"
|
||||
}
|
||||
},
|
||||
"cards": {
|
||||
|
||||
208
yarn.lock
208
yarn.lock
@@ -1696,15 +1696,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/ecma402-abstract@npm:3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "@formatjs/ecma402-abstract@npm:3.0.2"
|
||||
"@formatjs/ecma402-abstract@npm:3.0.3":
|
||||
version: 3.0.3
|
||||
resolution: "@formatjs/ecma402-abstract@npm:3.0.3"
|
||||
dependencies:
|
||||
"@formatjs/fast-memoize": "npm:3.0.1"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.2"
|
||||
decimal.js: "npm:^10.4.3"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/a0e5838b141189331bdf63d36b101b98e3f78465813acf41d7a42c7f0733ba4e72815a7d5258bb7b97fcd455e0721ff14fc8e9478b33385f73ef460ff6b33d22
|
||||
checksum: 10/134192d5ee45e215b3b61d06849f4f311948629417cd57aa4eb26bd8ca8c13d39660b2d3d46797a83f811c877a86d691ca58969fe889be59241782a59b638db1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1717,100 +1717,100 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/icu-messageformat-parser@npm:3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "@formatjs/icu-messageformat-parser@npm:3.0.2"
|
||||
"@formatjs/icu-messageformat-parser@npm:3.0.4":
|
||||
version: 3.0.4
|
||||
resolution: "@formatjs/icu-messageformat-parser@npm:3.0.4"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.2"
|
||||
"@formatjs/icu-skeleton-parser": "npm:2.0.2"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.3"
|
||||
"@formatjs/icu-skeleton-parser": "npm:2.0.3"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/457d04de7978f3b7a5504cad53bf61a79e6dcce482261699162233e006726dc22557681a676d449297962c511cb102b1cc4cfd9d3710135cbda7f8b57bd8f630
|
||||
checksum: 10/87bc0504a07b536a17f2ef7fe61529352b67b42f7e9253441120bcf105958747315031a15939791487358198d8790a85cb112c956216bbe07d6e2f284a78279e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/icu-skeleton-parser@npm:2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "@formatjs/icu-skeleton-parser@npm:2.0.2"
|
||||
"@formatjs/icu-skeleton-parser@npm:2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "@formatjs/icu-skeleton-parser@npm:2.0.3"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.2"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.3"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/d153fe022ca7ac11e216dfac3297e20387269711689b5c2da8c1f046883e7805bbfd0e76fdf54ac1346a7d6c7283eb455c0d7a23e8c90b8d2db7a1bb0da846ce
|
||||
checksum: 10/a402b4cc6765a718ad2b83bb97becc0e17b5cf400d6216e629f1804bd224dbf3a8c04f4a0e3fc5ab4dcf1b449af975d750e003e7958a0eaae8db87b47ac2f971
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-datetimeformat@npm:7.0.2":
|
||||
version: 7.0.2
|
||||
resolution: "@formatjs/intl-datetimeformat@npm:7.0.2"
|
||||
"@formatjs/intl-datetimeformat@npm:7.0.4":
|
||||
version: 7.0.4
|
||||
resolution: "@formatjs/intl-datetimeformat@npm:7.0.4"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.2"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.3"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.2"
|
||||
decimal.js: "npm:^10.4.3"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/7fc86a7d3846ffb73324a75f4cb59e50d5ee0d741db2e211a534c480b70939e92f368a4ed388562924d32abaed4786a33d88be98f2c361f0d5855f10615059e9
|
||||
checksum: 10/f3f5a4de0f6a2e39ba476ab84f9b2c3a2d8dc9e5536659af08db58e6e450bd79eb49d6a9a83df3a83b701953455add0293dbc2f4e5a09793b8d4c0c1ceb63788
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-displaynames@npm:7.0.2":
|
||||
version: 7.0.2
|
||||
resolution: "@formatjs/intl-displaynames@npm:7.0.2"
|
||||
"@formatjs/intl-displaynames@npm:7.0.4":
|
||||
version: 7.0.4
|
||||
resolution: "@formatjs/intl-displaynames@npm:7.0.4"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.2"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.3"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.2"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/ca017e9d09d7fa8c2c783cc76ff7aa0614e32fb937d7eb262610ab76fced4d9c7cbc26f62af259936ac17e42e755367282b86a2a9f396bab293713b5fefe41ed
|
||||
checksum: 10/2273cb9b4a4ef66b14cdc8dfe6707bb4b58e75cd98de3a1998143d3302e164f40238a0e20df382e9fc4d496063f8d605f41829e68e2d8da56106b252f28053a3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-durationformat@npm:0.8.2":
|
||||
version: 0.8.2
|
||||
resolution: "@formatjs/intl-durationformat@npm:0.8.2"
|
||||
"@formatjs/intl-durationformat@npm:0.8.4":
|
||||
version: 0.8.4
|
||||
resolution: "@formatjs/intl-durationformat@npm:0.8.4"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.2"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.3"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.2"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/7f16359e6a56dc9d14a4595557b8fdd4304722e876fa19cf423bfa931232c3d808111b401fa9c45363477d026dada9cb9b22271f2abc7829b84d6ff9b0fb78a4
|
||||
checksum: 10/1eca9dfacf931ce6ca4c1c2e4d7c20f145a09615aa0e530152524645e3f4d31e2238416cdac57f2ea80534f14013193aa070d865dc91cdd2fb5cf191bbe40ac3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-enumerator@npm:2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "@formatjs/intl-enumerator@npm:2.0.2"
|
||||
"@formatjs/intl-enumerator@npm:2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "@formatjs/intl-enumerator@npm:2.0.3"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.2"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.3"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/9ad06c1107b7134ab572b1c8a4bcf70644966b04da32045480f46ba2b05c5d1e5282d7da0b15485685644dea56e3954a38238c4f96656c4efc161ccf701d4ff2
|
||||
checksum: 10/6ab5d123a2b66cd84af8143fe2c7bafcd2492757874b36cb01a78a1810c5de085e103e590b619c3425c6f57e97c968e68334cf95c114f9d7d17766cda7fe56f3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-getcanonicallocales@npm:3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "@formatjs/intl-getcanonicallocales@npm:3.0.2"
|
||||
"@formatjs/intl-getcanonicallocales@npm:3.0.3":
|
||||
version: 3.0.3
|
||||
resolution: "@formatjs/intl-getcanonicallocales@npm:3.0.3"
|
||||
dependencies:
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/1b14f44d7912d27b28f3ea4658040709297a858fbfc6e23a712edccab7aab50a6c243d447a608fd3f8466d146b59ec9c14e1ed9bec090ed79ef230b6cf3a707a
|
||||
checksum: 10/c69c976e742124943831fb196fb5d428bfd6ba74a6fd112edba88f776b258b76c8092180e7f2729608d3b2b588c833c7f1684a669322a2aad8de39992d8faa34
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-listformat@npm:8.0.2":
|
||||
version: 8.0.2
|
||||
resolution: "@formatjs/intl-listformat@npm:8.0.2"
|
||||
"@formatjs/intl-listformat@npm:8.0.4":
|
||||
version: 8.0.4
|
||||
resolution: "@formatjs/intl-listformat@npm:8.0.4"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.2"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.3"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.2"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/982839fa2e9650e53dee03693789e22a64846fa43ac30c263d4121cbfb3591bc7b2f5a13cb412283016bee3f06047238ef4d8bcf0d4d929b540e1e5dfd90ab40
|
||||
checksum: 10/2bc17b9315a30afc1f667ad35475daaf35430093da30280ff531944e088b184085b1cea9e77bb040b7f668d1b4b47d4616c5e1792a097858aa9a436127875c1f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-locale@npm:5.0.2":
|
||||
version: 5.0.2
|
||||
resolution: "@formatjs/intl-locale@npm:5.0.2"
|
||||
"@formatjs/intl-locale@npm:5.0.4":
|
||||
version: 5.0.4
|
||||
resolution: "@formatjs/intl-locale@npm:5.0.4"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.2"
|
||||
"@formatjs/intl-enumerator": "npm:2.0.2"
|
||||
"@formatjs/intl-getcanonicallocales": "npm:3.0.2"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.3"
|
||||
"@formatjs/intl-enumerator": "npm:2.0.3"
|
||||
"@formatjs/intl-getcanonicallocales": "npm:3.0.3"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/cba579c5a5600bd577433da2ee39a772d55e39de065992c54fb2f54ccb712263d7fdf1ae360e7ac31aea2b39794daca32fec7496d9b2e48f4bf6e19b3bd87c8e
|
||||
checksum: 10/e0c32e6718052af6c05c5e08249ac320fc6e8b336f3d61332b1bf8987c9157b0d7e5ee51546a5754b2c7799a65ec25da0a5d9d24798bc64ad58fa9aced1e90d8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1823,38 +1823,38 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-numberformat@npm:9.0.3":
|
||||
version: 9.0.3
|
||||
resolution: "@formatjs/intl-numberformat@npm:9.0.3"
|
||||
"@formatjs/intl-numberformat@npm:9.0.5":
|
||||
version: 9.0.5
|
||||
resolution: "@formatjs/intl-numberformat@npm:9.0.5"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.2"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.3"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.2"
|
||||
decimal.js: "npm:^10.4.3"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/3bc32f76a32974ad51a7b23f8b9a5301f44550f4abfb6ffa3a84a0ad492072a32ab97b1e2dba130cb4a2290640d444dacdaeb8afaad6126bc542d202de22499d
|
||||
checksum: 10/72c6eaae12e270aad4d395fd36e6cfc7201452dd264bdbffbcecddbec34d3e2bfd86b0bd08e4ba04d771b9d1de9956554687405f4daa509e1c560f741aabf399
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-pluralrules@npm:6.0.2":
|
||||
version: 6.0.2
|
||||
resolution: "@formatjs/intl-pluralrules@npm:6.0.2"
|
||||
"@formatjs/intl-pluralrules@npm:6.0.4":
|
||||
version: 6.0.4
|
||||
resolution: "@formatjs/intl-pluralrules@npm:6.0.4"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.2"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.3"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.2"
|
||||
decimal.js: "npm:^10.4.3"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/7fc62240225ce92f6ed5d3ae78f6bb618567f343274fcadae94946692ef7070f887da2fbcba2574d4d664cc0493936f9ae917379a9b83d83653ccaf0c75a1931
|
||||
checksum: 10/da9a89840e3f9164b4fdcc5b3c05adf9a85a7306c748f42f1cb91a01dd1c69e9f724129556f244ce17b348c545cc4c3b6ab7ae6461f31c852155494f2a324704
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-relativetimeformat@npm:12.0.3":
|
||||
version: 12.0.3
|
||||
resolution: "@formatjs/intl-relativetimeformat@npm:12.0.3"
|
||||
"@formatjs/intl-relativetimeformat@npm:12.0.5":
|
||||
version: 12.0.5
|
||||
resolution: "@formatjs/intl-relativetimeformat@npm:12.0.5"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.2"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.3"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.2"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/00f0e6feedeed9f04624c530e943d28d7e1dd3f2086f2a7717f379ffbe231c6c5c0808706f75bceee4881be01314a4f2aa16db1f3b9f4b206487f355abf938aa
|
||||
checksum: 10/ade26ba145dc1ae3c76ce179a8f1f7badca57d9b64f9a235298480219d357ad4422962c4fd48f436fb386766448cef403326199ec6ca2ddecbcb5a3031b6cf37
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2352,10 +2352,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@lokalise/node-api@npm:15.5.0":
|
||||
version: 15.5.0
|
||||
resolution: "@lokalise/node-api@npm:15.5.0"
|
||||
checksum: 10/245e215bc3d4ced54c05e12760ed1f607937f1e285752ad9a87be40b7b34f553a698c960581b0151d13a8bc44e71c86c2bfa930ae889e0b3631d1ff5290e9672
|
||||
"@lokalise/node-api@npm:15.6.0":
|
||||
version: 15.6.0
|
||||
resolution: "@lokalise/node-api@npm:15.6.0"
|
||||
checksum: 10/29d4497c34fe090d53bd3ca3f4f1924159d79624fe4706eac6e71b656e55f54b30cd73063e942ea4f4a45714586364fc0e964f1ad4fef4dd7d43af96da19dbb2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4166,16 +4166,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sinonjs/fake-timers@npm:^13.0.5":
|
||||
version: 13.0.5
|
||||
resolution: "@sinonjs/fake-timers@npm:13.0.5"
|
||||
"@sinonjs/fake-timers@npm:^15.1.0":
|
||||
version: 15.1.0
|
||||
resolution: "@sinonjs/fake-timers@npm:15.1.0"
|
||||
dependencies:
|
||||
"@sinonjs/commons": "npm:^3.0.1"
|
||||
checksum: 10/11ee417968fc4dce1896ab332ac13f353866075a9d2a88ed1f6258f17cc4f7d93e66031b51fcddb8c203aa4d53fd980b0ae18aba06269f4682164878a992ec3f
|
||||
checksum: 10/f675a7c8d5f48aba52de57e3ddccb3c0b9346d11e15e8994f6293942c7ea54a7182b485f7c3078106fef0feef1e5b289d00aa00ef0dddadf3f91e9c8f338e8e8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sinonjs/samsam@npm:^8.0.1":
|
||||
"@sinonjs/samsam@npm:^8.0.3":
|
||||
version: 8.0.3
|
||||
resolution: "@sinonjs/samsam@npm:8.0.3"
|
||||
dependencies:
|
||||
@@ -7102,10 +7102,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"diff@npm:^7.0.0":
|
||||
version: 7.0.0
|
||||
resolution: "diff@npm:7.0.0"
|
||||
checksum: 10/e9b8e48d054c9c0c093c65ce8e2637af94b35f2427001607b14e5e0589e534ea3413a7f91ebe6d7c5a1494ace49cb7c7c3972f442ddd96a4767ff091999a082e
|
||||
"diff@npm:^8.0.2":
|
||||
version: 8.0.2
|
||||
resolution: "diff@npm:8.0.2"
|
||||
checksum: 10/82a2120d3418f97822e17a6044ccd4b99a91e26e145e8698353673d7146bd2d092bbebb79c112aae7badc7b9c526f9098cbe342f96174feb6beabdd2587b3c42
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9000,15 +9000,15 @@ __metadata:
|
||||
"@codemirror/view": "npm:6.39.4"
|
||||
"@date-fns/tz": "npm:1.4.1"
|
||||
"@egjs/hammerjs": "npm:2.0.17"
|
||||
"@formatjs/intl-datetimeformat": "npm:7.0.2"
|
||||
"@formatjs/intl-displaynames": "npm:7.0.2"
|
||||
"@formatjs/intl-durationformat": "npm:0.8.2"
|
||||
"@formatjs/intl-getcanonicallocales": "npm:3.0.2"
|
||||
"@formatjs/intl-listformat": "npm:8.0.2"
|
||||
"@formatjs/intl-locale": "npm:5.0.2"
|
||||
"@formatjs/intl-numberformat": "npm:9.0.3"
|
||||
"@formatjs/intl-pluralrules": "npm:6.0.2"
|
||||
"@formatjs/intl-relativetimeformat": "npm:12.0.3"
|
||||
"@formatjs/intl-datetimeformat": "npm:7.0.4"
|
||||
"@formatjs/intl-displaynames": "npm:7.0.4"
|
||||
"@formatjs/intl-durationformat": "npm:0.8.4"
|
||||
"@formatjs/intl-getcanonicallocales": "npm:3.0.3"
|
||||
"@formatjs/intl-listformat": "npm:8.0.4"
|
||||
"@formatjs/intl-locale": "npm:5.0.4"
|
||||
"@formatjs/intl-numberformat": "npm:9.0.5"
|
||||
"@formatjs/intl-pluralrules": "npm:6.0.4"
|
||||
"@formatjs/intl-relativetimeformat": "npm:12.0.5"
|
||||
"@fullcalendar/core": "npm:6.1.19"
|
||||
"@fullcalendar/daygrid": "npm:6.1.19"
|
||||
"@fullcalendar/interaction": "npm:6.1.19"
|
||||
@@ -9022,7 +9022,7 @@ __metadata:
|
||||
"@lit-labs/virtualizer": "npm:2.1.1"
|
||||
"@lit/context": "npm:1.1.6"
|
||||
"@lit/reactive-element": "npm:2.1.1"
|
||||
"@lokalise/node-api": "npm:15.5.0"
|
||||
"@lokalise/node-api": "npm:15.6.0"
|
||||
"@material/chips": "npm:=14.0.0-canary.53b3cad2f.0"
|
||||
"@material/data-table": "npm:=14.0.0-canary.53b3cad2f.0"
|
||||
"@material/mwc-base": "npm:0.27.0"
|
||||
@@ -9123,7 +9123,7 @@ __metadata:
|
||||
html-minifier-terser: "npm:7.2.0"
|
||||
husky: "npm:9.1.7"
|
||||
idb-keyval: "npm:6.2.2"
|
||||
intl-messageformat: "npm:11.0.2"
|
||||
intl-messageformat: "npm:11.0.4"
|
||||
js-yaml: "npm:4.1.1"
|
||||
jsdom: "npm:27.3.0"
|
||||
jszip: "npm:3.10.1"
|
||||
@@ -9151,7 +9151,7 @@ __metadata:
|
||||
rrule: "npm:2.8.1"
|
||||
rspack-manifest-plugin: "npm:5.2.0"
|
||||
serve: "npm:14.2.5"
|
||||
sinon: "npm:21.0.0"
|
||||
sinon: "npm:21.0.1"
|
||||
sortablejs: "patch:sortablejs@npm%3A1.15.6#~/.yarn/patches/sortablejs-npm-1.15.6-3235a8f83b.patch"
|
||||
stacktrace-js: "npm:2.0.2"
|
||||
superstruct: "npm:2.0.2"
|
||||
@@ -9530,15 +9530,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"intl-messageformat@npm:11.0.2":
|
||||
version: 11.0.2
|
||||
resolution: "intl-messageformat@npm:11.0.2"
|
||||
"intl-messageformat@npm:11.0.4":
|
||||
version: 11.0.4
|
||||
resolution: "intl-messageformat@npm:11.0.4"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.2"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.3"
|
||||
"@formatjs/fast-memoize": "npm:3.0.1"
|
||||
"@formatjs/icu-messageformat-parser": "npm:3.0.2"
|
||||
"@formatjs/icu-messageformat-parser": "npm:3.0.4"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/ea21a2cd6afd40c26ca0e675f2271b94f223c1bc89a590f69061e059bf92ed42ecd88f3e8332319f808a16f6ffe5ae82d02b1f314e82e073e510c0586dd0c6e1
|
||||
checksum: 10/b7ed6afd7dbb6cc755b12b20d0cf9d506d8530947fa0e631e44fac40a45ee59bdcddea4c1c241de133c51c343b2cae941654f3fbd6cb3f9fbd1aedb59a82a9ce
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -12932,16 +12932,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sinon@npm:21.0.0":
|
||||
version: 21.0.0
|
||||
resolution: "sinon@npm:21.0.0"
|
||||
"sinon@npm:21.0.1":
|
||||
version: 21.0.1
|
||||
resolution: "sinon@npm:21.0.1"
|
||||
dependencies:
|
||||
"@sinonjs/commons": "npm:^3.0.1"
|
||||
"@sinonjs/fake-timers": "npm:^13.0.5"
|
||||
"@sinonjs/samsam": "npm:^8.0.1"
|
||||
diff: "npm:^7.0.0"
|
||||
"@sinonjs/fake-timers": "npm:^15.1.0"
|
||||
"@sinonjs/samsam": "npm:^8.0.3"
|
||||
diff: "npm:^8.0.2"
|
||||
supports-color: "npm:^7.2.0"
|
||||
checksum: 10/b460e1cde56fa34ecbebc216522667fa3985444020dc70508177f85d127e6b9a27184c4b3146c575b30ec3091bde508b1373fad306b1e808037a37035e62065d
|
||||
checksum: 10/b7cc06de384a964636ccdfca56ec0d3d7288608c41ffe1654222ac6de9a40f87df1ddc546abff8684e998d0bf0b152ac1f600fe8d99b31a351725ac37ca5b9a0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user