mirror of
https://github.com/home-assistant/frontend.git
synced 2026-01-13 02:37:29 +00:00
Compare commits
9 Commits
checkbox-i
...
clock-date
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46d48bf1af | ||
|
|
572dea2b0c | ||
|
|
e34db36098 | ||
|
|
3a6109e147 | ||
|
|
ce1e89e716 | ||
|
|
75f9baf6ef | ||
|
|
d4138dced9 | ||
|
|
3dc34cb1cc | ||
|
|
858eeb7960 |
@@ -213,9 +213,7 @@ const createRspackConfig = ({
|
||||
"lit/directives/join$": "lit/directives/join.js",
|
||||
"lit/directives/repeat$": "lit/directives/repeat.js",
|
||||
"lit/directives/live$": "lit/directives/live.js",
|
||||
"lit/directives/keyed$": latestBuild
|
||||
? "lit/directives/keyed.js"
|
||||
: path.resolve(__dirname, "../src/common/lit/keyed-es5.ts"),
|
||||
"lit/directives/keyed$": "lit/directives/keyed.js",
|
||||
"lit/polyfill-support$": "lit/polyfill-support.js",
|
||||
"@lit-labs/virtualizer/layouts/grid":
|
||||
"@lit-labs/virtualizer/layouts/grid.js",
|
||||
|
||||
36
package.json
36
package.json
@@ -34,18 +34,18 @@
|
||||
"@codemirror/legacy-modes": "6.5.2",
|
||||
"@codemirror/search": "6.5.11",
|
||||
"@codemirror/state": "6.5.3",
|
||||
"@codemirror/view": "6.39.9",
|
||||
"@codemirror/view": "6.39.8",
|
||||
"@date-fns/tz": "1.4.1",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "7.1.2",
|
||||
"@formatjs/intl-displaynames": "7.1.2",
|
||||
"@formatjs/intl-durationformat": "0.9.2",
|
||||
"@formatjs/intl-getcanonicallocales": "3.1.2",
|
||||
"@formatjs/intl-listformat": "8.1.2",
|
||||
"@formatjs/intl-locale": "5.1.2",
|
||||
"@formatjs/intl-numberformat": "9.1.2",
|
||||
"@formatjs/intl-pluralrules": "6.1.2",
|
||||
"@formatjs/intl-relativetimeformat": "12.1.2",
|
||||
"@formatjs/intl-datetimeformat": "7.1.1",
|
||||
"@formatjs/intl-displaynames": "7.1.1",
|
||||
"@formatjs/intl-durationformat": "0.9.1",
|
||||
"@formatjs/intl-getcanonicallocales": "3.1.1",
|
||||
"@formatjs/intl-listformat": "8.1.1",
|
||||
"@formatjs/intl-locale": "5.1.1",
|
||||
"@formatjs/intl-numberformat": "9.1.1",
|
||||
"@formatjs/intl-pluralrules": "6.1.1",
|
||||
"@formatjs/intl-relativetimeformat": "12.1.1",
|
||||
"@fullcalendar/core": "6.1.20",
|
||||
"@fullcalendar/daygrid": "6.1.20",
|
||||
"@fullcalendar/interaction": "6.1.20",
|
||||
@@ -112,13 +112,13 @@
|
||||
"hls.js": "1.6.15",
|
||||
"home-assistant-js-websocket": "9.6.0",
|
||||
"idb-keyval": "6.2.2",
|
||||
"intl-messageformat": "11.0.9",
|
||||
"intl-messageformat": "11.0.8",
|
||||
"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",
|
||||
"leaflet.markercluster": "1.5.3",
|
||||
"lit": "3.3.2",
|
||||
"lit-html": "3.3.2",
|
||||
"lit": "3.3.1",
|
||||
"lit-html": "3.3.1",
|
||||
"luxon": "3.7.2",
|
||||
"marked": "17.0.1",
|
||||
"memoize-one": "6.0.0",
|
||||
@@ -150,13 +150,13 @@
|
||||
"@babel/helper-define-polyfill-provider": "0.6.5",
|
||||
"@babel/plugin-transform-runtime": "7.28.5",
|
||||
"@babel/preset-env": "7.28.5",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.21.8",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.21.7",
|
||||
"@lokalise/node-api": "15.6.0",
|
||||
"@octokit/auth-oauth-device": "8.0.3",
|
||||
"@octokit/plugin-retry": "8.0.3",
|
||||
"@octokit/rest": "22.0.1",
|
||||
"@rsdoctor/rspack-plugin": "1.4.0",
|
||||
"@rspack/core": "1.7.1",
|
||||
"@rspack/core": "1.7.0",
|
||||
"@rspack/dev-server": "1.1.5",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.25",
|
||||
@@ -215,7 +215,7 @@
|
||||
"terser-webpack-plugin": "5.3.16",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.52.0",
|
||||
"typescript-eslint": "8.51.0",
|
||||
"vite-tsconfig-paths": "6.0.3",
|
||||
"vitest": "4.0.16",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
@@ -224,8 +224,8 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"@material/mwc-button@^0.25.3": "^0.27.0",
|
||||
"lit": "3.3.2",
|
||||
"lit-html": "3.3.2",
|
||||
"lit": "3.3.1",
|
||||
"lit-html": "3.3.1",
|
||||
"clean-css": "5.3.3",
|
||||
"@lit/reactive-element": "2.1.2",
|
||||
"@fullcalendar/daygrid": "6.1.20",
|
||||
|
||||
@@ -38,11 +38,13 @@ export class HaAuthFormString extends HaFormString {
|
||||
}
|
||||
</style>
|
||||
<ha-auth-textfield
|
||||
.type=${!this.isPassword
|
||||
.type=${
|
||||
!this.isPassword
|
||||
? this.stringType
|
||||
: this.unmaskedPassword
|
||||
? "text"
|
||||
: "password"}
|
||||
: "password"
|
||||
}
|
||||
.label=${this.label}
|
||||
.value=${this.data || ""}
|
||||
.helper=${this.helper}
|
||||
@@ -53,17 +55,18 @@ export class HaAuthFormString extends HaFormString {
|
||||
.name=${this.schema.name}
|
||||
.autocomplete=${this.schema.autocomplete}
|
||||
?autofocus=${this.schema.autofocus}
|
||||
.suffix=${this.isPassword
|
||||
? // reserve some space for the icon.
|
||||
html`<div style="width: 24px"></div>`
|
||||
: this.schema.description?.suffix}
|
||||
.validationMessage=${this.schema.required
|
||||
? this.localize?.("ui.panel.page-authorize.form.error_required")
|
||||
: undefined}
|
||||
.suffix=${
|
||||
this.isPassword
|
||||
? // reserve some space for the icon.
|
||||
html`<div style="width: 24px"></div>`
|
||||
: this.schema.description?.suffix
|
||||
}
|
||||
.validationMessage=${this.schema.required ? this.localize?.("ui.panel.page-authorize.form.error_required") : undefined}
|
||||
@input=${this._valueChanged}
|
||||
@change=${this._valueChanged}
|
||||
></ha-auth-textfield>
|
||||
${this.renderIcon()}
|
||||
></ha-auth-textfield>
|
||||
${this.renderIcon()}
|
||||
</ha-auth-textfield>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export const generateColorPalette = (
|
||||
}
|
||||
|
||||
return steps.map((step) => {
|
||||
const name = `ha-color-${label}-${step}`;
|
||||
const name = `color-${label}-${step}`;
|
||||
|
||||
// Base color at 50%
|
||||
if (step === 50) {
|
||||
|
||||
@@ -93,8 +93,8 @@ export const calcDateRange = (
|
||||
];
|
||||
case "now-12m":
|
||||
return [
|
||||
calcDate(today, subMonths, hass.locale, hass.config, 12),
|
||||
calcDate(today, subMonths, hass.locale, hass.config, 0),
|
||||
calcDate(subMonths(today, 12), startOfMonth, hass.locale, hass.config),
|
||||
calcDate(subMonths(today, 1), endOfMonth, hass.locale, hass.config),
|
||||
];
|
||||
case "now-1h":
|
||||
return [
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* ES5-compatible implementation of the keyed directive.
|
||||
* Based on lit-html's keyed directive but written to avoid ES5 minification issues.
|
||||
*
|
||||
* This implementation avoids parameter destructuring in the update() method,
|
||||
* which causes Terser with ecma: 5 to generate invalid references like `_k`.
|
||||
*
|
||||
* Used only for ES5 builds (legacy browsers). Modern builds use the original
|
||||
* lit-html keyed directive.
|
||||
*
|
||||
* @see https://github.com/home-assistant/frontend/issues/28732
|
||||
*/
|
||||
// eslint-disable-next-line import/extensions
|
||||
import { directive, Directive } from "lit-html/directive.js";
|
||||
// eslint-disable-next-line import/extensions
|
||||
import { setCommittedValue } from "lit-html/directive-helpers.js";
|
||||
// eslint-disable-next-line lit/no-legacy-imports
|
||||
import { nothing } from "lit-html";
|
||||
// eslint-disable-next-line import/extensions
|
||||
import type { Part } from "lit-html/directive.js";
|
||||
|
||||
class KeyedES5 extends Directive {
|
||||
private _key: unknown = nothing;
|
||||
|
||||
render(k: unknown, v: unknown) {
|
||||
this._key = k;
|
||||
return v;
|
||||
}
|
||||
|
||||
update(part: unknown, args: [unknown, unknown]) {
|
||||
const k = args[0];
|
||||
const v = args[1];
|
||||
if (k !== this._key) {
|
||||
// Clear the part before returning a value. The one-arg form of
|
||||
// setCommittedValue sets the value to a sentinel which forces a
|
||||
// commit the next render.
|
||||
setCommittedValue(part as Part);
|
||||
this._key = k;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a renderable value with a unique key. When the key changes, the
|
||||
* previous DOM is removed and disposed before rendering the next value, even
|
||||
* if the value - such as a template - is the same.
|
||||
*
|
||||
* This is useful for forcing re-renders of stateful components, or working
|
||||
* with code that expects new data to generate new HTML elements, such as some
|
||||
* animation techniques.
|
||||
*/
|
||||
export const keyed = directive(KeyedES5);
|
||||
@@ -21,7 +21,6 @@ import { measureTextWidth } from "../../util/text";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { CLIMATE_HVAC_ACTION_TO_MODE } from "../../data/climate";
|
||||
import { blankBeforeUnit } from "../../common/translations/blank_before_unit";
|
||||
import { filterXSS } from "../../common/util/xss";
|
||||
|
||||
const safeParseFloat = (value) => {
|
||||
const parsed = parseFloat(value);
|
||||
@@ -185,7 +184,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
}
|
||||
|
||||
if (param.seriesName) {
|
||||
return `${param.marker} ${filterXSS(param.seriesName)}: ${value}`;
|
||||
return `${param.marker} ${param.seriesName}: ${value}`;
|
||||
}
|
||||
return `${param.marker} ${value}`;
|
||||
})
|
||||
|
||||
@@ -18,7 +18,6 @@ import type { HomeAssistant } from "../../types";
|
||||
import { brandsUrl } from "../../util/brands-url";
|
||||
import "../ha-generic-picker";
|
||||
import type { HaGenericPicker } from "../ha-generic-picker";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity/entity";
|
||||
|
||||
export type HaDevicePickerDeviceFilterFunc = (
|
||||
device: DeviceRegistryEntry
|
||||
@@ -95,30 +94,7 @@ export class HaDevicePicker extends LitElement {
|
||||
|
||||
@state() private _configEntryLookup: Record<string, ConfigEntry> = {};
|
||||
|
||||
private _getDevicesMemoized = memoizeOne(
|
||||
(
|
||||
_devices: HomeAssistant["devices"],
|
||||
configEntryLookup: Record<string, ConfigEntry>,
|
||||
includeDomains?: string[],
|
||||
excludeDomains?: string[],
|
||||
includeDeviceClasses?: string[],
|
||||
deviceFilter?: HaDevicePickerDeviceFilterFunc,
|
||||
entityFilter?: HaEntityPickerEntityFilterFunc,
|
||||
excludeDevices?: string[],
|
||||
value?: string
|
||||
) =>
|
||||
getDevices(
|
||||
this.hass,
|
||||
configEntryLookup,
|
||||
includeDomains,
|
||||
excludeDomains,
|
||||
includeDeviceClasses,
|
||||
deviceFilter,
|
||||
entityFilter,
|
||||
excludeDevices,
|
||||
value
|
||||
)
|
||||
);
|
||||
private _getDevicesMemoized = memoizeOne(getDevices);
|
||||
|
||||
protected firstUpdated(_changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(_changedProperties);
|
||||
@@ -134,7 +110,7 @@ export class HaDevicePicker extends LitElement {
|
||||
|
||||
private _getItems = () =>
|
||||
this._getDevicesMemoized(
|
||||
this.hass.devices,
|
||||
this.hass,
|
||||
this._configEntryLookup,
|
||||
this.includeDomains,
|
||||
this.excludeDomains,
|
||||
|
||||
@@ -275,11 +275,6 @@ export class HaEntityNamePicker extends LitElement {
|
||||
this._editIndex = idx;
|
||||
await this.updateComplete;
|
||||
await this._picker?.open();
|
||||
const value = this._items[idx];
|
||||
// Pre-fill the field value when editing a text item
|
||||
if (value.type === "text" && value.text) {
|
||||
this._picker?.setFieldValue(value.text);
|
||||
}
|
||||
}
|
||||
|
||||
private get _items(): EntityNameItem[] {
|
||||
|
||||
@@ -143,19 +143,17 @@ export class HaEntityToggle extends LitElement {
|
||||
// Optimistic update.
|
||||
this._isOn = turnOn;
|
||||
|
||||
try {
|
||||
await this.hass.callService(serviceDomain, service, {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
} finally {
|
||||
setTimeout(async () => {
|
||||
// If after 2 seconds we have not received a state update
|
||||
// reset the switch to it's original state.
|
||||
if (this.stateObj === currentState) {
|
||||
this._isOn = isOn(this.stateObj);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
await this.hass.callService(serviceDomain, service, {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
|
||||
setTimeout(async () => {
|
||||
// If after 2 seconds we have not received a state update
|
||||
// reset the switch to it's original state.
|
||||
if (this.stateObj === currentState) {
|
||||
this._isOn = isOn(this.stateObj);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
|
||||
@@ -141,7 +141,6 @@ export class HaStatisticPicker extends LitElement {
|
||||
|
||||
private async _getStatisticIds() {
|
||||
this.statisticIds = await getStatisticIds(this.hass, this.statisticTypes);
|
||||
this._picker?.requestUpdate();
|
||||
}
|
||||
|
||||
private _getItems = () =>
|
||||
@@ -178,9 +177,9 @@ export class HaStatisticPicker extends LitElement {
|
||||
entitiesOnly?: boolean,
|
||||
excludeStatistics?: string[],
|
||||
value?: string
|
||||
): StatisticComboBoxItem[] | undefined => {
|
||||
): StatisticComboBoxItem[] => {
|
||||
if (!statisticIds) {
|
||||
return undefined;
|
||||
return [];
|
||||
}
|
||||
|
||||
if (includeStatisticsUnitOfMeasurement) {
|
||||
|
||||
@@ -174,14 +174,12 @@ export class HaAutomationRow extends LitElement {
|
||||
}
|
||||
::slotted([slot="header"]) {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
margin: 0 var(--ha-space-3);
|
||||
}
|
||||
.icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
:host([sort-selected]) .row {
|
||||
outline: solid;
|
||||
|
||||
@@ -51,10 +51,7 @@ export class HaCard extends LitElement {
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
}
|
||||
|
||||
:host
|
||||
::slotted(
|
||||
.card-content:not(:nth-child(1 of .card-content, .card-header))
|
||||
),
|
||||
:host ::slotted(.card-content:not(:first-child)),
|
||||
slot:not(:first-child)::slotted(.card-content) {
|
||||
padding-top: 0;
|
||||
margin-top: calc(var(--ha-space-2) * -1);
|
||||
|
||||
24
src/components/ha-combo-box-textfield.ts
Normal file
24
src/components/ha-combo-box-textfield.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { HaTextField } from "./ha-textfield";
|
||||
|
||||
@customElement("ha-combo-box-textfield")
|
||||
export class HaComboBoxTextField extends HaTextField {
|
||||
@property({ type: Boolean, attribute: "force-blank-value" })
|
||||
public forceBlankValue = false;
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
if (changedProps.has("value") || changedProps.has("forceBlankValue")) {
|
||||
if (this.forceBlankValue && this.value) {
|
||||
this.value = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-combo-box-textfield": HaComboBoxTextField;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
import { mdiMinusThick, mdiPlusThick } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import "./ha-base-time-input";
|
||||
import type { TimeChangedEvent } from "./ha-base-time-input";
|
||||
import "./ha-button-toggle-group";
|
||||
|
||||
export interface HaDurationData {
|
||||
days?: number;
|
||||
@@ -15,8 +13,6 @@ export interface HaDurationData {
|
||||
milliseconds?: number;
|
||||
}
|
||||
|
||||
const FIELDS = ["milliseconds", "seconds", "minutes", "hours", "days"];
|
||||
|
||||
@customElement("ha-duration-input")
|
||||
class HaDurationInput extends LitElement {
|
||||
@property({ attribute: false }) public data?: HaDurationData;
|
||||
@@ -33,80 +29,41 @@ class HaDurationInput extends LitElement {
|
||||
@property({ attribute: "enable-day", type: Boolean })
|
||||
public enableDay = false;
|
||||
|
||||
@property({ attribute: "allow-negative", type: Boolean })
|
||||
public allowNegative = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
private _toggleNegative = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="row">
|
||||
${this.allowNegative
|
||||
? html`
|
||||
<ha-button-toggle-group
|
||||
size="small"
|
||||
.buttons=${[
|
||||
{ label: "+", iconPath: mdiPlusThick, value: "+" },
|
||||
{ label: "-", iconPath: mdiMinusThick, value: "-" },
|
||||
]}
|
||||
.active=${this._negative ? "-" : "+"}
|
||||
@value-changed=${this._negativeChanged}
|
||||
></ha-button-toggle-group>
|
||||
`
|
||||
: nothing}
|
||||
<ha-base-time-input
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.required=${this.required}
|
||||
.clearable=${!this.required && this.data !== undefined}
|
||||
.autoValidate=${this.required}
|
||||
.disabled=${this.disabled}
|
||||
errorMessage="Required"
|
||||
enable-second
|
||||
.enableMillisecond=${this.enableMillisecond}
|
||||
.enableDay=${this.enableDay}
|
||||
format="24"
|
||||
.days=${this._days}
|
||||
.hours=${this._hours}
|
||||
.minutes=${this._minutes}
|
||||
.seconds=${this._seconds}
|
||||
.milliseconds=${this._milliseconds}
|
||||
@value-changed=${this._durationChanged}
|
||||
no-hours-limit
|
||||
day-label="dd"
|
||||
hour-label="hh"
|
||||
min-label="mm"
|
||||
sec-label="ss"
|
||||
ms-label="ms"
|
||||
></ha-base-time-input>
|
||||
</div>
|
||||
<ha-base-time-input
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.required=${this.required}
|
||||
.clearable=${!this.required && this.data !== undefined}
|
||||
.autoValidate=${this.required}
|
||||
.disabled=${this.disabled}
|
||||
errorMessage="Required"
|
||||
enable-second
|
||||
.enableMillisecond=${this.enableMillisecond}
|
||||
.enableDay=${this.enableDay}
|
||||
format="24"
|
||||
.days=${this._days}
|
||||
.hours=${this._hours}
|
||||
.minutes=${this._minutes}
|
||||
.seconds=${this._seconds}
|
||||
.milliseconds=${this._milliseconds}
|
||||
@value-changed=${this._durationChanged}
|
||||
no-hours-limit
|
||||
day-label="dd"
|
||||
hour-label="hh"
|
||||
min-label="mm"
|
||||
sec-label="ss"
|
||||
ms-label="ms"
|
||||
></ha-base-time-input>
|
||||
`;
|
||||
}
|
||||
|
||||
private get _negative() {
|
||||
return (
|
||||
this._toggleNegative ||
|
||||
(this.data?.days
|
||||
? this.data.days < 0
|
||||
: this.data?.hours
|
||||
? this.data.hours < 0
|
||||
: this.data?.minutes
|
||||
? this.data.minutes < 0
|
||||
: this.data?.seconds
|
||||
? this.data.seconds < 0
|
||||
: this.data?.milliseconds
|
||||
? this.data.milliseconds < 0
|
||||
: false)
|
||||
);
|
||||
}
|
||||
|
||||
private get _days() {
|
||||
return this.data?.days
|
||||
? this.allowNegative
|
||||
? Math.abs(Number(this.data.days))
|
||||
: Number(this.data.days)
|
||||
? Number(this.data.days)
|
||||
: this.required || this.data
|
||||
? 0
|
||||
: NaN;
|
||||
@@ -114,9 +71,7 @@ class HaDurationInput extends LitElement {
|
||||
|
||||
private get _hours() {
|
||||
return this.data?.hours
|
||||
? this.allowNegative
|
||||
? Math.abs(Number(this.data.hours))
|
||||
: Number(this.data.hours)
|
||||
? Number(this.data.hours)
|
||||
: this.required || this.data
|
||||
? 0
|
||||
: NaN;
|
||||
@@ -124,9 +79,7 @@ class HaDurationInput extends LitElement {
|
||||
|
||||
private get _minutes() {
|
||||
return this.data?.minutes
|
||||
? this.allowNegative
|
||||
? Math.abs(Number(this.data.minutes))
|
||||
: Number(this.data.minutes)
|
||||
? Number(this.data.minutes)
|
||||
: this.required || this.data
|
||||
? 0
|
||||
: NaN;
|
||||
@@ -134,9 +87,7 @@ class HaDurationInput extends LitElement {
|
||||
|
||||
private get _seconds() {
|
||||
return this.data?.seconds
|
||||
? this.allowNegative
|
||||
? Math.abs(Number(this.data.seconds))
|
||||
: Number(this.data.seconds)
|
||||
? Number(this.data.seconds)
|
||||
: this.required || this.data
|
||||
? 0
|
||||
: NaN;
|
||||
@@ -144,9 +95,7 @@ class HaDurationInput extends LitElement {
|
||||
|
||||
private get _milliseconds() {
|
||||
return this.data?.milliseconds
|
||||
? this.allowNegative
|
||||
? Math.abs(Number(this.data.milliseconds))
|
||||
: Number(this.data.milliseconds)
|
||||
? Number(this.data.milliseconds)
|
||||
: this.required || this.data
|
||||
? 0
|
||||
: NaN;
|
||||
@@ -164,14 +113,6 @@ class HaDurationInput extends LitElement {
|
||||
if ("days" in value) value.days ||= 0;
|
||||
if ("milliseconds" in value) value.milliseconds ||= 0;
|
||||
|
||||
if (this.allowNegative) {
|
||||
FIELDS.forEach((t) => {
|
||||
if (value[t]) {
|
||||
value[t] = Math.abs(value[t]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.enableMillisecond && !value.milliseconds) {
|
||||
// @ts-ignore
|
||||
delete value.milliseconds;
|
||||
@@ -194,47 +135,12 @@ class HaDurationInput extends LitElement {
|
||||
value.days = (value.days ?? 0) + Math.floor(value.hours / 24);
|
||||
value.hours %= 24;
|
||||
}
|
||||
|
||||
if (this._negative) {
|
||||
FIELDS.forEach((t) => {
|
||||
if (value[t]) {
|
||||
value[t] = -Math.abs(value[t]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
private _negativeChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
const negative = (ev.detail?.value || ev.target.value) === "-";
|
||||
this._toggleNegative = negative;
|
||||
const value = this.data;
|
||||
if (value) {
|
||||
FIELDS.forEach((t) => {
|
||||
if (value[t]) {
|
||||
value[t] = negative ? -Math.abs(value[t]) : Math.abs(value[t]);
|
||||
}
|
||||
});
|
||||
fireEvent(this, "value-changed", {
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
ha-button-toggle-group {
|
||||
margin: var(--ha-space-2);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
import type { SelectedDetail } from "@material/mwc-list";
|
||||
import { mdiFilterVariantRemove } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-check-list-item";
|
||||
import "./ha-expansion-panel";
|
||||
import "./ha-icon";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-label";
|
||||
import "./ha-list";
|
||||
import "./ha-list-item";
|
||||
import "./search-input-outlined";
|
||||
import "./voice-assistant-brand-icon";
|
||||
import { voiceAssistants } from "../data/expose";
|
||||
import "../panels/config/voice-assistants/expose/expose-assistant-icon";
|
||||
|
||||
@customElement("ha-filter-voice-assistants")
|
||||
export class HaFilterVoiceAssistants extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
// the list of selected voiceAssistantIds
|
||||
@property({ attribute: false }) public value: string[] = [];
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public expanded = false;
|
||||
|
||||
@state() private _voiceAssistantOptions: string[] = [];
|
||||
|
||||
@state() private _shouldRender = false;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-expansion-panel
|
||||
left-chevron
|
||||
.expanded=${this.expanded}
|
||||
@expanded-will-change=${this._expandedWillChange}
|
||||
@expanded-changed=${this._expandedChanged}
|
||||
>
|
||||
<div slot="header" class="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.dashboard.voice_assistants.main"
|
||||
)}
|
||||
${this.value?.length
|
||||
? html`<div class="badge">${this.value?.length}</div>
|
||||
<ha-icon-button
|
||||
.path=${mdiFilterVariantRemove}
|
||||
@click=${this._clearFilter}
|
||||
></ha-icon-button>`
|
||||
: nothing}
|
||||
</div>
|
||||
${this._shouldRender
|
||||
? html`<ha-list
|
||||
@selected=${this._assistantsSelected}
|
||||
class="ha-scrollbar"
|
||||
multi
|
||||
>
|
||||
${repeat(
|
||||
this._voiceAssistantOptions,
|
||||
(voiceAssistantId) => voiceAssistantId,
|
||||
(voiceAssistantId) =>
|
||||
html`<ha-check-list-item
|
||||
.value=${voiceAssistantId}
|
||||
.selected=${(this.value || []).includes(voiceAssistantId)}
|
||||
hasMeta
|
||||
graphic="icon"
|
||||
>
|
||||
<voice-assistant-brand-icon
|
||||
slot="graphic"
|
||||
.voiceAssistantId=${voiceAssistantId}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
</voice-assistant-brand-icon>
|
||||
${voiceAssistants[voiceAssistantId].name}
|
||||
</ha-check-list-item>`
|
||||
)}
|
||||
</ha-list> `
|
||||
: nothing}
|
||||
</ha-expansion-panel>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._voiceAssistantOptions = Object.keys(voiceAssistants);
|
||||
}
|
||||
|
||||
protected updated(changed) {
|
||||
if (changed.has("expanded") && this.expanded) {
|
||||
setTimeout(() => {
|
||||
if (!this.expanded) return;
|
||||
this.renderRoot.querySelector("ha-list")!.style.height =
|
||||
`${this.clientHeight - 49}px`;
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
private _expandedWillChange(ev) {
|
||||
this._shouldRender = ev.detail.expanded;
|
||||
}
|
||||
|
||||
private _expandedChanged(ev) {
|
||||
this.expanded = ev.detail.expanded;
|
||||
}
|
||||
|
||||
private async _assistantsSelected(
|
||||
ev: CustomEvent<SelectedDetail<Set<number>>>
|
||||
) {
|
||||
if (!ev.detail.index) {
|
||||
fireEvent(this, "data-table-filter-changed", {
|
||||
value: [],
|
||||
items: undefined,
|
||||
});
|
||||
this.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const newvalue: string[] = [];
|
||||
for (const index of ev.detail.index) {
|
||||
newvalue.push(this._voiceAssistantOptions![index]);
|
||||
}
|
||||
this.value = newvalue;
|
||||
|
||||
fireEvent(this, "data-table-filter-changed", {
|
||||
value: this.value,
|
||||
items: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
private _clearFilter(ev) {
|
||||
ev.preventDefault();
|
||||
this.value = [];
|
||||
fireEvent(this, "data-table-filter-changed", {
|
||||
value: undefined,
|
||||
items: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
:host {
|
||||
position: relative;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
:host([expanded]) {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
ha-expansion-panel {
|
||||
--ha-card-border-radius: var(--ha-border-radius-square);
|
||||
--expansion-panel-content-padding: 0;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.header ha-icon-button {
|
||||
margin-inline-start: auto;
|
||||
margin-inline-end: 8px;
|
||||
}
|
||||
.badge {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
margin-inline-start: 8px;
|
||||
margin-inline-end: initial;
|
||||
min-width: 16px;
|
||||
box-sizing: border-box;
|
||||
border-radius: var(--ha-border-radius-circle);
|
||||
font-size: var(--ha-font-size-xs);
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
background-color: var(--primary-color);
|
||||
line-height: var(--ha-line-height-normal);
|
||||
text-align: center;
|
||||
padding: 0px 2px;
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
.add {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-filter-voice-assistants": HaFilterVoiceAssistants;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,5 @@
|
||||
import type { Selector } from "../../data/selector";
|
||||
import type { HaFormData, HaFormSchema } from "./types";
|
||||
|
||||
const setDefaultValue = (
|
||||
field: HaFormSchema,
|
||||
value: HaFormData | undefined
|
||||
) => {
|
||||
if ("selector" in field && "choose" in field.selector) {
|
||||
const firstChoice = Object.keys(field.selector.choose.choices)[0];
|
||||
if (firstChoice) {
|
||||
return {
|
||||
active_choice: firstChoice,
|
||||
[firstChoice]: value,
|
||||
};
|
||||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
import type { HaFormSchema } from "./types";
|
||||
|
||||
export const computeInitialHaFormData = (
|
||||
schema: HaFormSchema[] | readonly HaFormSchema[]
|
||||
@@ -26,12 +10,9 @@ export const computeInitialHaFormData = (
|
||||
field.description?.suggested_value !== undefined &&
|
||||
field.description?.suggested_value !== null
|
||||
) {
|
||||
data[field.name] = setDefaultValue(
|
||||
field,
|
||||
field.description.suggested_value
|
||||
);
|
||||
data[field.name] = field.description.suggested_value;
|
||||
} else if ("default" in field) {
|
||||
data[field.name] = setDefaultValue(field, field.default);
|
||||
data[field.name] = field.default;
|
||||
} else if (field.type === "expandable") {
|
||||
const expandableData = computeInitialHaFormData(field.schema);
|
||||
if (field.required || Object.keys(expandableData).length) {
|
||||
@@ -127,21 +108,6 @@ export const computeInitialHaFormData = (
|
||||
data[field.name] = {};
|
||||
} else if ("state" in selector) {
|
||||
data[field.name] = selector.state?.multiple ? [] : "";
|
||||
} else if ("choose" in selector) {
|
||||
const firstChoice = Object.keys(selector.choose.choices)[0];
|
||||
if (!firstChoice) {
|
||||
data[field.name] = {};
|
||||
} else {
|
||||
data[field.name] = {
|
||||
active_choice: firstChoice,
|
||||
[firstChoice]: computeInitialHaFormData([
|
||||
{
|
||||
name: firstChoice,
|
||||
selector: selector.choose.choices[firstChoice].selector,
|
||||
},
|
||||
])[firstChoice],
|
||||
};
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`Selector ${Object.keys(selector)[0]} not supported in initial form data`
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
import "@home-assistant/webawesome/dist/components/popover/popover";
|
||||
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||
import { mdiPlaylistPlus } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
type CSSResultGroup,
|
||||
type PropertyValues,
|
||||
} from "lit";
|
||||
import { css, html, LitElement, nothing, type CSSResultGroup } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { tinykeys } from "tinykeys";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { throttle } from "../common/util/throttle";
|
||||
import { PickerMixin } from "../mixins/picker-mixin";
|
||||
import type { FuseWeightedKey } from "../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../types";
|
||||
@@ -46,7 +39,7 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
public getItems!: (
|
||||
searchString?: string,
|
||||
section?: string
|
||||
) => (PickerComboBoxItem | string)[] | undefined;
|
||||
) => (PickerComboBoxItem | string)[];
|
||||
|
||||
@property({ attribute: false, type: Array })
|
||||
public getAdditionalItems?: (searchString?: string) => PickerComboBoxItem[];
|
||||
@@ -121,8 +114,6 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
|
||||
@state() private _openedNarrow = false;
|
||||
|
||||
@state() private _unknownValue = false;
|
||||
|
||||
static shadowRootOptions = {
|
||||
...LitElement.shadowRootOptions,
|
||||
delegatesFocus: true,
|
||||
@@ -139,25 +130,6 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
|
||||
private _unsubscribeTinyKeys?: () => void;
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues) {
|
||||
if (changedProperties.has("value")) {
|
||||
this._setUnknownValue();
|
||||
return;
|
||||
}
|
||||
if (changedProperties.has("hass")) {
|
||||
this._throttleUnknownValue();
|
||||
}
|
||||
}
|
||||
|
||||
public setFieldValue(value: string) {
|
||||
if (this._comboBox) {
|
||||
this._comboBox.setFieldValue(value);
|
||||
return;
|
||||
}
|
||||
// Store initial value to set when opened
|
||||
this._initialFieldValue = value;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
// Only show label if it's not a top label and there is a value.
|
||||
const label = this.useTopLabel && this.value ? undefined : this.label;
|
||||
@@ -185,7 +157,11 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
type="button"
|
||||
class=${this._opened ? "opened" : ""}
|
||||
compact
|
||||
.unknown=${this._unknownValue}
|
||||
.unknown=${this._unknownValue(
|
||||
this.allowCustomValue,
|
||||
this.value,
|
||||
this.getItems()
|
||||
)}
|
||||
.unknownItemText=${this.unknownItemText}
|
||||
aria-label=${ifDefined(this.label)}
|
||||
@click=${this.open}
|
||||
@@ -206,42 +182,40 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
</ha-picker-field>`}
|
||||
</slot>
|
||||
</div>
|
||||
${this._pickerWrapperOpen || this._opened
|
||||
? this._openedNarrow
|
||||
? html`
|
||||
<ha-bottom-sheet
|
||||
flexcontent
|
||||
.open=${this._pickerWrapperOpen}
|
||||
@wa-after-show=${this._dialogOpened}
|
||||
@closed=${this._hidePicker}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label=${this.label || "Select option"}
|
||||
>
|
||||
${this._renderComboBox(true)}
|
||||
</ha-bottom-sheet>
|
||||
`
|
||||
: html`
|
||||
<wa-popover
|
||||
.open=${this._pickerWrapperOpen}
|
||||
style="--body-width: ${this._popoverWidth}px;"
|
||||
without-arrow
|
||||
distance="-4"
|
||||
.placement=${this.popoverPlacement}
|
||||
for="picker"
|
||||
auto-size="vertical"
|
||||
auto-size-padding="16"
|
||||
@wa-after-show=${this._dialogOpened}
|
||||
@wa-after-hide=${this._hidePicker}
|
||||
trap-focus
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label=${this.label || "Select option"}
|
||||
>
|
||||
${this._renderComboBox()}
|
||||
</wa-popover>
|
||||
`
|
||||
: nothing}
|
||||
${!this._openedNarrow && (this._pickerWrapperOpen || this._opened)
|
||||
? html`
|
||||
<wa-popover
|
||||
.open=${this._pickerWrapperOpen}
|
||||
style="--body-width: ${this._popoverWidth}px;"
|
||||
without-arrow
|
||||
distance="-4"
|
||||
.placement=${this.popoverPlacement}
|
||||
for="picker"
|
||||
auto-size="vertical"
|
||||
auto-size-padding="16"
|
||||
@wa-after-show=${this._dialogOpened}
|
||||
@wa-after-hide=${this._hidePicker}
|
||||
trap-focus
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label=${this.label || "Select option"}
|
||||
>
|
||||
${this._renderComboBox()}
|
||||
</wa-popover>
|
||||
`
|
||||
: this._pickerWrapperOpen || this._opened
|
||||
? html`<ha-bottom-sheet
|
||||
flexcontent
|
||||
.open=${this._pickerWrapperOpen}
|
||||
@wa-after-show=${this._dialogOpened}
|
||||
@closed=${this._hidePicker}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label=${this.label || "Select option"}
|
||||
>
|
||||
${this._renderComboBox(true)}
|
||||
</ha-bottom-sheet>`
|
||||
: nothing}
|
||||
</div>
|
||||
${this._renderHelper()}`;
|
||||
}
|
||||
@@ -274,29 +248,26 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
private _setUnknownValue = () => {
|
||||
const items = this.getItems();
|
||||
if (
|
||||
this.allowCustomValue ||
|
||||
this.value === undefined ||
|
||||
this.value === null ||
|
||||
this.value === "" ||
|
||||
!items
|
||||
) {
|
||||
this._unknownValue = false;
|
||||
return;
|
||||
private _unknownValue = memoizeOne(
|
||||
(
|
||||
allowCustomValue: boolean,
|
||||
value?: string,
|
||||
items?: (PickerComboBoxItem | string)[]
|
||||
) => {
|
||||
if (
|
||||
allowCustomValue ||
|
||||
value === undefined ||
|
||||
value === null ||
|
||||
value === "" ||
|
||||
!items
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !items.some(
|
||||
(item) => typeof item !== "string" && item.id === value
|
||||
);
|
||||
}
|
||||
|
||||
this._unknownValue = !items.some(
|
||||
(item) => typeof item !== "string" && item.id === this.value
|
||||
);
|
||||
};
|
||||
|
||||
private _throttleUnknownValue = throttle(
|
||||
this._setUnknownValue,
|
||||
1000,
|
||||
true,
|
||||
false
|
||||
);
|
||||
|
||||
private _renderHelper() {
|
||||
@@ -312,16 +283,9 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
</ha-input-helper-text>`;
|
||||
}
|
||||
|
||||
private _initialFieldValue?: string;
|
||||
|
||||
private _dialogOpened = () => {
|
||||
this._opened = true;
|
||||
requestAnimationFrame(() => {
|
||||
// Set initial field value if needed
|
||||
if (this._initialFieldValue) {
|
||||
this._comboBox?.setFieldValue(this._initialFieldValue);
|
||||
this._initialFieldValue = undefined;
|
||||
}
|
||||
if (this.hass && isIosApp(this.hass)) {
|
||||
this.hass.auth.external!.fireMessage({
|
||||
type: "focus_element",
|
||||
@@ -331,7 +295,6 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._comboBox?.focus();
|
||||
});
|
||||
};
|
||||
@@ -413,7 +376,6 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
.container {
|
||||
position: relative;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
label[disabled] {
|
||||
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.6));
|
||||
|
||||
@@ -109,7 +109,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
public getItems!: (
|
||||
searchString?: string,
|
||||
section?: string
|
||||
) => PickerComboBoxItem[] | undefined;
|
||||
) => PickerComboBoxItem[];
|
||||
|
||||
@property({ attribute: false, type: Array })
|
||||
public getAdditionalItems?: (searchString?: string) => PickerComboBoxItem[];
|
||||
@@ -153,12 +153,6 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
|
||||
@state() private _items: PickerComboBoxItem[] = [];
|
||||
|
||||
public setFieldValue(value: string) {
|
||||
if (this._searchFieldElement) {
|
||||
this._searchFieldElement.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected get scrollableElement(): HTMLElement | null {
|
||||
return this._virtualizerElement as HTMLElement | null;
|
||||
}
|
||||
@@ -301,7 +295,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
this.getAdditionalItems?.(searchString) || [];
|
||||
|
||||
private _getItems = () => {
|
||||
let items = [...(this.getItems(this._search, this.selectedSection) || [])];
|
||||
let items = [...this.getItems(this._search, this.selectedSection)];
|
||||
|
||||
if (!this.sections?.length) {
|
||||
items = items.sort((entityA, entityB) => {
|
||||
@@ -793,7 +787,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
.section-title,
|
||||
.title {
|
||||
background-color: var(--ha-color-fill-neutral-quiet-resting);
|
||||
padding: var(--ha-space-2) var(--ha-space-3);
|
||||
padding: var(--ha-space-1) var(--ha-space-2);
|
||||
font-weight: var(--ha-font-weight-bold);
|
||||
color: var(--secondary-text-color);
|
||||
min-height: var(--ha-space-6);
|
||||
|
||||
@@ -10,7 +10,7 @@ class HaSectionTitle extends LitElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
background-color: var(--ha-color-fill-neutral-quiet-resting);
|
||||
padding: var(--ha-space-2) var(--ha-space-3);
|
||||
padding: var(--ha-space-1) var(--ha-space-2);
|
||||
font-weight: var(--ha-font-weight-bold);
|
||||
color: var(--secondary-text-color);
|
||||
min-height: var(--ha-space-6);
|
||||
|
||||
@@ -38,13 +38,6 @@ export class HaChooseSelector extends LitElement {
|
||||
) {
|
||||
this._setActiveChoice();
|
||||
}
|
||||
if (
|
||||
changedProperties.has("value") &&
|
||||
changedProperties.get("value")?.active_choice &&
|
||||
changedProperties.get("value")?.active_choice !== this._activeChoice
|
||||
) {
|
||||
this._setActiveChoice();
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -61,8 +54,7 @@ export class HaChooseSelector extends LitElement {
|
||||
size="small"
|
||||
.buttons=${this._toggleButtons(
|
||||
this.selector.choose.choices,
|
||||
this.selector.choose.translation_key,
|
||||
this.hass.localize
|
||||
this.selector.choose.translation_key
|
||||
)}
|
||||
.active=${this._activeChoice}
|
||||
@value-changed=${this._choiceChanged}
|
||||
@@ -80,11 +72,7 @@ export class HaChooseSelector extends LitElement {
|
||||
}
|
||||
|
||||
private _toggleButtons = memoizeOne(
|
||||
(
|
||||
choices: ChooseSelector["choose"]["choices"],
|
||||
translationKey?: string,
|
||||
_localize?: HomeAssistant["localize"]
|
||||
) =>
|
||||
(choices: ChooseSelector["choose"]["choices"], translationKey?: string) =>
|
||||
Object.keys(choices).map((choice) => ({
|
||||
label:
|
||||
this.localizeValue && translationKey
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import memoizeOne from "memoize-one";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { DurationSelector } from "../../data/selector";
|
||||
@@ -12,10 +11,7 @@ export class HaTimeDuration extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public selector!: DurationSelector;
|
||||
|
||||
@property({ attribute: false }) public value?:
|
||||
| HaDurationData
|
||||
| string
|
||||
| number;
|
||||
@property({ attribute: false }) public value?: HaDurationData;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@@ -25,47 +21,16 @@ export class HaTimeDuration extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
private _data = memoizeOne(
|
||||
(value?: HaDurationData | string | number): HaDurationData | undefined => {
|
||||
if (typeof value === "number") {
|
||||
return { seconds: value };
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
const negative = value.trim()[0] === "-";
|
||||
const parts = value
|
||||
.split(":")
|
||||
.map((p) => (negative && p ? -Math.abs(Number(p)) : Number(p)));
|
||||
|
||||
if (parts.length === 1) {
|
||||
return { seconds: parts[0] };
|
||||
}
|
||||
if (parts.length === 2) {
|
||||
return { hours: parts[0], minutes: parts[1] };
|
||||
}
|
||||
if (parts.length === 3) {
|
||||
return {
|
||||
hours: parts[0],
|
||||
minutes: parts[1],
|
||||
seconds: parts[2],
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
);
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-duration-input
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.data=${this._data(this.value)}
|
||||
.data=${this.value}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.enableDay=${this.selector.duration?.enable_day}
|
||||
.enableMillisecond=${this.selector.duration?.enable_millisecond}
|
||||
.allowNegative=${this.selector.duration?.allow_negative}
|
||||
></ha-duration-input>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -2,16 +2,13 @@ import type { HassServiceTarget } from "home-assistant-js-websocket";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import {
|
||||
resolveEntityIDs,
|
||||
type StateSelector,
|
||||
type TargetSelector,
|
||||
} from "../../data/selector";
|
||||
import type { StateSelector } from "../../data/selector";
|
||||
import { extractFromTarget } from "../../data/target";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { PickerComboBoxItem } from "../ha-picker-combo-box";
|
||||
import "../entity/ha-entity-state-picker";
|
||||
import "../entity/ha-entity-states-picker";
|
||||
import type { PickerComboBoxItem } from "../ha-picker-combo-box";
|
||||
|
||||
@customElement("ha-selector-state")
|
||||
export class HaSelectorState extends SubscribeMixin(LitElement) {
|
||||
@@ -33,7 +30,6 @@ export class HaSelectorState extends SubscribeMixin(LitElement) {
|
||||
filter_attribute?: string;
|
||||
filter_entity?: string | string[];
|
||||
filter_target?: HassServiceTarget;
|
||||
target_selector?: TargetSelector;
|
||||
};
|
||||
|
||||
@state() private _entityIds?: string | string[];
|
||||
@@ -58,8 +54,7 @@ export class HaSelectorState extends SubscribeMixin(LitElement) {
|
||||
this._resolveEntityIds(
|
||||
this.selector.state?.entity_id,
|
||||
this.context?.filter_entity,
|
||||
this.context?.filter_target,
|
||||
this.context?.target_selector
|
||||
this.context?.filter_target
|
||||
).then((entityIds) => {
|
||||
this._entityIds = entityIds;
|
||||
});
|
||||
@@ -109,8 +104,7 @@ export class HaSelectorState extends SubscribeMixin(LitElement) {
|
||||
private async _resolveEntityIds(
|
||||
selectorEntityId: string | string[] | undefined,
|
||||
contextFilterEntity: string | string[] | undefined,
|
||||
contextFilterTarget: HassServiceTarget | undefined,
|
||||
contextTargetSelector: TargetSelector | undefined
|
||||
contextFilterTarget: HassServiceTarget | undefined
|
||||
): Promise<string | string[] | undefined> {
|
||||
if (selectorEntityId !== undefined) {
|
||||
return selectorEntityId;
|
||||
@@ -119,14 +113,8 @@ export class HaSelectorState extends SubscribeMixin(LitElement) {
|
||||
return contextFilterEntity;
|
||||
}
|
||||
if (contextFilterTarget !== undefined) {
|
||||
return resolveEntityIDs(
|
||||
this.hass,
|
||||
contextFilterTarget,
|
||||
this.hass.entities,
|
||||
this.hass.devices,
|
||||
this.hass.areas,
|
||||
contextTargetSelector
|
||||
);
|
||||
const result = await extractFromTarget(this.hass, contextFilterTarget);
|
||||
return result.referenced_entities;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,6 @@ export class HaSlider extends Slider {
|
||||
#thumb {
|
||||
border: none;
|
||||
background-color: var(--ha-slider-thumb-color, var(--primary-color));
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#thumb:after {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/dialog/dialog";
|
||||
import type WaDialog from "@home-assistant/webawesome/dist/components/dialog/dialog";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import {
|
||||
@@ -14,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";
|
||||
|
||||
@@ -50,6 +50,7 @@ export type DialogWidth = "small" | "medium" | "large" | "full";
|
||||
* @cssprop --ha-dialog-hide-duration - Hide animation duration.
|
||||
* @cssprop --ha-dialog-surface-background - Dialog background color.
|
||||
* @cssprop --ha-dialog-border-radius - Border radius of the dialog surface.
|
||||
* @cssprop --dialog-z-index - Z-index for the dialog.
|
||||
* @cssprop --dialog-surface-margin-top - Top margin for the dialog surface.
|
||||
*
|
||||
* @attr {boolean} open - Controls the dialog open state.
|
||||
@@ -114,8 +115,6 @@ export class HaWaDialog extends ScrollableFadeMixin(LitElement) {
|
||||
@state()
|
||||
private _bodyScrolled = false;
|
||||
|
||||
private _escapePressed = false;
|
||||
|
||||
protected get scrollableElement(): HTMLElement | null {
|
||||
return this.bodyContainer;
|
||||
}
|
||||
@@ -141,8 +140,6 @@ export class HaWaDialog extends ScrollableFadeMixin(LitElement) {
|
||||
(this.headerTitle !== undefined ? "ha-wa-dialog-title" : undefined)
|
||||
)}
|
||||
aria-describedby=${ifDefined(this.ariaDescribedBy)}
|
||||
@keydown=${this._handleKeyDown}
|
||||
@wa-hide=${this._handleHide}
|
||||
@wa-show=${this._handleShow}
|
||||
@wa-after-show=${this._handleAfterShow}
|
||||
@wa-after-hide=${this._handleAfterHide}
|
||||
@@ -188,22 +185,21 @@ export class HaWaDialog extends ScrollableFadeMixin(LitElement) {
|
||||
await this.updateComplete;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
// temporary disabled because of issues with focus in iOS app, can be reenabled in 2026.2.0
|
||||
// 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;
|
||||
// }
|
||||
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();
|
||||
});
|
||||
};
|
||||
@@ -212,11 +208,9 @@ export class HaWaDialog extends ScrollableFadeMixin(LitElement) {
|
||||
fireEvent(this, "after-show");
|
||||
};
|
||||
|
||||
private _handleAfterHide = (ev: CustomEvent<{ source: Element }>) => {
|
||||
if (ev.eventPhase === Event.AT_TARGET) {
|
||||
this._open = false;
|
||||
fireEvent(this, "closed");
|
||||
}
|
||||
private _handleAfterHide = () => {
|
||||
this._open = false;
|
||||
fireEvent(this, "closed");
|
||||
};
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
@@ -229,23 +223,6 @@ export class HaWaDialog extends ScrollableFadeMixin(LitElement) {
|
||||
this._bodyScrolled = (ev.target as HTMLDivElement).scrollTop > 0;
|
||||
}
|
||||
|
||||
private _handleKeyDown(ev: KeyboardEvent) {
|
||||
if (ev.key === "Escape") {
|
||||
this._escapePressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private _handleHide(ev: CustomEvent<{ source: Element }>) {
|
||||
if (
|
||||
this.preventScrimClose &&
|
||||
this._escapePressed &&
|
||||
ev.detail.source === (ev.target as WaDialog).dialog
|
||||
) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
this._escapePressed = false;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
...super.styles,
|
||||
@@ -294,7 +271,6 @@ export class HaWaDialog extends ScrollableFadeMixin(LitElement) {
|
||||
}
|
||||
|
||||
wa-dialog::part(dialog) {
|
||||
color: var(--primary-text-color);
|
||||
min-width: var(--width, var(--full-width));
|
||||
max-width: var(--width, var(--full-width));
|
||||
max-height: var(
|
||||
|
||||
@@ -24,7 +24,6 @@ import { setupLeafletMap } from "../../common/dom/setup-leaflet-map";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { DecoratedMarker } from "../../common/map/decorated_marker";
|
||||
import { filterXSS } from "../../common/util/xss";
|
||||
import type { HomeAssistant, ThemeMode } from "../../types";
|
||||
import { isTouch } from "../../util/is_touch";
|
||||
import "../ha-icon-button";
|
||||
@@ -382,7 +381,7 @@ export class HaMap extends ReactiveElement {
|
||||
this.hass.config
|
||||
);
|
||||
}
|
||||
return `${filterXSS(path.name ?? "")}<br>${formattedTime}`;
|
||||
return `${path.name}<br>${formattedTime}`;
|
||||
}
|
||||
|
||||
private _drawPaths(): void {
|
||||
@@ -550,7 +549,7 @@ export class HaMap extends ReactiveElement {
|
||||
iconHTML = el.outerHTML;
|
||||
} else {
|
||||
const el = document.createElement("span");
|
||||
el.textContent = title;
|
||||
el.innerHTML = title;
|
||||
iconHTML = el.outerHTML;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ActionDetail } from "@material/mwc-list";
|
||||
import {
|
||||
mdiAlphaABoxOutline,
|
||||
mdiArrowLeft,
|
||||
mdiClose,
|
||||
mdiDotsVertical,
|
||||
mdiGrid,
|
||||
@@ -20,10 +21,9 @@ import type {
|
||||
} from "../../data/media-player";
|
||||
import { haStyleDialog, haStyleDialogFixedTop } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-wa-dialog";
|
||||
import "../ha-dialog";
|
||||
import "../ha-dialog-header";
|
||||
import "../ha-list-item";
|
||||
import "../ha-icon-button-arrow-prev";
|
||||
import "./ha-media-manage-button";
|
||||
import "./ha-media-player-browse";
|
||||
import type {
|
||||
@@ -44,8 +44,6 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
|
||||
@state() _preferredLayout: MediaPlayerLayoutType = "auto";
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
@query("ha-media-player-browse") private _browser!: HaMediaPlayerBrowse;
|
||||
|
||||
public showDialog(params: MediaPlayerBrowseDialogParams): void {
|
||||
@@ -56,11 +54,9 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
media_content_type: undefined,
|
||||
},
|
||||
];
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._open = false;
|
||||
this._params = undefined;
|
||||
this._navigateIds = undefined;
|
||||
this._currentItem = undefined;
|
||||
@@ -75,20 +71,28 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-wa-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
flexcontent
|
||||
<ha-dialog
|
||||
open
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
hideActions
|
||||
flexContent
|
||||
.heading=${!this._currentItem
|
||||
? this.hass.localize(
|
||||
"ui.components.media-browser.media-player-browser"
|
||||
)
|
||||
: this._currentItem.title}
|
||||
@closed=${this.closeDialog}
|
||||
@opened=${this._dialogOpened}
|
||||
>
|
||||
<ha-dialog-header show-border slot="header">
|
||||
<ha-dialog-header show-border slot="heading">
|
||||
${this._navigateIds.length > (this._params.minimumNavigateLevel ?? 1)
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
<ha-icon-button
|
||||
slot="navigationIcon"
|
||||
.path=${mdiArrowLeft}
|
||||
@click=${this._goBack}
|
||||
></ha-icon-button-arrow-prev>
|
||||
></ha-icon-button>
|
||||
`
|
||||
: nothing}
|
||||
<span slot="title">
|
||||
@@ -149,7 +153,7 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
data-dialog="close"
|
||||
dialogAction="close"
|
||||
slot="actionItems"
|
||||
></ha-icon-button>
|
||||
</ha-dialog-header>
|
||||
@@ -169,7 +173,7 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
@media-picked=${this._mediaPicked}
|
||||
@media-browsed=${this._mediaBrowsed}
|
||||
></ha-media-player-browse>
|
||||
</ha-wa-dialog>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -221,7 +225,8 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
haStyleDialog,
|
||||
haStyleDialogFixedTop,
|
||||
css`
|
||||
ha-wa-dialog {
|
||||
ha-dialog {
|
||||
--dialog-z-index: 9;
|
||||
--dialog-content-padding: 0;
|
||||
}
|
||||
|
||||
@@ -236,9 +241,9 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
ha-wa-dialog {
|
||||
--ha-dialog-max-width: 800px;
|
||||
--ha-dialog-max-height: calc(
|
||||
ha-dialog {
|
||||
--mdc-dialog-max-width: 800px;
|
||||
--mdc-dialog-max-height: calc(
|
||||
100vh - var(--ha-space-18) - var(--safe-area-inset-y)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { type CSSResultGroup, LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { mdiSpeaker, mdiSpeakerPause, mdiSpeakerPlay } from "@mdi/js";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { mdiSpeaker } from "@mdi/js";
|
||||
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { computeEntityNameList } from "../../common/entity/compute_entity_name_display";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
import "../ha-switch";
|
||||
import "../ha-svg-icon";
|
||||
import type { MediaPlayerEntity } from "../../data/media-player";
|
||||
|
||||
@customElement("ha-media-player-toggle")
|
||||
class HaMediaPlayerToggle extends LitElement {
|
||||
@@ -21,61 +20,15 @@ class HaMediaPlayerToggle extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
private _computeDisplayData = memoizeOne(
|
||||
(
|
||||
entityId: string,
|
||||
entities: HomeAssistant["entities"],
|
||||
devices: HomeAssistant["devices"],
|
||||
areas: HomeAssistant["areas"],
|
||||
floors: HomeAssistant["floors"],
|
||||
isRTL: boolean,
|
||||
stateObj: HomeAssistant["states"][string]
|
||||
) => {
|
||||
const [entityName, deviceName, areaName] = computeEntityNameList(
|
||||
stateObj,
|
||||
[{ type: "entity" }, { type: "device" }, { type: "area" }],
|
||||
entities,
|
||||
devices,
|
||||
areas,
|
||||
floors
|
||||
);
|
||||
|
||||
const primary = entityName || deviceName || entityId;
|
||||
const secondary = [areaName, entityName ? deviceName : undefined]
|
||||
.filter(Boolean)
|
||||
.join(isRTL ? " ◂ " : " ▸ ");
|
||||
|
||||
return { primary, secondary };
|
||||
}
|
||||
);
|
||||
|
||||
protected render() {
|
||||
const stateObj = this.hass.states[this.entityId];
|
||||
|
||||
let icon = mdiSpeaker;
|
||||
if (stateObj.state === "playing") {
|
||||
icon = mdiSpeakerPlay;
|
||||
} else if (stateObj.state === "paused") {
|
||||
icon = mdiSpeakerPause;
|
||||
}
|
||||
|
||||
const isRTL = computeRTL(this.hass);
|
||||
|
||||
const { primary, secondary } = this._computeDisplayData(
|
||||
this.entityId,
|
||||
this.hass.entities,
|
||||
this.hass.devices,
|
||||
this.hass.areas,
|
||||
this.hass.floors,
|
||||
isRTL,
|
||||
stateObj
|
||||
);
|
||||
|
||||
return html`<div class="list-item">
|
||||
<ha-svg-icon .path=${icon}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiSpeaker}></ha-svg-icon>
|
||||
<div class="info">
|
||||
<div class="main-text">${primary}</div>
|
||||
<div class="secondary-text">${secondary}</div>
|
||||
<div class="main-text">${computeStateName(stateObj)}</div>
|
||||
<div class="secondary-text">
|
||||
${this._formatSecondaryText(stateObj as MediaPlayerEntity)}
|
||||
</div>
|
||||
</div>
|
||||
<ha-switch
|
||||
.disabled=${this.disabled}
|
||||
@@ -85,6 +38,16 @@ class HaMediaPlayerToggle extends LitElement {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private _formatSecondaryText(stateObj: MediaPlayerEntity): string {
|
||||
if (stateObj.state !== "playing") {
|
||||
return this.hass.localize("ui.card.media_player.idle");
|
||||
}
|
||||
|
||||
return [stateObj.attributes.media_title, stateObj.attributes.media_artist]
|
||||
.filter((segment) => segment)
|
||||
.join(" · ");
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
css`
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { voiceAssistants } from "../data/expose";
|
||||
import { brandsUrl } from "../util/brands-url";
|
||||
|
||||
@customElement("voice-assistant-brand-icon")
|
||||
export class VoiceAssistantBrandicon extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public voiceAssistantId!: string;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<img
|
||||
class="logo"
|
||||
alt=${voiceAssistants[this.voiceAssistantId].name}
|
||||
src=${brandsUrl({
|
||||
domain: voiceAssistants[this.voiceAssistantId].domain,
|
||||
type: "icon",
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.logo {
|
||||
position: relative;
|
||||
height: 24px;
|
||||
margin-right: 16px;
|
||||
margin-inline-end: 16px;
|
||||
margin-inline-start: initial;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"voice-assistant-brand-icon": VoiceAssistantBrandicon;
|
||||
}
|
||||
}
|
||||
@@ -449,9 +449,16 @@ const getEnergyData = async (
|
||||
const allStatIDs = [...energyStatIds, ...waterStatIds, ...powerStatIds];
|
||||
|
||||
const dayDifference = differenceInDays(end || new Date(), start);
|
||||
|
||||
const period = getSuggestedPeriod(start, end);
|
||||
const finePeriod = getSuggestedPeriod(start, end, true);
|
||||
const period =
|
||||
isFirstDayOfMonth(start) &&
|
||||
(!end || isLastDayOfMonth(end)) &&
|
||||
dayDifference > 35
|
||||
? "month"
|
||||
: dayDifference > 2
|
||||
? "day"
|
||||
: "hour";
|
||||
const finePeriod =
|
||||
dayDifference > 64 ? "day" : dayDifference > 8 ? "hour" : "5minute";
|
||||
|
||||
const statsMetadata: Record<string, StatisticsMetaData> = {};
|
||||
const statsMetadataArray = allStatIDs.length
|
||||
@@ -582,7 +589,7 @@ const getEnergyData = async (
|
||||
consumptionStatIDs,
|
||||
co2SignalEntity,
|
||||
end,
|
||||
period
|
||||
dayDifference > 35 ? "month" : dayDifference > 2 ? "day" : "hour"
|
||||
);
|
||||
if (compare) {
|
||||
_fossilEnergyConsumptionCompare = getFossilEnergyConsumption(
|
||||
@@ -591,7 +598,7 @@ const getEnergyData = async (
|
||||
consumptionStatIDs,
|
||||
co2SignalEntity,
|
||||
endCompare,
|
||||
period
|
||||
dayDifference > 35 ? "month" : dayDifference > 2 ? "day" : "hour"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1420,22 +1427,3 @@ export const formatPowerShort = (
|
||||
units[unitIndex]
|
||||
);
|
||||
};
|
||||
|
||||
export function getSuggestedPeriod(
|
||||
start: Date,
|
||||
end?: Date,
|
||||
fine = false
|
||||
): "5minute" | "hour" | "day" | "month" {
|
||||
const dayDifference = differenceInDays(end || new Date(), start);
|
||||
|
||||
if (fine) {
|
||||
return dayDifference > 64 ? "day" : dayDifference > 8 ? "hour" : "5minute";
|
||||
}
|
||||
return isFirstDayOfMonth(start) &&
|
||||
(!end || isLastDayOfMonth(end)) &&
|
||||
dayDifference > 35
|
||||
? "month"
|
||||
: dayDifference > 2
|
||||
? "day"
|
||||
: "hour";
|
||||
}
|
||||
|
||||
@@ -69,7 +69,6 @@ export const DOMAIN_ATTRIBUTES_UNITS = {
|
||||
current_humidity: "%",
|
||||
min_humidity: "%",
|
||||
max_humidity: "%",
|
||||
target_humidity_step: "%",
|
||||
},
|
||||
light: {
|
||||
color_temp: "mired",
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { EntityRegistryEntry } from "./entity/entity_registry";
|
||||
import { entityRegistryByEntityId } from "./entity/entity_registry";
|
||||
|
||||
export const voiceAssistants = {
|
||||
conversation: { domain: "assist_pipeline", name: "Assist" },
|
||||
@@ -54,13 +52,3 @@ export const listExposedEntities = (hass: HomeAssistant) =>
|
||||
hass.callWS<{ exposed_entities: Record<string, ExposeEntitySettings> }>({
|
||||
type: "homeassistant/expose_entity/list",
|
||||
});
|
||||
|
||||
export const getEntityVoiceAssistantsIds = (
|
||||
entityRegistry: EntityRegistryEntry[],
|
||||
entityId: string
|
||||
) => {
|
||||
const entity = entityRegistryByEntityId(entityRegistry)[entityId];
|
||||
return Object.keys(voiceAssistants).filter(
|
||||
(vaKey) => entity?.options?.[vaKey]?.should_expose
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,7 +16,6 @@ export type HumidifierEntity = HassEntityBase & {
|
||||
mode?: string;
|
||||
action?: HumidifierAction;
|
||||
available_modes?: string[];
|
||||
target_humidity_step?: number;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -52,9 +52,6 @@ export interface BaseActionConfig {
|
||||
|
||||
export interface ConfirmationRestrictionConfig {
|
||||
text?: string;
|
||||
title?: string;
|
||||
confirm_text?: string;
|
||||
dismiss_text?: string;
|
||||
exemptions?: RestrictionConfig[];
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,6 @@ export interface LovelaceBaseViewConfig {
|
||||
title?: string;
|
||||
path?: string;
|
||||
icon?: string;
|
||||
show_icon_and_title?: boolean;
|
||||
theme?: string;
|
||||
panel?: boolean;
|
||||
background?: string | LovelaceViewBackgroundConfig;
|
||||
|
||||
@@ -221,7 +221,6 @@ export interface DurationSelector {
|
||||
duration: {
|
||||
enable_day?: boolean;
|
||||
enable_millisecond?: boolean;
|
||||
allow_negative?: boolean;
|
||||
} | null;
|
||||
}
|
||||
|
||||
@@ -930,13 +929,13 @@ export const resolveEntityIDs = (
|
||||
targetPickerValue: HassServiceTarget,
|
||||
entities: HomeAssistant["entities"],
|
||||
devices: HomeAssistant["devices"],
|
||||
areas: HomeAssistant["areas"],
|
||||
targetSelector: TargetSelector = { target: {} }
|
||||
areas: HomeAssistant["areas"]
|
||||
): string[] => {
|
||||
if (!targetPickerValue) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const targetSelector = { target: {} };
|
||||
const targetEntities = new Set(ensureArray(targetPickerValue.entity_id));
|
||||
const targetDevices = new Set(ensureArray(targetPickerValue.device_id));
|
||||
const targetAreas = new Set(ensureArray(targetPickerValue.area_id));
|
||||
|
||||
@@ -44,27 +44,14 @@ export const updateUsesProgress = (entity: UpdateEntity): boolean =>
|
||||
supportsFeature(entity, UpdateEntityFeature.PROGRESS) &&
|
||||
entity.attributes.update_percentage !== null;
|
||||
|
||||
export const updateAvailable = (
|
||||
entity: UpdateEntity,
|
||||
showSkipped = false
|
||||
): boolean =>
|
||||
entity.state === BINARY_STATE_ON ||
|
||||
(showSkipped && Boolean(entity.attributes.skipped_version));
|
||||
|
||||
export const updateCanInstall = (
|
||||
entity: UpdateEntity,
|
||||
showSkipped = false
|
||||
): boolean =>
|
||||
updateAvailable(entity, showSkipped) &&
|
||||
(entity.state === BINARY_STATE_ON ||
|
||||
(showSkipped && Boolean(entity.attributes.skipped_version))) &&
|
||||
supportsFeature(entity, UpdateEntityFeature.INSTALL);
|
||||
|
||||
export const updateCanNotInstall = (
|
||||
entity: UpdateEntity,
|
||||
showSkipped = false
|
||||
): boolean =>
|
||||
updateAvailable(entity, showSkipped) &&
|
||||
!supportsFeature(entity, UpdateEntityFeature.INSTALL);
|
||||
|
||||
export const latestVersionIsSkipped = (entity: UpdateEntity): boolean =>
|
||||
!!(
|
||||
entity.attributes.latest_version &&
|
||||
@@ -121,17 +108,13 @@ export const filterUpdateEntities = (
|
||||
);
|
||||
});
|
||||
|
||||
export const filterUpdateEntitiesParameterized = (
|
||||
export const filterUpdateEntitiesWithInstall = (
|
||||
entities: HassEntities,
|
||||
showSkipped = false,
|
||||
showNotInstallable = false
|
||||
showSkipped = false
|
||||
) =>
|
||||
filterUpdateEntities(entities).filter((entity) => {
|
||||
if (showNotInstallable) {
|
||||
return updateCanNotInstall(entity, showSkipped);
|
||||
}
|
||||
return updateCanInstall(entity, showSkipped);
|
||||
});
|
||||
filterUpdateEntities(entities).filter((entity) =>
|
||||
updateCanInstall(entity, showSkipped)
|
||||
);
|
||||
|
||||
export const checkForEntityUpdates = async (
|
||||
element: HTMLElement,
|
||||
|
||||
@@ -766,10 +766,7 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
|
||||
}
|
||||
|
||||
.content-wrapper.settings-view .fade-bottom {
|
||||
bottom: calc(
|
||||
var(--ha-space-14) +
|
||||
max(var(--safe-area-inset-bottom), var(--ha-space-4))
|
||||
);
|
||||
bottom: var(--ha-space-18);
|
||||
}
|
||||
|
||||
.child-view {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { mdiAppleKeyboardCommand } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
@@ -153,10 +154,6 @@ const _SHORTCUTS: Section[] = [
|
||||
shortcut: ["M"],
|
||||
descriptionTranslationKey: "ui.dialogs.shortcuts.other.my_link",
|
||||
},
|
||||
{
|
||||
shortcut: ["Shift", "/"],
|
||||
descriptionTranslationKey: "ui.dialogs.shortcuts.other.show_shortcuts",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -187,7 +184,9 @@ class DialogShortcuts extends LitElement {
|
||||
html`<span
|
||||
>${shortcutKey === CTRL_CMD
|
||||
? isMac
|
||||
? "⌘"
|
||||
? html`<ha-svg-icon
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize("ui.panel.config.automation.editor.ctrl")
|
||||
: typeof shortcutKey === "string"
|
||||
? shortcutKey
|
||||
|
||||
@@ -152,7 +152,7 @@ export const provideHass = (
|
||||
for (const ent of ensureArray(newEntities)) {
|
||||
hass().entities[ent.entityId] = {
|
||||
entity_id: ent.entityId,
|
||||
name: ent.attributes.friendly_name || null,
|
||||
name: ent.name,
|
||||
icon: ent.icon,
|
||||
platform: "demo",
|
||||
labels: [],
|
||||
|
||||
@@ -50,7 +50,7 @@ export const ScrollableFadeMixin = <T extends Constructor<LitElement>>(
|
||||
/**
|
||||
* Safe area padding in pixels for the scrollable element.
|
||||
*/
|
||||
protected scrollFadeSafeAreaPadding = 4;
|
||||
protected scrollFadeSafeAreaPadding = 16;
|
||||
|
||||
/**
|
||||
* Scroll threshold in pixels for showing the fades.
|
||||
@@ -73,9 +73,6 @@ export const ScrollableFadeMixin = <T extends Constructor<LitElement>>(
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated?.(changedProperties);
|
||||
if (this.scrollableElement) {
|
||||
this._updateScrollableState(this.scrollableElement);
|
||||
}
|
||||
this._attachScrollableElement();
|
||||
}
|
||||
|
||||
@@ -86,8 +83,6 @@ export const ScrollableFadeMixin = <T extends Constructor<LitElement>>(
|
||||
|
||||
disconnectedCallback() {
|
||||
this._detachScrollableElement();
|
||||
this._contentScrolled = false;
|
||||
this._contentScrollable = false;
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
@@ -130,16 +125,16 @@ export const ScrollableFadeMixin = <T extends Constructor<LitElement>>(
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: var(--ha-space-2);
|
||||
height: var(--ha-space-4);
|
||||
pointer-events: none;
|
||||
transition: opacity 180ms ease-in-out;
|
||||
border-radius: var(--ha-border-radius-square);
|
||||
opacity: 0;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
var(--ha-color-shadow-scrollable-fade),
|
||||
var(--shadow-color),
|
||||
transparent
|
||||
);
|
||||
border-radius: var(--ha-border-radius-square);
|
||||
opacity: 0;
|
||||
}
|
||||
.fade-top {
|
||||
top: 0;
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import "../ha-config-section";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import {
|
||||
loadAreaRegistryDetailDialog,
|
||||
|
||||
@@ -2062,7 +2062,6 @@ class DialogAddAutomationElement
|
||||
|
||||
.content.column {
|
||||
flex-direction: column;
|
||||
gap: var(--ha-space-3);
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
|
||||
@@ -285,8 +285,6 @@ export class HaAutomationAddItems extends LitElement {
|
||||
border-radius: var(--ha-border-radius-md);
|
||||
background: var(--ha-color-fill-neutral-normal-resting);
|
||||
padding: 0 var(--ha-space-2) 0 var(--ha-space-1);
|
||||
border: var(--ha-border-width-sm) solid
|
||||
var(--ha-color-border-neutral-quiet);
|
||||
color: var(--ha-color-on-neutral-normal);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ class DialogAutomationSave extends LitElement implements HassDialog {
|
||||
`
|
||||
: nothing}
|
||||
${this._visibleOptionals.includes("description")
|
||||
? html`<ha-textarea
|
||||
? html` <ha-textarea
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.label"
|
||||
)}
|
||||
@@ -168,7 +168,6 @@ class DialogAutomationSave extends LitElement implements HassDialog {
|
||||
autogrow
|
||||
.value=${this._newDescription}
|
||||
.helper=${supportsMarkdownHelper(this.hass.localize)}
|
||||
helperPersistent
|
||||
@input=${this._valueChanged}
|
||||
></ha-textarea>`
|
||||
: nothing}
|
||||
@@ -571,7 +570,7 @@ ${dump(this._params.config)}
|
||||
ha-category-picker,
|
||||
ha-labels-picker,
|
||||
ha-area-picker,
|
||||
ha-chip-set:has(> ha-assist-chip) {
|
||||
ha-chip-set {
|
||||
margin-top: 16px;
|
||||
}
|
||||
ha-alert {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { css, 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 { computeDomain } from "../../../../../common/entity/compute_domain";
|
||||
import "../../../../../components/ha-checkbox";
|
||||
import "../../../../../components/ha-selector/ha-selector";
|
||||
import "../../../../../components/ha-settings-row";
|
||||
@@ -268,11 +269,8 @@ export class HaPlatformCondition extends LitElement {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const context: Record<string, any> = {};
|
||||
const context = {};
|
||||
for (const [context_key, data_key] of Object.entries(field.context)) {
|
||||
if (data_key === "target" && this.description?.target) {
|
||||
context.target_selector = this._targetSelector(this.description.target);
|
||||
}
|
||||
context[context_key] =
|
||||
data_key === "target"
|
||||
? this.condition.target
|
||||
@@ -380,7 +378,7 @@ export class HaPlatformCondition extends LitElement {
|
||||
return "";
|
||||
}
|
||||
return this.hass.localize(
|
||||
`component.${getConditionDomain(this.condition.condition)}.selector.${key}`
|
||||
`component.${computeDomain(this.condition.condition)}.selector.${key}`
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ import type { Entries, HomeAssistant, Route } from "../../../types";
|
||||
import { isMac } from "../../../util/is_mac";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import { showAssignCategoryDialog } from "../category/show-dialog-assign-category";
|
||||
import "../ha-config-section";
|
||||
import { showAutomationModeDialog } from "./automation-mode-dialog/show-dialog-automation-mode";
|
||||
import {
|
||||
type EntityRegistryUpdate,
|
||||
|
||||
@@ -115,8 +115,6 @@ import { showCategoryRegistryDetailDialog } from "../category/show-dialog-catego
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
|
||||
import { showNewAutomationDialog } from "./show-dialog-new-automation";
|
||||
import { getEntityVoiceAssistantsIds } from "../../../data/expose";
|
||||
import "../voice-assistants/expose/expose-assistant-icon";
|
||||
|
||||
type AutomationItem = AutomationEntity & {
|
||||
name: string;
|
||||
@@ -378,31 +376,6 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
||||
></ha-icon-button>
|
||||
`,
|
||||
},
|
||||
voice_assistants: {
|
||||
title: localize(
|
||||
"ui.panel.config.automation.picker.headers.voice_assistants"
|
||||
),
|
||||
type: "icon",
|
||||
defaultHidden: true,
|
||||
minWidth: "100px",
|
||||
maxWidth: "100px",
|
||||
template: (automation) => {
|
||||
const exposedToVoiceAssistantIds = getEntityVoiceAssistantsIds(
|
||||
this._entityReg,
|
||||
automation.entity_id
|
||||
);
|
||||
return html` ${exposedToVoiceAssistantIds.length !== 0
|
||||
? exposedToVoiceAssistantIds.map(
|
||||
(vaId) =>
|
||||
html` <voice-assistants-expose-assistant-icon
|
||||
.assistant=${vaId}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
</voice-assistants-expose-assistant-icon>`
|
||||
)
|
||||
: "—"}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
return columns;
|
||||
}
|
||||
|
||||
@@ -223,8 +223,6 @@ export class HaAutomationRowTargets extends LitElement {
|
||||
background: var(--ha-color-fill-neutral-normal-resting);
|
||||
padding: 0 var(--ha-space-2) 0 var(--ha-space-1);
|
||||
color: var(--ha-color-on-neutral-normal);
|
||||
border: var(--ha-border-width-sm) solid
|
||||
var(--ha-color-border-neutral-quiet);
|
||||
overflow: hidden;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { css, 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 { computeDomain } from "../../../../../common/entity/compute_domain";
|
||||
import "../../../../../components/ha-checkbox";
|
||||
import "../../../../../components/ha-selector/ha-selector";
|
||||
import "../../../../../components/ha-settings-row";
|
||||
@@ -304,11 +305,8 @@ export class HaPlatformTrigger extends LitElement {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const context: Record<string, any> = {};
|
||||
const context = {};
|
||||
for (const [context_key, data_key] of Object.entries(field.context)) {
|
||||
if (data_key === "target" && this.description?.target) {
|
||||
context.target_selector = this._targetSelector(this.description.target);
|
||||
}
|
||||
context[context_key] =
|
||||
data_key === "target"
|
||||
? this.trigger.target
|
||||
@@ -416,7 +414,7 @@ export class HaPlatformTrigger extends LitElement {
|
||||
return "";
|
||||
}
|
||||
return this.hass.localize(
|
||||
`component.${getTriggerDomain(this.trigger.trigger)}.selector.${key}`
|
||||
`component.${computeDomain(this.trigger.trigger)}.selector.${key}`
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import {
|
||||
mdiDotsVertical,
|
||||
mdiLocationEnter,
|
||||
mdiLocationExit,
|
||||
mdiRefresh,
|
||||
} from "@mdi/js";
|
||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical, mdiRefresh } from "@mdi/js";
|
||||
import type { HassEntities } from "home-assistant-js-websocket";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-bar";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-check-list-item";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-metric";
|
||||
import { extractApiErrorMessage } from "../../../data/hassio/common";
|
||||
import type {
|
||||
HassioSupervisorInfo,
|
||||
@@ -23,16 +26,13 @@ import {
|
||||
} from "../../../data/hassio/supervisor";
|
||||
import {
|
||||
checkForEntityUpdates,
|
||||
filterUpdateEntitiesParameterized,
|
||||
filterUpdateEntitiesWithInstall,
|
||||
} from "../../../data/update";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../dashboard/ha-config-updates";
|
||||
import { showJoinBetaDialog } from "./updates/show-dialog-join-beta";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
|
||||
@customElement("ha-config-section-updates")
|
||||
class HaConfigSectionUpdates extends LitElement {
|
||||
@@ -53,11 +53,7 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const canInstallUpdates = this._filterInstallableUpdateEntities(
|
||||
this.hass.states,
|
||||
this._showSkipped
|
||||
);
|
||||
const notInstallableUpdates = this._filterNotInstallableUpdateEntities(
|
||||
const canInstallUpdates = this._filterUpdateEntitiesWithInstall(
|
||||
this.hass.states,
|
||||
this._showSkipped
|
||||
);
|
||||
@@ -77,86 +73,57 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
.path=${mdiRefresh}
|
||||
@click=${this._checkUpdates}
|
||||
></ha-icon-button>
|
||||
<ha-dropdown @wa-select=${this._handleOverflowAction}>
|
||||
<ha-button-menu multi>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-dropdown-item
|
||||
type="checkbox"
|
||||
.checked=${this._showSkipped}
|
||||
value="show_skipped"
|
||||
<ha-check-list-item
|
||||
left
|
||||
@request-selected=${this._toggleSkipped}
|
||||
.selected=${this._showSkipped}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.updates.show_skipped")}
|
||||
</ha-dropdown-item>
|
||||
</ha-check-list-item>
|
||||
${this._supervisorInfo
|
||||
? html`
|
||||
<wa-divider></wa-divider>
|
||||
<ha-dropdown-item
|
||||
value="toggle_beta"
|
||||
<li divider role="separator"></li>
|
||||
<ha-list-item
|
||||
@request-selected=${this._toggleBeta}
|
||||
.disabled=${this._supervisorInfo.channel === "dev"}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${this._supervisorInfo.channel === "stable"
|
||||
? mdiLocationEnter
|
||||
: mdiLocationExit}
|
||||
slot="icon"
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.updates.${this._supervisorInfo.channel === "stable" ? "join" : "leave"}_beta`
|
||||
)}
|
||||
${this._supervisorInfo.channel === "stable"
|
||||
? this.hass.localize("ui.panel.config.updates.join_beta")
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.updates.leave_beta"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-list-item>
|
||||
`
|
||||
: nothing}
|
||||
</ha-dropdown>
|
||||
: ""}
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
<div class="content">
|
||||
${canInstallUpdates.length
|
||||
? html`
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
${canInstallUpdates.length
|
||||
? html`
|
||||
<ha-config-updates
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.updateEntities=${canInstallUpdates}
|
||||
.isInstallable=${true}
|
||||
showAll
|
||||
></ha-config-updates>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
: nothing}
|
||||
${notInstallableUpdates.length
|
||||
? html`
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
<ha-config-updates
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.updateEntities=${notInstallableUpdates}
|
||||
.isInstallable=${false}
|
||||
showAll
|
||||
></ha-config-updates>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
: nothing}
|
||||
${canInstallUpdates.length + notInstallableUpdates.length
|
||||
? nothing
|
||||
: html`
|
||||
<ha-card outlined>
|
||||
<div class="no-updates">
|
||||
${this.hass.localize("ui.panel.config.updates.no_updates")}
|
||||
</div>
|
||||
</ha-card>
|
||||
`}
|
||||
`
|
||||
: html`
|
||||
<div class="no-updates">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.updates.no_updates"
|
||||
)}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
@@ -166,19 +133,27 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
this._supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
||||
}
|
||||
|
||||
private _handleOverflowAction(
|
||||
ev: CustomEvent<{ item: { value: string } }>
|
||||
): void {
|
||||
if (ev.detail.item.value === "toggle_beta") {
|
||||
if (this._supervisorInfo!.channel === "stable") {
|
||||
showJoinBetaDialog(this, {
|
||||
join: () => this._setChannel("beta"),
|
||||
});
|
||||
} else {
|
||||
this._setChannel("stable");
|
||||
}
|
||||
} else if (ev.detail.item.value === "show_skipped") {
|
||||
this._showSkipped = !this._showSkipped;
|
||||
private _toggleSkipped(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (ev.detail.source !== "property") {
|
||||
return;
|
||||
}
|
||||
|
||||
this._showSkipped = !this._showSkipped;
|
||||
}
|
||||
|
||||
private async _toggleBeta(
|
||||
ev: CustomEvent<RequestSelectedDetail>
|
||||
): Promise<void> {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._supervisorInfo!.channel === "stable") {
|
||||
showJoinBetaDialog(this, {
|
||||
join: async () => this._setChannel("beta"),
|
||||
});
|
||||
} else {
|
||||
this._setChannel("stable");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,14 +177,9 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
checkForEntityUpdates(this, this.hass);
|
||||
}
|
||||
|
||||
private _filterInstallableUpdateEntities = memoizeOne(
|
||||
private _filterUpdateEntitiesWithInstall = memoizeOne(
|
||||
(entities: HassEntities, showSkipped: boolean) =>
|
||||
filterUpdateEntitiesParameterized(entities, showSkipped, false)
|
||||
);
|
||||
|
||||
private _filterNotInstallableUpdateEntities = memoizeOne(
|
||||
(entities: HassEntities, showSkipped: boolean) =>
|
||||
filterUpdateEntitiesParameterized(entities, showSkipped, true)
|
||||
filterUpdateEntitiesWithInstall(entities, showSkipped)
|
||||
);
|
||||
|
||||
static styles = css`
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
import type { UpdateEntity } from "../../../data/update";
|
||||
import {
|
||||
checkForEntityUpdates,
|
||||
filterUpdateEntitiesParameterized,
|
||||
filterUpdateEntitiesWithInstall,
|
||||
} from "../../../data/update";
|
||||
import {
|
||||
QuickBarMode,
|
||||
@@ -161,27 +161,24 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
total: 0,
|
||||
};
|
||||
|
||||
private _pages = memoizeOne(
|
||||
(cloudStatus, isCloudLoaded, hasExternalSettings) => [
|
||||
isCloudLoaded
|
||||
? [
|
||||
{
|
||||
component: "cloud",
|
||||
path: "/config/cloud",
|
||||
name: "Home Assistant Cloud",
|
||||
info: cloudStatus,
|
||||
iconPath: mdiCloudLock,
|
||||
iconColor: "#3B808E",
|
||||
translationKey: "cloud",
|
||||
},
|
||||
...configSections.dashboard,
|
||||
]
|
||||
: configSections.dashboard,
|
||||
hasExternalSettings ? configSections.dashboard_external_settings : [],
|
||||
configSections.dashboard_2,
|
||||
configSections.dashboard_3,
|
||||
]
|
||||
);
|
||||
private _pages = memoizeOne((cloudStatus, isCloudLoaded) => [
|
||||
isCloudLoaded
|
||||
? [
|
||||
{
|
||||
component: "cloud",
|
||||
path: "/config/cloud",
|
||||
name: "Home Assistant Cloud",
|
||||
info: cloudStatus,
|
||||
iconPath: mdiCloudLock,
|
||||
iconColor: "#3B808E",
|
||||
translationKey: "cloud",
|
||||
},
|
||||
...configSections.dashboard,
|
||||
]
|
||||
: configSections.dashboard,
|
||||
configSections.dashboard_2,
|
||||
configSections.dashboard_3,
|
||||
]);
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
@@ -206,7 +203,7 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const { updates: canInstallUpdates, total: totalUpdates } =
|
||||
this._filterUpdateEntitiesParameterized(
|
||||
this._filterUpdateEntitiesWithInstall(
|
||||
this.hass.states,
|
||||
this.hass.entities
|
||||
);
|
||||
@@ -291,7 +288,6 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
.narrow=${this.narrow}
|
||||
.total=${totalUpdates}
|
||||
.updateEntities=${canInstallUpdates}
|
||||
.isInstallable=${true}
|
||||
></ha-config-updates>
|
||||
${totalUpdates > canInstallUpdates.length
|
||||
? html`
|
||||
@@ -314,8 +310,7 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
: ""}
|
||||
${this._pages(
|
||||
this.cloudStatus,
|
||||
isComponentLoaded(this.hass, "cloud"),
|
||||
this.hass.auth.external?.config.hasSettingsScreen
|
||||
isComponentLoaded(this.hass, "cloud")
|
||||
).map((categoryPages) =>
|
||||
categoryPages.length === 0
|
||||
? nothing
|
||||
@@ -349,16 +344,14 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
showShortcutsDialog(this);
|
||||
}
|
||||
|
||||
private _filterUpdateEntitiesParameterized = memoizeOne(
|
||||
private _filterUpdateEntitiesWithInstall = memoizeOne(
|
||||
(
|
||||
entities: HomeAssistant["states"],
|
||||
entityRegistry: HomeAssistant["entities"]
|
||||
): { updates: UpdateEntity[]; total: number } => {
|
||||
const updates = filterUpdateEntitiesParameterized(
|
||||
entities,
|
||||
false,
|
||||
false
|
||||
).filter((entity) => !entityRegistry[entity.entity_id]?.hidden);
|
||||
const updates = filterUpdateEntitiesWithInstall(entities).filter(
|
||||
(entity) => !entityRegistry[entity.entity_id]?.hidden
|
||||
);
|
||||
|
||||
return {
|
||||
updates: updates.slice(0, updates.length === 3 ? updates.length : 2),
|
||||
|
||||
@@ -32,8 +32,6 @@ class HaConfigUpdates extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property({ type: Number }) public total?: number;
|
||||
|
||||
@property({ attribute: false }) public isInstallable = true;
|
||||
|
||||
@state() private _devices?: DeviceRegistryEntry[];
|
||||
|
||||
@state() private _entities?: EntityRegistryEntry[];
|
||||
@@ -91,16 +89,9 @@ class HaConfigUpdates extends SubscribeMixin(LitElement) {
|
||||
|
||||
return html`
|
||||
<div class="title" role="heading" aria-level="2">
|
||||
${this.isInstallable
|
||||
? this.hass.localize("ui.panel.config.updates.title", {
|
||||
count: this.total || this.updateEntities.length,
|
||||
})
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.updates.title_not_installable",
|
||||
{
|
||||
count: this.total || this.updateEntities.length,
|
||||
}
|
||||
)}
|
||||
${this.hass.localize("ui.panel.config.updates.title", {
|
||||
count: this.total || this.updateEntities.length,
|
||||
})}
|
||||
</div>
|
||||
<ha-md-list>
|
||||
${updates.map((entity) => {
|
||||
|
||||
@@ -64,7 +64,6 @@ import "../../../components/ha-filter-floor-areas";
|
||||
import "../../../components/ha-filter-integrations";
|
||||
import "../../../components/ha-filter-labels";
|
||||
import "../../../components/ha-filter-states";
|
||||
import "../../../components/ha-filter-voice-assistants";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-md-divider";
|
||||
@@ -116,8 +115,6 @@ import { isHelperDomain } from "../helpers/const";
|
||||
import "../integrations/ha-integration-overflow-menu";
|
||||
import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog";
|
||||
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
|
||||
import { getEntityVoiceAssistantsIds } from "../../../data/expose";
|
||||
import "../voice-assistants/expose/expose-assistant-icon";
|
||||
|
||||
export interface StateEntity extends Omit<
|
||||
EntityRegistryEntry,
|
||||
@@ -496,31 +493,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
template: (entry) =>
|
||||
entry.label_entries.map((lbl) => lbl.name).join(" "),
|
||||
},
|
||||
voice_assistants: {
|
||||
title: localize(
|
||||
"ui.panel.config.entities.picker.headers.voice_assistants"
|
||||
),
|
||||
type: "icon",
|
||||
defaultHidden: true,
|
||||
minWidth: "100px",
|
||||
maxWidth: "100px",
|
||||
template: (entry) => {
|
||||
const exposedToVoiceAssistantIds = getEntityVoiceAssistantsIds(
|
||||
this._entities,
|
||||
entry.entity_id
|
||||
);
|
||||
return html` ${exposedToVoiceAssistantIds.length !== 0
|
||||
? exposedToVoiceAssistantIds.map(
|
||||
(vaId) =>
|
||||
html` <voice-assistants-expose-assistant-icon
|
||||
.assistant=${vaId}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
</voice-assistants-expose-assistant-icon>`
|
||||
)
|
||||
: "—"}`;
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
@@ -665,16 +637,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
filteredEntities = filteredEntities.filter((entity) =>
|
||||
entity.labels.some((lbl) => (filter as string[]).includes(lbl))
|
||||
);
|
||||
} else if (
|
||||
key === "ha-filter-voice-assistants" &&
|
||||
Array.isArray(filter) &&
|
||||
filter.length
|
||||
) {
|
||||
filteredEntities = filteredEntities.filter((entity) =>
|
||||
getEntityVoiceAssistantsIds(this._entities, entity.entity_id).some(
|
||||
(va) => (filter as string[]).includes(va)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1114,15 +1076,6 @@ ${
|
||||
.narrow=${this.narrow}
|
||||
@expanded-changed=${this._filterExpanded}
|
||||
></ha-filter-labels>
|
||||
<ha-filter-voice-assistants
|
||||
.hass=${this.hass}
|
||||
.value=${this._filters["ha-filter-voice-assistants"]}
|
||||
@data-table-filter-changed=${this._filterChanged}
|
||||
slot="filter-pane"
|
||||
.expanded=${this._expandedFilter === "ha-filter-voice-assistants"}
|
||||
.narrow=${this.narrow}
|
||||
@expanded-changed=${this._filterExpanded}
|
||||
></ha-filter-voice-assistants>
|
||||
${
|
||||
includeAddDeviceFab
|
||||
? html`<ha-fab
|
||||
@@ -1175,7 +1128,6 @@ ${
|
||||
const subEntry = this._searchParms.get("sub_entry");
|
||||
const device = this._searchParms.get("device");
|
||||
const label = this._searchParms.get("label");
|
||||
const voiceAssistant = this._searchParms.get("voice_assistant");
|
||||
|
||||
if (!domain && !configEntry && !label && !device) {
|
||||
return;
|
||||
@@ -1188,7 +1140,6 @@ ${
|
||||
"ha-filter-integrations": domain ? [domain] : [],
|
||||
"ha-filter-devices": device ? [device] : [],
|
||||
"ha-filter-labels": label ? [label] : [],
|
||||
"ha-filter-voice-assistants": voiceAssistant ? [voiceAssistant] : [],
|
||||
config_entry: configEntry ? [configEntry] : [],
|
||||
sub_entry: subEntry ? [subEntry] : [],
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
mdiScrewdriver,
|
||||
mdiScriptText,
|
||||
mdiShape,
|
||||
mdiLan,
|
||||
mdiSofa,
|
||||
mdiTools,
|
||||
mdiUpdate,
|
||||
@@ -105,24 +106,7 @@ export const configSections: Record<string, PageNavigation[]> = {
|
||||
iconColor: "#3263C3",
|
||||
},
|
||||
],
|
||||
dashboard_external_settings: [
|
||||
{
|
||||
path: "#external-app-configuration",
|
||||
translationKey: "companion",
|
||||
iconPath: mdiCellphoneCog,
|
||||
iconColor: "#8E24AA",
|
||||
},
|
||||
],
|
||||
dashboard_2: [
|
||||
{
|
||||
path: "/config/matter",
|
||||
name: "Matter",
|
||||
iconPath:
|
||||
"M7.228375 6.41685c0.98855 0.80195 2.16365 1.3412 3.416275 1.56765V1.30093l1.3612 -0.7854275 1.360125 0.7854275V7.9845c1.252875 -0.226675 2.4283 -0.765875 3.41735 -1.56765l2.471225 1.4293c-4.019075 3.976275 -10.490025 3.976275 -14.5091 0l2.482925 -1.4293Zm3.00335 17.067575c1.43325 -5.47035 -1.8052 -11.074775 -7.2604 -12.564675v2.859675c1.189125 0.455 2.244125 1.202875 3.0672 2.174275L0.25 19.2955v1.5719l1.3611925 0.781175L7.39865 18.3068c0.430175 1.19825 0.550625 2.48575 0.35015 3.743l2.482925 1.434625ZM21.034 10.91975c-5.452225 1.4932 -8.6871 7.09635 -7.254025 12.564675l2.47655 -1.43035c-0.200025 -1.257275 -0.079575 -2.544675 0.35015 -3.743025l5.7832 3.337525L23.75 20.86315V19.2955L17.961475 15.9537c0.8233 -0.97115 1.878225 -1.718975 3.0672 -2.174275l0.005325 -2.859675Z",
|
||||
iconColor: "#2458B3",
|
||||
component: "matter",
|
||||
translationKey: "matter",
|
||||
},
|
||||
{
|
||||
path: "/config/zha",
|
||||
name: "Zigbee",
|
||||
@@ -142,8 +126,7 @@ export const configSections: Record<string, PageNavigation[]> = {
|
||||
{
|
||||
path: "/knx",
|
||||
name: "KNX",
|
||||
iconPath:
|
||||
"M 3.9861338,14.261456 3.7267552,13.934877 6.3179131,11.306266 H 4.466374 l -2.6385205,2.68258 V 11.312882 H 0.00440574 L 0,17.679803 l 1.8278535,5.43e-4 v -1.818482 l 0.7225444,-0.732459 2.1373588,2.543782 2.1869324,-5.44e-4 M 24,17.680369 21.809238,17.669359 19.885559,15.375598 17.640262,17.68037 h -1.828407 l 3.236048,-3.302138 -2.574075,-3.067547 2.135161,0.0016 1.610309,1.87687 1.866403,-1.87687 h 1.828429 l -2.857742,2.87478 m -10.589867,-2.924898 2.829625,3.990552 -0.01489,-3.977887 1.811889,-0.0044 0.0011,6.357564 -2.093314,-5.44e-4 -2.922133,-3.947594 -0.0314,3.947594 H 8.2581097 V 11.261677 M 11.971203,6.3517488 c 0,0 2.800714,-0.093203 6.172001,1.0812045 3.462393,1.0898845 5.770926,3.4695627 5.770926,3.4695627 l -1.823898,-5.43e-4 C 22.088532,10.900273 20.577938,9.4271528 17.660223,8.5024618 15.139256,7.703366 12.723057,7.645835 12.111178,7.6449876 l -9.71e-4,0.0011 c 0,0 -0.0259,-6.4e-4 -0.07527,-9.714e-4 -0.04726,3.33e-4 -0.07201,9.714e-4 -0.07201,9.714e-4 v -0.00113 C 11.337007,7.6453728 8.8132091,7.7001736 6.2821829,8.5024618 3.3627914,9.4276738 1.8521646,10.901973 1.8521646,10.901973 l -1.82398708,5.43e-4 C 0.03128403,10.899322 2.339143,8.5221038 5.799224,7.4329533 9.170444,6.2585642 11.971203,6.3517488 11.971203,6.3517488 Z",
|
||||
iconPath: mdiLan,
|
||||
iconColor: "#4EAA66",
|
||||
component: "knx",
|
||||
translationKey: "knx",
|
||||
@@ -152,7 +135,10 @@ export const configSections: Record<string, PageNavigation[]> = {
|
||||
path: "/config/thread",
|
||||
name: "Thread",
|
||||
iconPath:
|
||||
"m 17.126982,8.0730792 c 0,-0.7297242 -0.593746,-1.32357 -1.323637,-1.32357 -0.729454,0 -1.323199,0.5938458 -1.323199,1.32357 v 1.3234242 l 1.323199,1.458e-4 c 0.729891,0 1.323637,-0.5937006 1.323637,-1.32357 z M 11.999709,0 C 5.3829818,0 0,5.3838955 0,12.001455 0,18.574352 5.3105455,23.927406 11.865164,24 V 12.012075 l -3.9275642,-2.91e-4 c -1.1669814,0 -2.1169453,0.949979 -2.1169453,2.118323 0,1.16718 0.9499639,2.116868 2.1169453,2.116868 v 2.615717 c -2.6093089,0 -4.732218,-2.12327 -4.732218,-4.732585 0,-2.61048 2.1229091,-4.7343308 4.732218,-4.7343308 l 3.9275642,5.82e-4 v -1.323279 c 0,-2.172296 1.766691,-3.9395777 3.938181,-3.9395777 2.171928,0 3.9392,1.7672817 3.9392,3.9395777 0,2.1721498 -1.767272,3.9395768 -3.9392,3.9395768 l -1.323199,-1.45e-4 V 23.744102 C 19.911127,22.597726 24,17.768833 24,12.001455 24,5.3838955 18.616727,0 11.999709,0 Z",
|
||||
"M82.498,0C37.008,0,0,37.008,0,82.496c0,45.181,36.51,81.977,81.573,82.476V82.569l-27.002-0.002 c-8.023,0-14.554,6.53-14.554,14.561c0,8.023,6.531,14.551,14.554,14.551v17.98c-17.939,0-32.534-14.595-32.534-32.531 c0-17.944,14.595-32.543,32.534-32.543l27.002,0.004v-9.096c0-14.932,12.146-27.08,27.075-27.08 c14.932,0,27.082,12.148,27.082,27.08c0,14.931-12.15,27.08-27.082,27.08l-9.097-0.001v80.641 C136.889,155.333,165,122.14,165,82.496C165,37.008,127.99,0,82.498,0z",
|
||||
iconSecondaryPath:
|
||||
"M117.748 55.493C117.748 50.477 113.666 46.395 108.648 46.395C103.633 46.395 99.551 50.477 99.551 55.493V64.59L108.648 64.591C113.666 64.591 117.748 60.51 117.748 55.493Z",
|
||||
iconViewBox: "0 0 165 165",
|
||||
iconColor: "#ED7744",
|
||||
component: "thread",
|
||||
translationKey: "thread",
|
||||
@@ -169,7 +155,8 @@ export const configSections: Record<string, PageNavigation[]> = {
|
||||
path: "/insteon",
|
||||
name: "Insteon",
|
||||
iconPath:
|
||||
"m 12.001571,6.3842473 h 0.02973 c 3.652189,0 6.767389,-2.29456 7.987462,-5.5177193 L 15.389382,0 Z m 0,0 h -0.02972 c -3.6522186,0 -6.7673314,-2.2918546 -7.9874477,-5.5177193 h -0.00271 L 8.6111273,0 Z M 6.3840436,11.999287 v -0.02972 c 0,-3.6524074 -2.2944727,-6.7675928 -5.51754469,-7.9877383 L 0,8.6114473 Z m 0,0 v 0.02964 c 0,3.652378 -2.2917818,6.767578 -5.51754469,7.987796 v 0.0026 L 0,15.389818 Z M 24,8.6114473 23.133527,3.9818327 v 0.00269 C 19.907636,5.2046836 17.616,8.3198691 17.616,11.972276 v 0.02966 0.02972 0.0027 c 0,3.65232 2.2944,6.76752 5.517527,7.987738 L 24,15.392436 17.616,12.001935 Z M 20.018618,23.133527 15.389091,24 11.99872,17.615709 h 0.02964 c 3.652218,0 6.767418,2.291927 7.987491,5.517818 z M 11.99872,17.615709 8.6082618,24 3.9788364,23.133527 C 5.1989527,19.9104 8.3140655,17.615709 11.966284,17.615709 h 0.0027 z",
|
||||
"M82.5108 43.8917H82.7152C107.824 43.8917 129.241 28.1166 137.629 5.95738L105.802 0L82.5108 43.8917ZM82.5108 43.8917H82.3065C57.1975 43.8917 35.7811 28.1352 27.3928 5.95738H27.3742L59.2015 0L82.5108 43.8917ZM43.8903 82.4951V82.2908C43.8903 57.1805 28.1158 35.7636 5.95718 27.3751L0 59.2037L43.8903 82.4951ZM43.8903 82.4951V82.6989C43.8903 107.809 28.1343 129.226 5.95718 137.615V137.633L0 105.805L43.8903 82.4951ZM165 59.2037L159.043 27.3751V27.3936C136.865 35.7822 121.11 57.1991 121.11 82.3094V82.5133V82.7176V82.7363C121.11 107.846 136.884 129.263 159.043 137.652L165 105.823L121.11 82.5133L165 59.2037ZM137.628 159.043L105.8 165L82.4912 121.108H82.695C107.804 121.108 129.221 136.865 137.609 159.043H137.628ZM82.4912 121.108L59.1818 165L27.3545 159.043C35.7428 136.884 57.1592 121.108 82.2682 121.108H82.2868H82.4912Z",
|
||||
iconViewBox: "0 0 165 165",
|
||||
iconColor: "#E4002C",
|
||||
component: "insteon",
|
||||
translationKey: "insteon",
|
||||
@@ -190,6 +177,12 @@ export const configSections: Record<string, PageNavigation[]> = {
|
||||
iconColor: "#5A87FA",
|
||||
component: ["person", "users"],
|
||||
},
|
||||
{
|
||||
path: "#external-app-configuration",
|
||||
translationKey: "companion",
|
||||
iconPath: mdiCellphoneCog,
|
||||
iconColor: "#8E24AA",
|
||||
},
|
||||
{
|
||||
path: "/config/system",
|
||||
translationKey: "system",
|
||||
|
||||
@@ -260,6 +260,8 @@ export class DialogHelperDetail extends LitElement {
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
class=${classMap({ "button-left": !this._domain })}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.hideActions=${!this._domain}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
|
||||
@@ -105,7 +105,7 @@ class HaTimerForm extends LitElement {
|
||||
<ha-checkbox
|
||||
.configValue=${"restore"}
|
||||
.checked=${this._restore}
|
||||
@change=${this._toggleRestore}
|
||||
@click=${this._toggleRestore}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
</ha-checkbox>
|
||||
@@ -135,8 +135,11 @@ class HaTimerForm extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _toggleRestore(ev) {
|
||||
this._restore = ev.target.checked;
|
||||
private _toggleRestore() {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
this._restore = !this._restore;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, restore: this._restore },
|
||||
});
|
||||
|
||||
@@ -122,8 +122,6 @@ import "../integrations/ha-integration-overflow-menu";
|
||||
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
|
||||
import { isHelperDomain, type HelperDomain } from "./const";
|
||||
import { showHelperDetailDialog } from "./show-dialog-helper-detail";
|
||||
import { getEntityVoiceAssistantsIds } from "../../../data/expose";
|
||||
import "../voice-assistants/expose/expose-assistant-icon";
|
||||
|
||||
interface HelperItem {
|
||||
id: string;
|
||||
@@ -225,8 +223,6 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _diagnosticHandlers?: Record<string, boolean>;
|
||||
|
||||
@state() private _searchParms = new URLSearchParams(window.location.search);
|
||||
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
key: "helpers-table-filters",
|
||||
@@ -484,32 +480,6 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
</ha-icon-overflow-menu>
|
||||
`,
|
||||
},
|
||||
voice_assistants: {
|
||||
title: localize(
|
||||
"ui.panel.config.helpers.picker.headers.voice_assistants"
|
||||
),
|
||||
type: "icon",
|
||||
defaultHidden: true,
|
||||
minWidth: "100px",
|
||||
maxWidth: "100px",
|
||||
template: (helper) => {
|
||||
const exposedToVoiceAssistantIds = getEntityVoiceAssistantsIds(
|
||||
this._entityReg,
|
||||
helper.entity_id
|
||||
);
|
||||
return html` ${exposedToVoiceAssistantIds.length !== 0
|
||||
? exposedToVoiceAssistantIds.map(
|
||||
(vaId) => html`
|
||||
<voice-assistants-expose-assistant-icon
|
||||
.assistant=${vaId}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
</voice-assistants-expose-assistant-icon>
|
||||
`
|
||||
)
|
||||
: "—"}`;
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1023,50 +993,6 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
this._filteredStateItems = items ? [...items] : undefined;
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
window.addEventListener("location-changed", this._locationChanged);
|
||||
window.addEventListener("popstate", this._popState);
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener("location-changed", this._locationChanged);
|
||||
window.removeEventListener("popstate", this._popState);
|
||||
}
|
||||
|
||||
private _locationChanged = () => {
|
||||
if (window.location.search.substring(1) !== this._searchParms.toString()) {
|
||||
this._searchParms = new URLSearchParams(window.location.search);
|
||||
this._setFiltersFromUrl();
|
||||
}
|
||||
};
|
||||
|
||||
private _popState = () => {
|
||||
if (window.location.search.substring(1) !== this._searchParms.toString()) {
|
||||
this._searchParms = new URLSearchParams(window.location.search);
|
||||
this._setFiltersFromUrl();
|
||||
}
|
||||
};
|
||||
|
||||
private _setFiltersFromUrl() {
|
||||
const device = this._searchParms.get("device");
|
||||
const label = this._searchParms.get("label");
|
||||
const category = this._searchParms.get("category");
|
||||
|
||||
if (!category && !label && !device) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._filter = history.state?.filter || "";
|
||||
|
||||
this._filters = {
|
||||
"ha-filter-devices": device ? [device] : [],
|
||||
"ha-filter-labels": label ? [label] : [],
|
||||
"ha-filter-categories": category ? [category] : [],
|
||||
};
|
||||
}
|
||||
|
||||
private _clearFilter() {
|
||||
this._filters = {};
|
||||
this._filteredItems = {};
|
||||
@@ -1167,7 +1093,7 @@ ${rejected
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._setFiltersFromUrl();
|
||||
|
||||
this._fetchEntitySources();
|
||||
|
||||
if (isComponentLoaded(this.hass, "diagnostics")) {
|
||||
@@ -1280,10 +1206,6 @@ ${rejected
|
||||
protected willUpdate(changedProps: PropertyValues) {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!this.hasUpdated) {
|
||||
this._setFiltersFromUrl();
|
||||
}
|
||||
|
||||
if (!this._entityEntries || !this._configEntries) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -327,6 +327,7 @@ class AddIntegrationDialog extends LitElement {
|
||||
return html`<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
scrimClickAction
|
||||
hideActions
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
|
||||
@@ -23,6 +23,7 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isDevVersion } from "../../../common/config/version";
|
||||
import { computeDeviceNameDisplay } from "../../../common/entity/compute_device_name";
|
||||
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||
@@ -212,7 +213,10 @@ class HaConfigEntryRow extends LitElement {
|
||||
? html`<ha-button slot="end" @click=${this._handleEnable}>
|
||||
${this.hass.localize("ui.common.enable")}
|
||||
</ha-button>`
|
||||
: configPanel && !stateText
|
||||
: configPanel &&
|
||||
(item.domain !== "matter" ||
|
||||
isDevVersion(this.hass.config.version)) &&
|
||||
!stateText
|
||||
? html`<a
|
||||
slot="end"
|
||||
href=${`/${configPanel}?config_entry=${item.entry_id}`}
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import { mdiCogOutline } from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../../components/ha-alert";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-list";
|
||||
import "../../../../../components/ha-list-item";
|
||||
|
||||
import "../../../../../components/ha-code-editor";
|
||||
import "../../../../../components/ha-formfield";
|
||||
import "../../../../../components/ha-switch";
|
||||
import "../../../../../components/ha-button";
|
||||
import { getConfigEntries } from "../../../../../data/config_entries";
|
||||
import { showOptionsFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-options-flow";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import {
|
||||
subscribeBluetoothConnectionAllocations,
|
||||
subscribeBluetoothScannerState,
|
||||
subscribeBluetoothScannersDetails,
|
||||
} from "../../../../../data/bluetooth";
|
||||
import type {
|
||||
BluetoothAllocationsData,
|
||||
BluetoothScannerState,
|
||||
@@ -16,17 +23,10 @@ import type {
|
||||
HaScannerType,
|
||||
} from "../../../../../data/bluetooth";
|
||||
import {
|
||||
subscribeBluetoothConnectionAllocations,
|
||||
subscribeBluetoothScannerState,
|
||||
subscribeBluetoothScannersDetails,
|
||||
} from "../../../../../data/bluetooth";
|
||||
import type { ConfigEntry } from "../../../../../data/config_entries";
|
||||
import { getConfigEntries } from "../../../../../data/config_entries";
|
||||
import type { DeviceRegistryEntry } from "../../../../../data/device/device_registry";
|
||||
import { showOptionsFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-options-flow";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
getValueInPercentage,
|
||||
roundWithOneDecimal,
|
||||
} from "../../../../../util/calculate";
|
||||
import "../../../../../components/ha-metric";
|
||||
|
||||
@customElement("bluetooth-config-dashboard")
|
||||
export class BluetoothConfigDashboard extends LitElement {
|
||||
@@ -34,14 +34,18 @@ export class BluetoothConfigDashboard extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@state() private _configEntries: ConfigEntry[] = [];
|
||||
|
||||
@state() private _connectionAllocationData: BluetoothAllocationsData[] = [];
|
||||
|
||||
@state() private _scannerStates: Record<string, BluetoothScannerState> = {};
|
||||
@state() private _connectionAllocationsError?: string;
|
||||
|
||||
@state() private _scannerState?: BluetoothScannerState;
|
||||
|
||||
@state() private _scannerDetails?: BluetoothScannersDetails;
|
||||
|
||||
private _configEntry = new URLSearchParams(window.location.search).get(
|
||||
"config_entry"
|
||||
);
|
||||
|
||||
private _unsubConnectionAllocations?: (() => Promise<void>) | undefined;
|
||||
|
||||
private _unsubScannerState?: (() => Promise<void>) | undefined;
|
||||
@@ -51,44 +55,41 @@ export class BluetoothConfigDashboard extends LitElement {
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
if (this.hass) {
|
||||
this._loadConfigEntries();
|
||||
this._subscribeBluetoothConnectionAllocations();
|
||||
this._subscribeBluetoothScannerState();
|
||||
this._subscribeScannerDetails();
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadConfigEntries(): Promise<void> {
|
||||
this._configEntries = await getConfigEntries(this.hass, {
|
||||
domain: "bluetooth",
|
||||
});
|
||||
}
|
||||
|
||||
private async _subscribeBluetoothConnectionAllocations(): Promise<void> {
|
||||
if (this._unsubConnectionAllocations) {
|
||||
if (this._unsubConnectionAllocations || !this._configEntry) {
|
||||
return;
|
||||
}
|
||||
this._unsubConnectionAllocations =
|
||||
await subscribeBluetoothConnectionAllocations(
|
||||
this.hass.connection,
|
||||
(data) => {
|
||||
this._connectionAllocationData = data;
|
||||
}
|
||||
);
|
||||
try {
|
||||
this._unsubConnectionAllocations =
|
||||
await subscribeBluetoothConnectionAllocations(
|
||||
this.hass.connection,
|
||||
(data) => {
|
||||
this._connectionAllocationData = data;
|
||||
},
|
||||
this._configEntry
|
||||
);
|
||||
} catch (err: any) {
|
||||
this._unsubConnectionAllocations = undefined;
|
||||
this._connectionAllocationsError = err.message;
|
||||
}
|
||||
}
|
||||
|
||||
private async _subscribeBluetoothScannerState(): Promise<void> {
|
||||
if (this._unsubScannerState) {
|
||||
if (this._unsubScannerState || !this._configEntry) {
|
||||
return;
|
||||
}
|
||||
this._unsubScannerState = await subscribeBluetoothScannerState(
|
||||
this.hass.connection,
|
||||
(scannerState) => {
|
||||
this._scannerStates = {
|
||||
...this._scannerStates,
|
||||
[scannerState.source]: scannerState,
|
||||
};
|
||||
}
|
||||
this._scannerState = scannerState;
|
||||
},
|
||||
this._configEntry
|
||||
);
|
||||
}
|
||||
|
||||
@@ -121,19 +122,31 @@ export class BluetoothConfigDashboard extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
// Get scanner type to determine if options button should be shown
|
||||
const scannerDetails =
|
||||
this._scannerState && this._scannerDetails?.[this._scannerState.source];
|
||||
const scannerType: HaScannerType =
|
||||
scannerDetails?.scanner_type ?? "unknown";
|
||||
const isRemoteScanner = scannerType === "remote";
|
||||
|
||||
return html`
|
||||
<hass-subpage
|
||||
header=${this.hass.localize("ui.panel.config.bluetooth.title")}
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
<hass-subpage .narrow=${this.narrow} .hass=${this.hass}>
|
||||
<div class="content">
|
||||
<ha-card
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.settings_title"
|
||||
)}
|
||||
>
|
||||
<ha-list>${this._renderAdaptersList()}</ha-list>
|
||||
<div class="card-content">${this._renderScannerState()}</div>
|
||||
${!isRemoteScanner
|
||||
? html`<div class="card-actions">
|
||||
<ha-button @click=${this._openOptionFlow}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.option_flow"
|
||||
)}</ha-button
|
||||
>
|
||||
</div>`
|
||||
: nothing}
|
||||
</ha-card>
|
||||
<ha-card
|
||||
.header=${this.hass.localize(
|
||||
@@ -170,11 +183,7 @@ export class BluetoothConfigDashboard extends LitElement {
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.connection_slot_allocations_monitor_description"
|
||||
)}
|
||||
</p>
|
||||
${this._renderConnectionAllocations()}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-button
|
||||
@@ -192,90 +201,13 @@ export class BluetoothConfigDashboard extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderAdaptersList() {
|
||||
if (this._configEntries.length === 0) {
|
||||
return html`<ha-list-item noninteractive>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.no_scanner_state_available"
|
||||
)}
|
||||
</ha-list-item>`;
|
||||
}
|
||||
|
||||
// Build source to device mapping (same as visualization)
|
||||
const sourceDevices: Record<string, DeviceRegistryEntry> = {};
|
||||
Object.values(this.hass.devices).forEach((device) => {
|
||||
const btConnection = device.connections.find(
|
||||
(connection) => connection[0] === "bluetooth"
|
||||
);
|
||||
if (btConnection) {
|
||||
sourceDevices[btConnection[1]] = device;
|
||||
}
|
||||
});
|
||||
|
||||
return this._configEntries.map((entry) => {
|
||||
// Find scanner by matching device's config_entries to this entry
|
||||
const scannerDetails = this._scannerDetails
|
||||
? Object.values(this._scannerDetails).find((d) => {
|
||||
const device = sourceDevices[d.source];
|
||||
return device?.config_entries.includes(entry.entry_id);
|
||||
})
|
||||
: undefined;
|
||||
const scannerState = scannerDetails
|
||||
? this._scannerStates[scannerDetails.source]
|
||||
: undefined;
|
||||
const scannerType: HaScannerType =
|
||||
scannerDetails?.scanner_type ?? "unknown";
|
||||
const isRemoteScanner = scannerType === "remote";
|
||||
const hasMismatch =
|
||||
scannerState &&
|
||||
scannerState.current_mode !== scannerState.requested_mode;
|
||||
|
||||
// Find allocation data for this scanner
|
||||
const allocations = scannerDetails
|
||||
? this._connectionAllocationData.find(
|
||||
(a) => a.source === scannerDetails.source
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const secondaryText = this._formatScannerModeText(scannerState);
|
||||
|
||||
return html`
|
||||
<ha-list-item twoline hasMeta noninteractive>
|
||||
<span>${entry.title}</span>
|
||||
<span slot="secondary">
|
||||
${secondaryText}${allocations
|
||||
? allocations.slots > 0
|
||||
? ` · ${allocations.slots - allocations.free}/${allocations.slots} ${this.hass.localize("ui.panel.config.bluetooth.active_connections")}`
|
||||
: ` · ${this.hass.localize("ui.panel.config.bluetooth.no_connection_slots")}`
|
||||
: nothing}
|
||||
</span>
|
||||
${!isRemoteScanner
|
||||
? html`<ha-icon-button
|
||||
slot="meta"
|
||||
.path=${mdiCogOutline}
|
||||
.entry=${entry}
|
||||
@click=${this._openOptionFlow}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.option_flow"
|
||||
)}
|
||||
></ha-icon-button>`
|
||||
: nothing}
|
||||
</ha-list-item>
|
||||
${hasMismatch && scannerDetails
|
||||
? this._renderScannerMismatchWarning(
|
||||
entry.title,
|
||||
scannerState,
|
||||
scannerType
|
||||
)
|
||||
: nothing}
|
||||
`;
|
||||
});
|
||||
}
|
||||
private _getUsedAllocations = (used: number, total: number) =>
|
||||
roundWithOneDecimal(getValueInPercentage(used, 0, total));
|
||||
|
||||
private _renderScannerMismatchWarning(
|
||||
name: string,
|
||||
scannerState: BluetoothScannerState,
|
||||
scannerType: HaScannerType
|
||||
scannerType: HaScannerType,
|
||||
formatMode: (mode: string | null) => string
|
||||
) {
|
||||
const instructions: string[] = [];
|
||||
|
||||
@@ -306,9 +238,8 @@ export class BluetoothConfigDashboard extends LitElement {
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.scanner_mode_mismatch",
|
||||
{
|
||||
name: name,
|
||||
requested: this._formatMode(scannerState.requested_mode),
|
||||
current: this._formatMode(scannerState.current_mode),
|
||||
requested: formatMode(scannerState.requested_mode),
|
||||
current: formatMode(scannerState.current_mode),
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
@@ -318,59 +249,127 @@ export class BluetoothConfigDashboard extends LitElement {
|
||||
</ha-alert>`;
|
||||
}
|
||||
|
||||
private _formatMode(mode: string | null): string {
|
||||
switch (mode) {
|
||||
case null:
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.bluetooth.scanning_mode_none"
|
||||
);
|
||||
case "active":
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.bluetooth.scanning_mode_active"
|
||||
);
|
||||
case "passive":
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.bluetooth.scanning_mode_passive"
|
||||
);
|
||||
default:
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
|
||||
private _formatModeLabel(mode: string | null): string {
|
||||
switch (mode) {
|
||||
case null:
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.bluetooth.scanning_mode_none_label"
|
||||
);
|
||||
case "active":
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.bluetooth.scanning_mode_active_label"
|
||||
);
|
||||
case "passive":
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.bluetooth.scanning_mode_passive_label"
|
||||
);
|
||||
default:
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
|
||||
private _formatScannerModeText(
|
||||
scannerState: BluetoothScannerState | undefined
|
||||
): string {
|
||||
if (!scannerState) {
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.bluetooth.scanner_state_unknown"
|
||||
);
|
||||
private _renderScannerState() {
|
||||
if (!this._configEntry || !this._scannerState) {
|
||||
return html`<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.no_scanner_state_available"
|
||||
)}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
return this._formatModeLabel(scannerState.current_mode);
|
||||
const scannerState = this._scannerState;
|
||||
// Find the scanner details for this source
|
||||
const scannerDetails = this._scannerDetails?.[scannerState.source];
|
||||
const scannerType: HaScannerType =
|
||||
scannerDetails?.scanner_type ?? "unknown";
|
||||
|
||||
const formatMode = (mode: string | null) => {
|
||||
switch (mode) {
|
||||
case null:
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.bluetooth.scanning_mode_none"
|
||||
);
|
||||
case "active":
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.bluetooth.scanning_mode_active"
|
||||
);
|
||||
case "passive":
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.bluetooth.scanning_mode_passive"
|
||||
);
|
||||
default:
|
||||
return mode; // Fallback for unknown modes
|
||||
}
|
||||
};
|
||||
|
||||
return html`
|
||||
<div class="scanner-state">
|
||||
<div class="state-row">
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.current_scanning_mode"
|
||||
)}:</span
|
||||
>
|
||||
<span class="state-value"
|
||||
>${formatMode(scannerState.current_mode)}</span
|
||||
>
|
||||
</div>
|
||||
<div class="state-row">
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.requested_scanning_mode"
|
||||
)}:</span
|
||||
>
|
||||
<span class="state-value"
|
||||
>${formatMode(scannerState.requested_mode)}</span
|
||||
>
|
||||
</div>
|
||||
${scannerState.current_mode !== scannerState.requested_mode
|
||||
? this._renderScannerMismatchWarning(
|
||||
scannerState,
|
||||
scannerType,
|
||||
formatMode
|
||||
)
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _openOptionFlow(ev: Event) {
|
||||
const button = ev.currentTarget as HTMLElement & { entry: ConfigEntry };
|
||||
showOptionsFlowDialog(this, button.entry);
|
||||
private _renderConnectionAllocations() {
|
||||
if (this._connectionAllocationsError) {
|
||||
return html`<ha-alert alert-type="error"
|
||||
>${this._connectionAllocationsError}</ha-alert
|
||||
>`;
|
||||
}
|
||||
if (this._connectionAllocationData.length === 0) {
|
||||
return html`<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.no_connection_slot_allocations"
|
||||
)}
|
||||
</div>`;
|
||||
}
|
||||
const allocations = this._connectionAllocationData[0];
|
||||
const allocationsUsed = allocations.slots - allocations.free;
|
||||
const allocationsTotal = allocations.slots;
|
||||
if (allocationsTotal === 0) {
|
||||
return html`<div>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.no_active_connection_support"
|
||||
)}
|
||||
</div>`;
|
||||
}
|
||||
return html`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.connection_slot_allocations_monitor_details",
|
||||
{ slots: allocationsTotal }
|
||||
)}
|
||||
</p>
|
||||
<ha-metric
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.used_connection_slot_allocations"
|
||||
)}
|
||||
.value=${this._getUsedAllocations(allocationsUsed, allocationsTotal)}
|
||||
.tooltip=${allocations.allocated.length > 0
|
||||
? `${allocationsUsed}/${allocationsTotal} (${allocations.allocated.join(", ")})`
|
||||
: `${allocationsUsed}/${allocationsTotal}`}
|
||||
></ha-metric>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _openOptionFlow() {
|
||||
const configEntryId = this._configEntry;
|
||||
if (!configEntryId) {
|
||||
return;
|
||||
}
|
||||
const configEntries = await getConfigEntries(this.hass, {
|
||||
domain: "bluetooth",
|
||||
});
|
||||
const configEntry = configEntries.find(
|
||||
(entry) => entry.entry_id === configEntryId
|
||||
);
|
||||
showOptionsFlowDialog(this, configEntry!);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
@@ -395,9 +394,17 @@ export class BluetoothConfigDashboard extends LitElement {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
ha-list-item {
|
||||
--mdc-list-item-meta-display: flex;
|
||||
--mdc-list-item-meta-size: 48px;
|
||||
.scanner-state {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.state-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 4px 0;
|
||||
}
|
||||
.state-value {
|
||||
font-weight: 500;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
import { mdiAlertCircle, mdiCheckCircle, mdiPlus } from "@mdi/js";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../../../common/config/is_component_loaded";
|
||||
import "../../../../../components/ha-alert";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-expansion-panel";
|
||||
import "../../../../../components/ha-fab";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import type { ConfigEntry } from "../../../../../data/config_entries";
|
||||
import { getConfigEntries } from "../../../../../data/config_entries";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../../../../../components/ha-button";
|
||||
import {
|
||||
acceptSharedMatterDevice,
|
||||
canCommissionMatterExternal,
|
||||
@@ -26,6 +18,7 @@ import {
|
||||
import { showPromptDialog } from "../../../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
|
||||
@customElement("matter-config-dashboard")
|
||||
export class MatterConfigDashboard extends LitElement {
|
||||
@@ -33,8 +26,6 @@ export class MatterConfigDashboard extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@state() private _configEntry?: ConfigEntry;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
private _unsub?: UnsubscribeFunc;
|
||||
@@ -44,33 +35,10 @@ export class MatterConfigDashboard extends LitElement {
|
||||
this._stopRedirect();
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
if (this.hass) {
|
||||
this._fetchConfigEntry();
|
||||
}
|
||||
}
|
||||
|
||||
private _matterDeviceCount = memoizeOne(
|
||||
(devices: HomeAssistant["devices"]): number =>
|
||||
Object.values(devices).filter((device) =>
|
||||
device.identifiers.some((identifier) => identifier[0] === "matter")
|
||||
).length
|
||||
);
|
||||
|
||||
protected render(): TemplateResult | typeof nothing {
|
||||
if (!this._configEntry) {
|
||||
return nothing;
|
||||
}
|
||||
const isOnline = this._configEntry.state === "loaded";
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-subpage
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
header="Matter"
|
||||
has-fab
|
||||
>
|
||||
${isComponentLoaded(this.hass, "thread")
|
||||
<hass-subpage .narrow=${this.narrow} .hass=${this.hass} header="Matter">
|
||||
${isComponentLoaded(this.hass, "otbr")
|
||||
? html`
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
@@ -83,114 +51,53 @@ export class MatterConfigDashboard extends LitElement {
|
||||
)}</ha-button
|
||||
>
|
||||
`
|
||||
: nothing}
|
||||
<div class="container">
|
||||
<ha-card class="network-status">
|
||||
: ""}
|
||||
<div class="content">
|
||||
<ha-card header="Matter">
|
||||
<ha-alert alert-type="warning"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.experimental_note"
|
||||
)}</ha-alert
|
||||
>
|
||||
<div class="card-content">
|
||||
<div class="heading">
|
||||
<div class="icon">
|
||||
<ha-svg-icon
|
||||
.path=${isOnline ? mdiCheckCircle : mdiAlertCircle}
|
||||
class=${isOnline ? "online" : "offline"}
|
||||
></ha-svg-icon>
|
||||
</div>
|
||||
<div class="details">
|
||||
Matter
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.status_title"
|
||||
)}:
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.matter.panel.status_${isOnline ? "online" : "offline"}`
|
||||
)}<br />
|
||||
<small>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.devices",
|
||||
{ count: this._matterDeviceCount(this.hass.devices) }
|
||||
)}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
${this.hass.localize("ui.panel.config.matter.panel.add_devices")}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-button
|
||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
appearance="plain"
|
||||
size="small"
|
||||
${canCommissionMatterExternal(this.hass)
|
||||
? html`<ha-button
|
||||
appearance="plain"
|
||||
@click=${this._startMobileCommissioning}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.mobile_app_commisioning"
|
||||
)}</ha-button
|
||||
>`
|
||||
: ""}
|
||||
<ha-button appearance="plain" @click=${this._commission}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.commission_device"
|
||||
)}</ha-button
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.devices.caption")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
href=${`/config/entities/dashboard?historyBack=1&config_entry=${this._configEntry?.entry_id}`}
|
||||
<ha-button appearance="plain" @click=${this._acceptSharedDevice}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.add_shared_device"
|
||||
)}</ha-button
|
||||
>
|
||||
<ha-button appearance="plain" @click=${this._setWifi}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.set_wifi_credentials"
|
||||
)}</ha-button
|
||||
>
|
||||
<ha-button appearance="plain" @click=${this._setThread}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.set_thread_credentials"
|
||||
)}</ha-button
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.entities.caption")}
|
||||
</ha-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
<ha-expansion-panel
|
||||
outlined
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.developer_tools_title"
|
||||
)}
|
||||
.secondary=${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.developer_tools_description"
|
||||
)}
|
||||
>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: nothing}
|
||||
<div class="dev-tools-content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.developer_tools_info"
|
||||
)}
|
||||
</p>
|
||||
<div class="dev-tools-actions">
|
||||
${canCommissionMatterExternal(this.hass)
|
||||
? html`<ha-button
|
||||
appearance="plain"
|
||||
@click=${this._startMobileCommissioning}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.mobile_app_commisioning"
|
||||
)}</ha-button
|
||||
>`
|
||||
: nothing}
|
||||
<ha-button appearance="plain" @click=${this._commission}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.commission_device"
|
||||
)}</ha-button
|
||||
>
|
||||
<ha-button appearance="plain" @click=${this._acceptSharedDevice}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.add_shared_device"
|
||||
)}</ha-button
|
||||
>
|
||||
<ha-button appearance="plain" @click=${this._setWifi}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.set_wifi_credentials"
|
||||
)}</ha-button
|
||||
>
|
||||
<ha-button appearance="plain" @click=${this._setThread}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.set_thread_credentials"
|
||||
)}</ha-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</ha-expansion-panel>
|
||||
</div>
|
||||
|
||||
<a href="/config/matter/add" slot="fab">
|
||||
<ha-fab
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.matter.panel.add_device"
|
||||
)}
|
||||
extended
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
</a>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
@@ -329,101 +236,27 @@ export class MatterConfigDashboard extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchConfigEntry(): Promise<void> {
|
||||
const configEntries = await getConfigEntries(this.hass, {
|
||||
domain: "matter",
|
||||
});
|
||||
if (configEntries.length) {
|
||||
this._configEntry = configEntries[0];
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-card {
|
||||
margin: auto;
|
||||
margin-top: var(--ha-space-4);
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
ha-card .card-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.network-status div.heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.network-status div.heading .icon {
|
||||
margin-inline-end: var(--ha-space-4);
|
||||
}
|
||||
|
||||
.network-status div.heading ha-svg-icon {
|
||||
--mdc-icon-size: 48px;
|
||||
}
|
||||
|
||||
.network-status div.heading .details {
|
||||
font-size: var(--ha-font-size-xl);
|
||||
}
|
||||
|
||||
.network-status small {
|
||||
font-size: var(--ha-font-size-m);
|
||||
}
|
||||
|
||||
.network-status .online {
|
||||
color: var(--state-on-color, var(--success-color));
|
||||
}
|
||||
|
||||
.network-status .offline {
|
||||
color: var(--error-color, var(--error-color));
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: var(--ha-space-2) var(--ha-space-4) var(--ha-space-4);
|
||||
}
|
||||
|
||||
ha-expansion-panel {
|
||||
margin: auto;
|
||||
margin-top: var(--ha-space-4);
|
||||
max-width: 500px;
|
||||
background: var(--card-background-color);
|
||||
border-radius: var(
|
||||
--ha-card-border-radius,
|
||||
var(--ha-border-radius-lg)
|
||||
);
|
||||
--expansion-panel-summary-padding: var(--ha-space-2) var(--ha-space-4);
|
||||
--expansion-panel-content-padding: 0 var(--ha-space-4);
|
||||
}
|
||||
|
||||
.dev-tools-content {
|
||||
padding: 0 0 var(--ha-space-4);
|
||||
}
|
||||
|
||||
.dev-tools-content p {
|
||||
margin: 0 0 var(--ha-space-4);
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.dev-tools-actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: var(--ha-space-2);
|
||||
}
|
||||
|
||||
a[slot="toolbar-icon"] {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a[slot="fab"] {
|
||||
text-decoration: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
static styles = [
|
||||
haStyle,
|
||||
css`
|
||||
ha-alert[alert-type="warning"] {
|
||||
position: relative;
|
||||
top: -16px;
|
||||
}
|
||||
.content {
|
||||
padding: 24px 0 32px;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
direction: ltr;
|
||||
}
|
||||
ha-card:first-child {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
a[slot="toolbar-icon"] {
|
||||
text-decoration: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -45,9 +45,9 @@ class DialogThreadDataset extends LitElement implements HassDialog {
|
||||
<div>
|
||||
Network name: ${dataset.network_name}<br />
|
||||
Channel: ${dataset.channel}<br />
|
||||
Dataset ID: ${dataset.dataset_id}<br />
|
||||
PAN ID: ${dataset.pan_id}<br />
|
||||
Extended PAN ID: ${dataset.extended_pan_id}<br />
|
||||
Dataset id: ${dataset.dataset_id}<br />
|
||||
Pan id: ${dataset.pan_id}<br />
|
||||
Extended Pan id: ${dataset.extended_pan_id}<br />
|
||||
|
||||
${hasOTBR
|
||||
? html`OTBR URL: ${otbrInfo.url}<br />
|
||||
|
||||
@@ -372,7 +372,7 @@ export class HaConfigLogs extends LitElement {
|
||||
|
||||
@media all and (max-width: 870px) {
|
||||
ha-generic-picker {
|
||||
max-width: max(30%, 160px);
|
||||
max-width: 50%;
|
||||
}
|
||||
ha-button {
|
||||
max-width: 100%;
|
||||
|
||||
@@ -107,8 +107,6 @@ import { showAssignCategoryDialog } from "../category/show-dialog-assign-categor
|
||||
import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
|
||||
import { getEntityVoiceAssistantsIds } from "../../../data/expose";
|
||||
import "../voice-assistants/expose/expose-assistant-icon";
|
||||
|
||||
type SceneItem = SceneEntity & {
|
||||
name: string;
|
||||
@@ -412,31 +410,6 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
||||
</ha-icon-overflow-menu>
|
||||
`,
|
||||
},
|
||||
voice_assistants: {
|
||||
title: localize(
|
||||
"ui.panel.config.scene.picker.headers.voice_assistants"
|
||||
),
|
||||
type: "icon",
|
||||
defaultHidden: true,
|
||||
minWidth: "100px",
|
||||
maxWidth: "100px",
|
||||
template: (scene) => {
|
||||
const exposedToVoiceAssistantIds = getEntityVoiceAssistantsIds(
|
||||
this._entityReg,
|
||||
scene.entity_id
|
||||
);
|
||||
return html` ${exposedToVoiceAssistantIds.length !== 0
|
||||
? exposedToVoiceAssistantIds.map(
|
||||
(vaId) =>
|
||||
html` <voice-assistants-expose-assistant-icon
|
||||
.assistant=${vaId}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
</voice-assistants-expose-assistant-icon>`
|
||||
)
|
||||
: "—"}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return columns;
|
||||
|
||||
@@ -111,8 +111,6 @@ import { showAssignCategoryDialog } from "../category/show-dialog-assign-categor
|
||||
import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
|
||||
import { getEntityVoiceAssistantsIds } from "../../../data/expose";
|
||||
import "../voice-assistants/expose/expose-assistant-icon";
|
||||
|
||||
type ScriptItem = ScriptEntity & {
|
||||
name: string;
|
||||
@@ -400,32 +398,8 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
||||
</ha-icon-overflow-menu>
|
||||
`,
|
||||
},
|
||||
voice_assistants: {
|
||||
title: localize(
|
||||
"ui.panel.config.script.picker.headers.voice_assistants"
|
||||
),
|
||||
type: "icon",
|
||||
defaultHidden: true,
|
||||
minWidth: "100px",
|
||||
maxWidth: "100px",
|
||||
template: (script) => {
|
||||
const exposedToVoiceAssistantIds = getEntityVoiceAssistantsIds(
|
||||
this._entityReg,
|
||||
script.entity_id
|
||||
);
|
||||
return html` ${exposedToVoiceAssistantIds.length !== 0
|
||||
? exposedToVoiceAssistantIds.map(
|
||||
(vaId) =>
|
||||
html` <voice-assistants-expose-assistant-icon
|
||||
.assistant=${vaId}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
</voice-assistants-expose-assistant-icon>`
|
||||
)
|
||||
: "—"}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return columns;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -23,6 +23,7 @@ export class VoiceAssistantExposeAssistantIcon extends LitElement {
|
||||
|
||||
render() {
|
||||
if (!this.assistant || !voiceAssistants[this.assistant]) return nothing;
|
||||
|
||||
return html`
|
||||
<div class="container" id="container">
|
||||
<img
|
||||
|
||||
@@ -21,8 +21,6 @@ class EventSubscribeCard extends LitElement {
|
||||
|
||||
@state() private _subscribed?: () => void;
|
||||
|
||||
@state() private _eventFilter = "";
|
||||
|
||||
@state() private _events: {
|
||||
id: number;
|
||||
event: HassEvent;
|
||||
@@ -32,8 +30,6 @@ class EventSubscribeCard extends LitElement {
|
||||
|
||||
private _eventCount = 0;
|
||||
|
||||
@state() _ignoredEventsCount = 0;
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (this._subscribed) {
|
||||
@@ -74,16 +70,6 @@ class EventSubscribeCard extends LitElement {
|
||||
.value=${this._eventType}
|
||||
@input=${this._valueChanged}
|
||||
></ha-textfield>
|
||||
<ha-textfield
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.developer-tools.tabs.events.filter_events"
|
||||
)}
|
||||
.value=${this._eventFilter}
|
||||
.disabled=${this._subscribed !== undefined}
|
||||
helperPersistent
|
||||
.helper=${`${this.hass!.localize("ui.panel.developer-tools.tabs.events.filter_helper")}${this._ignoredEventsCount ? ` ${this.hass!.localize("ui.panel.developer-tools.tabs.events.filter_ignored", { count: this._ignoredEventsCount })}` : ""}`}
|
||||
@input=${this._filterChanged}
|
||||
></ha-textfield>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
@@ -149,46 +135,6 @@ class EventSubscribeCard extends LitElement {
|
||||
this._error = undefined;
|
||||
}
|
||||
|
||||
private _filterChanged(ev): void {
|
||||
this._eventFilter = ev.target.value;
|
||||
}
|
||||
|
||||
private _testEventFilter(event: HassEvent): boolean {
|
||||
if (!this._eventFilter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const searchStr = this._eventFilter;
|
||||
|
||||
function visit(node) {
|
||||
// Handle primitives directly
|
||||
if (node === null || typeof node !== "object") {
|
||||
return String(node).includes(searchStr);
|
||||
}
|
||||
|
||||
// Handle arrays and plain objects
|
||||
for (const key in node) {
|
||||
if (!Object.prototype.hasOwnProperty.call(node, key)) continue;
|
||||
// Check key
|
||||
if (key.includes(searchStr)) return true;
|
||||
|
||||
const value = node[key];
|
||||
|
||||
// Check primitive value
|
||||
if (value === null || typeof value !== "object") {
|
||||
if (String(value).includes(searchStr)) return true;
|
||||
} else if (visit(value)) {
|
||||
// Recurse into nested object/array
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return visit(event);
|
||||
}
|
||||
|
||||
private async _startOrStopListening(): Promise<void> {
|
||||
if (this._subscribed) {
|
||||
this._subscribed();
|
||||
@@ -198,10 +144,6 @@ class EventSubscribeCard extends LitElement {
|
||||
try {
|
||||
this._subscribed =
|
||||
await this.hass!.connection.subscribeEvents<HassEvent>((event) => {
|
||||
if (!this._testEventFilter(event)) {
|
||||
this._ignoredEventsCount++;
|
||||
return;
|
||||
}
|
||||
const tail =
|
||||
this._events.length > 30
|
||||
? this._events.slice(0, 29)
|
||||
@@ -226,7 +168,6 @@ class EventSubscribeCard extends LitElement {
|
||||
private _clearEvents(): void {
|
||||
this._events = [];
|
||||
this._eventCount = 0;
|
||||
this._ignoredEventsCount = 0;
|
||||
this._error = undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -201,7 +201,6 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
label: this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.statistics.fix_issue.fix"
|
||||
),
|
||||
type: "icon",
|
||||
template: (statistic) =>
|
||||
html`${statistic.issues
|
||||
? html`<ha-button
|
||||
|
||||
@@ -283,18 +283,13 @@ class PanelEnergy extends LitElement {
|
||||
["grid", "solar", "battery"].includes(source.type)
|
||||
);
|
||||
|
||||
const hasPowerSource = this._prefs.energy_sources.some(
|
||||
(source) =>
|
||||
(source.type === "solar" && source.stat_rate) ||
|
||||
(source.type === "battery" && source.stat_rate) ||
|
||||
(source.type === "grid" && source.power?.length)
|
||||
);
|
||||
|
||||
const hasDevicePower = this._prefs.device_consumption.some(
|
||||
(device) => device.stat_rate
|
||||
);
|
||||
|
||||
const hasPower = hasPowerSource || hasDevicePower;
|
||||
const hasPower =
|
||||
this._prefs.energy_sources.some(
|
||||
(source) =>
|
||||
(source.type === "solar" && source.stat_rate) ||
|
||||
(source.type === "battery" && source.stat_rate) ||
|
||||
(source.type === "grid" && source.power?.length)
|
||||
) || this._prefs.device_consumption.some((device) => device.stat_rate);
|
||||
|
||||
const hasWater =
|
||||
this._prefs.energy_sources.some((source) => source.type === "water") ||
|
||||
@@ -319,10 +314,7 @@ class PanelEnergy extends LitElement {
|
||||
if (hasPower) {
|
||||
views.push(POWER_VIEW);
|
||||
}
|
||||
if (
|
||||
hasPowerSource ||
|
||||
[hasEnergy, hasGas, hasWater].filter(Boolean).length > 1
|
||||
) {
|
||||
if (views.length > 1) {
|
||||
views.unshift(OVERVIEW_VIEW);
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -2,6 +2,8 @@ import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { resolveTimeZone } from "../../../../common/datetime/resolve-time-zone";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { ClockCardConfig } from "../types";
|
||||
@@ -26,6 +28,11 @@ function romanize12HourClock(num: number) {
|
||||
return numerals[num];
|
||||
}
|
||||
|
||||
const INTERVAL = 1000;
|
||||
const QUARTER_TICKS = Array.from({ length: 4 }, (_, i) => i);
|
||||
const HOUR_TICKS = Array.from({ length: 12 }, (_, i) => i);
|
||||
const MINUTE_TICKS = Array.from({ length: 60 }, (_, i) => i);
|
||||
|
||||
@customElement("hui-clock-card-analog")
|
||||
export class HuiClockCardAnalog extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
@@ -40,27 +47,32 @@ export class HuiClockCardAnalog extends LitElement {
|
||||
|
||||
@state() private _secondOffsetSec?: number;
|
||||
|
||||
private _initDate() {
|
||||
if (!this.config || !this.hass) {
|
||||
return;
|
||||
@state() private _year = "";
|
||||
|
||||
@state() private _month = "";
|
||||
|
||||
@state() private _day = "";
|
||||
|
||||
private _tickInterval?: undefined | number;
|
||||
|
||||
private _currentDate = new Date();
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
document.addEventListener("visibilitychange", this._handleVisibilityChange);
|
||||
this._computeDateTime();
|
||||
if (this.config?.date && this.config.date !== "none") {
|
||||
this._startTick();
|
||||
}
|
||||
}
|
||||
|
||||
let locale = this.hass.locale;
|
||||
if (this.config.time_format) {
|
||||
locale = { ...locale, time_format: this.config.time_format };
|
||||
}
|
||||
|
||||
this._dateTimeFormat = new Intl.DateTimeFormat(this.hass.locale.language, {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hourCycle: "h12",
|
||||
timeZone:
|
||||
this.config.time_zone ||
|
||||
resolveTimeZone(locale.time_zone, this.hass.config?.time_zone),
|
||||
});
|
||||
|
||||
this._computeOffsets();
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
document.removeEventListener(
|
||||
"visibilitychange",
|
||||
this._handleVisibilityChange
|
||||
);
|
||||
this._stopTick();
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
@@ -72,30 +84,78 @@ export class HuiClockCardAnalog extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
document.addEventListener("visibilitychange", this._handleVisibilityChange);
|
||||
this._computeOffsets();
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
document.removeEventListener(
|
||||
"visibilitychange",
|
||||
this._handleVisibilityChange
|
||||
);
|
||||
}
|
||||
|
||||
private _handleVisibilityChange = () => {
|
||||
if (!document.hidden) {
|
||||
this._computeOffsets();
|
||||
this._computeDateTime();
|
||||
}
|
||||
};
|
||||
|
||||
private _computeOffsets() {
|
||||
private _initDate() {
|
||||
if (!this.config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
|
||||
let locale = this.hass.locale;
|
||||
if (this.config.time_format) {
|
||||
locale = { ...locale, time_format: this.config.time_format };
|
||||
}
|
||||
|
||||
this._dateTimeFormat = new Intl.DateTimeFormat(this.hass.locale.language, {
|
||||
...(this.config.date && this.config.date !== "none"
|
||||
? this.config.date === "day"
|
||||
? {
|
||||
day: "numeric",
|
||||
}
|
||||
: this.config.date === "day-month"
|
||||
? {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
}
|
||||
: this.config.date === "day-month-long"
|
||||
? {
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
}
|
||||
: {
|
||||
year: "numeric",
|
||||
month: this.config.date === "long" ? "long" : "short",
|
||||
day: "numeric",
|
||||
}
|
||||
: {}),
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hourCycle: "h12",
|
||||
timeZone:
|
||||
this.config.time_zone ||
|
||||
resolveTimeZone(locale.time_zone, this.hass.config?.time_zone),
|
||||
});
|
||||
|
||||
this._computeDateTime();
|
||||
}
|
||||
|
||||
private _startTick() {
|
||||
this._tick();
|
||||
this._tickInterval = window.setInterval(() => this._tick(), INTERVAL);
|
||||
}
|
||||
|
||||
private _stopTick() {
|
||||
if (this._tickInterval) {
|
||||
clearInterval(this._tickInterval);
|
||||
this._tickInterval = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _updateDate() {
|
||||
this._currentDate = new Date();
|
||||
}
|
||||
|
||||
private _computeDateTime() {
|
||||
if (!this._dateTimeFormat) return;
|
||||
|
||||
const parts = this._dateTimeFormat.formatToParts();
|
||||
this._updateDate();
|
||||
|
||||
const parts = this._dateTimeFormat.formatToParts(this._currentDate);
|
||||
const hourStr = parts.find((p) => p.type === "hour")?.value;
|
||||
const minuteStr = parts.find((p) => p.type === "minute")?.value;
|
||||
const secondStr = parts.find((p) => p.type === "second")?.value;
|
||||
@@ -103,7 +163,7 @@ export class HuiClockCardAnalog extends LitElement {
|
||||
const hour = hourStr ? parseInt(hourStr, 10) : 0;
|
||||
const minute = minuteStr ? parseInt(minuteStr, 10) : 0;
|
||||
const second = secondStr ? parseInt(secondStr, 10) : 0;
|
||||
const ms = new Date().getMilliseconds();
|
||||
const ms = this._currentDate.getMilliseconds();
|
||||
const secondsWithMs = second + ms / 1000;
|
||||
|
||||
const hour12 = hour % 12;
|
||||
@@ -111,18 +171,36 @@ export class HuiClockCardAnalog extends LitElement {
|
||||
this._secondOffsetSec = secondsWithMs;
|
||||
this._minuteOffsetSec = minute * 60 + secondsWithMs;
|
||||
this._hourOffsetSec = hour12 * 3600 + minute * 60 + secondsWithMs;
|
||||
|
||||
// Also update date parts if date is shown
|
||||
if (this.config?.date && this.config.date !== "none") {
|
||||
this._year = parts.find((p) => p.type === "year")?.value ?? "";
|
||||
this._month = parts.find((p) => p.type === "month")?.value ?? "";
|
||||
this._day = parts.find((p) => p.type === "day")?.value ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
private _tick() {
|
||||
this._computeDateTime();
|
||||
}
|
||||
|
||||
private _computeClock = memoizeOne((config: ClockCardConfig) => {
|
||||
const faceParts = config.face_style?.split("_");
|
||||
|
||||
return {
|
||||
sizeClass: config.clock_size ? `size-${config.clock_size}` : "",
|
||||
isNumbers: faceParts?.includes("numbers") ?? false,
|
||||
isRoman: faceParts?.includes("roman") ?? false,
|
||||
isUpright: faceParts?.includes("upright") ?? false,
|
||||
};
|
||||
});
|
||||
|
||||
render() {
|
||||
if (!this.config) return nothing;
|
||||
|
||||
const sizeClass = this.config.clock_size
|
||||
? `size-${this.config.clock_size}`
|
||||
: "";
|
||||
|
||||
const isNumbers = this.config?.face_style?.startsWith("numbers");
|
||||
const isRoman = this.config?.face_style?.startsWith("roman");
|
||||
const isUpright = this.config?.face_style?.endsWith("upright");
|
||||
const { sizeClass, isNumbers, isRoman, isUpright } = this._computeClock(
|
||||
this.config
|
||||
);
|
||||
|
||||
const indicator = (number?: number) => html`
|
||||
<div
|
||||
@@ -163,14 +241,14 @@ export class HuiClockCardAnalog extends LitElement {
|
||||
})}
|
||||
>
|
||||
${this.config.ticks === "quarter"
|
||||
? Array.from({ length: 4 }, (_, i) => i).map(
|
||||
? QUARTER_TICKS.map(
|
||||
(i) =>
|
||||
// 4 ticks (12, 3, 6, 9) at 0°, 90°, 180°, 270°
|
||||
html`
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="tick hour"
|
||||
style=${`--tick-rotation: ${i * 90}deg;`}
|
||||
style=${styleMap({ "--tick-rotation": `${i * 90}deg` })}
|
||||
>
|
||||
${indicator([12, 3, 6, 9][i])}
|
||||
</div>
|
||||
@@ -178,28 +256,30 @@ export class HuiClockCardAnalog extends LitElement {
|
||||
)
|
||||
: !this.config.ticks || // Default to hour ticks
|
||||
this.config.ticks === "hour"
|
||||
? Array.from({ length: 12 }, (_, i) => i).map(
|
||||
? HOUR_TICKS.map(
|
||||
(i) =>
|
||||
// 12 ticks (1-12)
|
||||
html`
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="tick hour"
|
||||
style=${`--tick-rotation: ${i * 30}deg;`}
|
||||
style=${styleMap({ "--tick-rotation": `${i * 30}deg` })}
|
||||
>
|
||||
${indicator(((i + 11) % 12) + 1)}
|
||||
</div>
|
||||
`
|
||||
)
|
||||
: this.config.ticks === "minute"
|
||||
? Array.from({ length: 60 }, (_, i) => i).map(
|
||||
? MINUTE_TICKS.map(
|
||||
(i) =>
|
||||
// 60 ticks (1-60)
|
||||
html`
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="tick ${i % 5 === 0 ? "hour" : "minute"}"
|
||||
style=${`--tick-rotation: ${i * 6}deg;`}
|
||||
style=${styleMap({
|
||||
"--tick-rotation": `${i * 6}deg`,
|
||||
})}
|
||||
>
|
||||
${i % 5 === 0
|
||||
? indicator(((i / 5 + 11) % 12) + 1)
|
||||
@@ -208,14 +288,27 @@ export class HuiClockCardAnalog extends LitElement {
|
||||
`
|
||||
)
|
||||
: nothing}
|
||||
${this.config?.date && this.config.date !== "none"
|
||||
? html`<div class="date-parts ${sizeClass}">
|
||||
<span class="date-part day-month"
|
||||
>${this._day} ${this._month}</span
|
||||
>
|
||||
<span class="date-part year">${this._year}</span>
|
||||
</div>`
|
||||
: nothing}
|
||||
|
||||
<div class="center-dot"></div>
|
||||
<div
|
||||
class="hand hour"
|
||||
style=${`animation-delay: -${this._hourOffsetSec ?? 0}s;`}
|
||||
style=${styleMap({
|
||||
"animation-delay": `-${this._hourOffsetSec ?? 0}s`,
|
||||
})}
|
||||
></div>
|
||||
<div
|
||||
class="hand minute"
|
||||
style=${`animation-delay: -${this._minuteOffsetSec ?? 0}s;`}
|
||||
style=${styleMap({
|
||||
"animation-delay": `-${this._minuteOffsetSec ?? 0}s`,
|
||||
})}
|
||||
></div>
|
||||
${this.config.show_seconds
|
||||
? html`<div
|
||||
@@ -224,11 +317,13 @@ export class HuiClockCardAnalog extends LitElement {
|
||||
second: true,
|
||||
step: this.config.seconds_motion === "tick",
|
||||
})}
|
||||
style=${`animation-delay: -${
|
||||
(this.config.seconds_motion === "tick"
|
||||
? Math.floor(this._secondOffsetSec ?? 0)
|
||||
: (this._secondOffsetSec ?? 0)) as number
|
||||
}s;`}
|
||||
style=${styleMap({
|
||||
"animation-delay": `-${
|
||||
(this.config.seconds_motion === "tick"
|
||||
? Math.floor(this._secondOffsetSec ?? 0)
|
||||
: (this._secondOffsetSec ?? 0)) as number
|
||||
}s`,
|
||||
})}
|
||||
></div>`
|
||||
: nothing}
|
||||
</div>
|
||||
@@ -407,6 +502,41 @@ export class HuiClockCardAnalog extends LitElement {
|
||||
transform: translate(-50%, 0) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.date-parts {
|
||||
position: absolute;
|
||||
top: 68%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: grid;
|
||||
align-items: center;
|
||||
grid-template-areas:
|
||||
"day-month"
|
||||
"year";
|
||||
direction: ltr;
|
||||
color: var(--primary-text-color);
|
||||
font-size: var(--ha-font-size-s);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
text-align: center;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.date-parts.size-medium {
|
||||
font-size: var(--ha-font-size-l);
|
||||
}
|
||||
|
||||
.date-parts.size-large {
|
||||
font-size: var(--ha-font-size-xl);
|
||||
}
|
||||
|
||||
.date-part.day-month {
|
||||
grid-area: day-month;
|
||||
}
|
||||
|
||||
.date-part.year {
|
||||
grid-area: year;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ export class HuiClockCardDigital extends LitElement {
|
||||
|
||||
@state() private _timeAmPm?: string;
|
||||
|
||||
@state() private _date?: string;
|
||||
|
||||
private _tickInterval?: undefined | number;
|
||||
|
||||
private _initDate() {
|
||||
@@ -39,6 +41,27 @@ export class HuiClockCardDigital extends LitElement {
|
||||
|
||||
const h12 = useAmPm(locale);
|
||||
this._dateTimeFormat = new Intl.DateTimeFormat(this.hass.locale.language, {
|
||||
...(this.config.date && this.config.date !== "none"
|
||||
? this.config.date === "day"
|
||||
? {
|
||||
day: "numeric",
|
||||
}
|
||||
: this.config.date === "day-month"
|
||||
? {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
}
|
||||
: this.config.date === "day-month-long"
|
||||
? {
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
}
|
||||
: {
|
||||
year: "numeric",
|
||||
month: this.config.date === "long" ? "long" : "short",
|
||||
day: "numeric",
|
||||
}
|
||||
: {}),
|
||||
hour: h12 ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
@@ -93,6 +116,16 @@ export class HuiClockCardDigital extends LitElement {
|
||||
? parts.find((part) => part.type === "second")?.value
|
||||
: undefined;
|
||||
this._timeAmPm = parts.find((part) => part.type === "dayPeriod")?.value;
|
||||
|
||||
this._date = this.config?.date
|
||||
? [
|
||||
parts.find((part) => part.type === "day")?.value,
|
||||
parts.find((part) => part.type === "month")?.value,
|
||||
parts.find((part) => part.type === "year")?.value,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
: undefined;
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -113,6 +146,9 @@ export class HuiClockCardDigital extends LitElement {
|
||||
? html`<div class="time-part am-pm">${this._timeAmPm}</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
${this.config.date && this.config.date !== "none"
|
||||
? html`<div class="date ${sizeClass}">${this._date}</div>`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -188,6 +224,20 @@ export class HuiClockCardDigital extends LitElement {
|
||||
content: ":";
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.date {
|
||||
text-align: center;
|
||||
opacity: 0.8;
|
||||
font-size: var(--ha-font-size-s);
|
||||
}
|
||||
|
||||
.date.size-medium {
|
||||
font-size: var(--ha-font-size-l);
|
||||
}
|
||||
|
||||
.date.size-large {
|
||||
font-size: var(--ha-font-size-2xl);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ import { formatTime } from "../../../../../common/datetime/format_time";
|
||||
import type { ECOption } from "../../../../../resources/echarts/echarts";
|
||||
import { filterXSS } from "../../../../../common/util/xss";
|
||||
import type { StatisticPeriod } from "../../../../../data/recorder";
|
||||
import { getSuggestedPeriod } from "../../../../../data/energy";
|
||||
|
||||
export function getSuggestedMax(period: StatisticPeriod, end: Date): number {
|
||||
let suggestedMax = new Date(end);
|
||||
@@ -57,6 +56,10 @@ export function getSuggestedMax(period: StatisticPeriod, end: Date): number {
|
||||
return suggestedMax.getTime();
|
||||
}
|
||||
|
||||
export function getSuggestedPeriod(dayDifference: number): StatisticPeriod {
|
||||
return dayDifference > 35 ? "month" : dayDifference > 2 ? "day" : "hour";
|
||||
}
|
||||
|
||||
function createYAxisLabelFormatter(locale: FrontendLocaleData) {
|
||||
let previousValue: number | undefined;
|
||||
|
||||
@@ -92,7 +95,7 @@ export function getCommonOptions(
|
||||
type: "time",
|
||||
min: start,
|
||||
max: getSuggestedMax(
|
||||
getSuggestedPeriod(start, end, detailedDailyData),
|
||||
detailedDailyData ? "5minute" : getSuggestedPeriod(dayDifference),
|
||||
end
|
||||
),
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { endOfToday, isToday, startOfToday } from "date-fns";
|
||||
import { differenceInDays, endOfToday, isToday, startOfToday } from "date-fns";
|
||||
import type { HassConfig, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
@@ -18,7 +18,6 @@ import type {
|
||||
import {
|
||||
getEnergyDataCollection,
|
||||
getEnergySolarForecasts,
|
||||
getSuggestedPeriod,
|
||||
} from "../../../../data/energy";
|
||||
import type { Statistics, StatisticsMetaData } from "../../../../data/recorder";
|
||||
import { getStatisticLabel } from "../../../../data/recorder";
|
||||
@@ -355,7 +354,7 @@ export class HuiEnergySolarGraphCard
|
||||
) {
|
||||
const data: LineSeriesOption[] = [];
|
||||
|
||||
const period = getSuggestedPeriod(start, end);
|
||||
const dayDifference = differenceInDays(end || new Date(), start);
|
||||
|
||||
// Process solar forecast data.
|
||||
solarSources.forEach((source) => {
|
||||
@@ -371,10 +370,10 @@ export class HuiEnergySolarGraphCard
|
||||
if (dateObj < start || (end && dateObj > end)) {
|
||||
return;
|
||||
}
|
||||
if (period === "month") {
|
||||
if (dayDifference > 35) {
|
||||
dateObj.setDate(1);
|
||||
}
|
||||
if (period === "month" || period === "day") {
|
||||
if (dayDifference > 2) {
|
||||
dateObj.setHours(0, 0, 0, 0);
|
||||
} else {
|
||||
dateObj.setMinutes(0, 0, 0);
|
||||
|
||||
@@ -67,19 +67,6 @@ export const SUM_DEVICE_CLASSES = [
|
||||
"water",
|
||||
];
|
||||
|
||||
// Additional sources for sensor device classes from entity attributes
|
||||
// Maps device_class -> array of { domain, attribute } to include in aggregation
|
||||
export const SENSOR_ATTRIBUTE_SOURCES: Record<
|
||||
string,
|
||||
{ domain: string; attribute: string }[]
|
||||
> = {
|
||||
temperature: [{ domain: "climate", attribute: "current_temperature" }],
|
||||
humidity: [
|
||||
{ domain: "climate", attribute: "current_humidity" },
|
||||
{ domain: "humidifier", attribute: "current_humidity" },
|
||||
],
|
||||
};
|
||||
|
||||
export interface AreaCardFeatureContext extends LovelaceCardFeatureContext {
|
||||
exclude_entities?: string[];
|
||||
}
|
||||
@@ -264,24 +251,6 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
);
|
||||
|
||||
private _domainEntityIds = memoizeOne(
|
||||
(
|
||||
entities: HomeAssistant["entities"],
|
||||
areaId: string,
|
||||
domains: string[],
|
||||
excludeEntities?: string[]
|
||||
): string[] => {
|
||||
const filter = generateEntityFilter(this.hass, {
|
||||
area: areaId,
|
||||
entity_category: "none",
|
||||
domain: domains,
|
||||
});
|
||||
return Object.keys(entities).filter(
|
||||
(id) => filter(id) && !excludeEntities?.includes(id)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
private _computeActiveAlertStates(): HassEntity[] {
|
||||
const areaId = this._config?.area;
|
||||
const area = areaId ? this.hass.areas[areaId] : undefined;
|
||||
@@ -390,91 +359,58 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
|
||||
: this.hass.formatEntityState(stateObj);
|
||||
}
|
||||
|
||||
const sensorEntityIds = groupedEntities.get(sensorClass) || [];
|
||||
const values: number[] = [];
|
||||
let uom: string | undefined;
|
||||
const entityIds = groupedEntities.get(sensorClass);
|
||||
|
||||
// Track devices that have sensor entities contributing values
|
||||
// to avoid duplicate readings from climate/humidifier attributes
|
||||
const devicesWithSensorValues = new Set<string>();
|
||||
|
||||
for (const entityId of sensorEntityIds) {
|
||||
const stateObj = this.hass.states[entityId];
|
||||
if (
|
||||
stateObj &&
|
||||
!isUnavailableState(stateObj.state) &&
|
||||
isNumericState(stateObj) &&
|
||||
!isNaN(Number(stateObj.state))
|
||||
) {
|
||||
if (!uom) {
|
||||
uom = stateObj.attributes.unit_of_measurement;
|
||||
}
|
||||
if (stateObj.attributes.unit_of_measurement === uom) {
|
||||
values.push(Number(stateObj.state));
|
||||
// Track the device this sensor belongs to
|
||||
const entityEntry = this.hass.entities[entityId];
|
||||
if (entityEntry?.device_id) {
|
||||
devicesWithSensorValues.add(entityEntry.device_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!entityIds) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Collect values from additional attribute sources
|
||||
const attrSources = SENSOR_ATTRIBUTE_SOURCES[sensorClass];
|
||||
if (attrSources) {
|
||||
const domains = [...new Set(attrSources.map((s) => s.domain))];
|
||||
const attrEntityIds = this._domainEntityIds(
|
||||
this.hass.entities,
|
||||
area.area_id,
|
||||
domains,
|
||||
excludeEntities
|
||||
);
|
||||
// Ensure all entities have state
|
||||
const entities = entityIds
|
||||
.map((entityId) => this.hass.states[entityId])
|
||||
.filter(Boolean);
|
||||
|
||||
for (const entityId of attrEntityIds) {
|
||||
const stateObj = this.hass.states[entityId];
|
||||
if (!stateObj) continue;
|
||||
|
||||
// Skip if this entity's device already has a sensor contributing values
|
||||
const entityEntry = this.hass.entities[entityId];
|
||||
if (
|
||||
entityEntry?.device_id &&
|
||||
devicesWithSensorValues.has(entityEntry.device_id)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const domain = entityId.split(".")[0];
|
||||
const source = attrSources.find((s) => s.domain === domain);
|
||||
if (!source) continue;
|
||||
|
||||
const attrValue = stateObj.attributes[source.attribute];
|
||||
if (attrValue == null || isNaN(Number(attrValue))) continue;
|
||||
|
||||
if (!uom) {
|
||||
// Determine unit from attribute
|
||||
uom = this._getAttributeUnit(sensorClass, domain);
|
||||
}
|
||||
values.push(Number(attrValue));
|
||||
}
|
||||
if (entities.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (values.length === 0) {
|
||||
// If only one entity, return its formatted state
|
||||
if (entities.length === 1) {
|
||||
const stateObj = entities[0];
|
||||
return isUnavailableState(stateObj.state)
|
||||
? ""
|
||||
: this.hass.formatEntityState(stateObj);
|
||||
}
|
||||
|
||||
// Use the first entity's unit_of_measurement for formatting
|
||||
const uom = entities.find(
|
||||
(entity) => entity.attributes.unit_of_measurement
|
||||
)?.attributes.unit_of_measurement;
|
||||
|
||||
// Ensure all entities have the same unit_of_measurement
|
||||
const validEntities = entities.filter(
|
||||
(entity) =>
|
||||
entity.attributes.unit_of_measurement === uom &&
|
||||
isNumericState(entity) &&
|
||||
!isNaN(Number(entity.state))
|
||||
);
|
||||
|
||||
if (validEntities.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const value = SUM_DEVICE_CLASSES.includes(sensorClass)
|
||||
? values.reduce((acc, v) => acc + v, 0)
|
||||
: this._computeMedianValue(values);
|
||||
? this._computeSumState(validEntities)
|
||||
: this._computeMedianState(validEntities);
|
||||
|
||||
const formattedValue = formatNumber(value, this.hass.locale, {
|
||||
const formattedAverage = formatNumber(value, this.hass!.locale, {
|
||||
maximumFractionDigits: 1,
|
||||
});
|
||||
const formattedUnit = uom
|
||||
? `${blankBeforeUnit(uom, this.hass.locale)}${uom}`
|
||||
? `${blankBeforeUnit(uom, this.hass!.locale)}${uom}`
|
||||
: "";
|
||||
|
||||
return `${formattedValue}${formattedUnit}`;
|
||||
return `${formattedAverage}${formattedUnit}`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
@@ -482,25 +418,20 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
|
||||
return sensorStates;
|
||||
}
|
||||
|
||||
private _getAttributeUnit(sensorClass: string, domain: string): string {
|
||||
// Return the expected unit for attributes from specific domains
|
||||
if (sensorClass === "temperature" && domain === "climate") {
|
||||
return this.hass.config.unit_system.temperature;
|
||||
}
|
||||
if (sensorClass === "humidity") {
|
||||
return "%";
|
||||
}
|
||||
return "";
|
||||
private _computeSumState(entities: HassEntity[]): number {
|
||||
return entities.reduce((acc, entity) => acc + Number(entity.state), 0);
|
||||
}
|
||||
|
||||
private _computeMedianValue(values: number[]): number {
|
||||
const sortedValues = [...values].sort((a, b) => a - b);
|
||||
if (sortedValues.length % 2 === 0) {
|
||||
const medianIndex = sortedValues.length / 2;
|
||||
return (sortedValues[medianIndex] + sortedValues[medianIndex - 1]) / 2;
|
||||
private _computeMedianState(entities: HassEntity[]): number {
|
||||
const sortedStates = entities
|
||||
.map((entity) => Number(entity.state))
|
||||
.sort((a, b) => a - b);
|
||||
if (sortedStates.length % 2 === 0) {
|
||||
const medianIndex = sortedStates.length / 2;
|
||||
return (sortedStates[medianIndex] + sortedStates[medianIndex - 1]) / 2;
|
||||
}
|
||||
const medianIndex = Math.floor(sortedValues.length / 2);
|
||||
return sortedValues[medianIndex];
|
||||
const medianIndex = Math.floor(sortedStates.length / 2);
|
||||
return sortedStates[medianIndex];
|
||||
}
|
||||
|
||||
private _featurePosition = memoizeOne((config: AreaCardConfig) => {
|
||||
|
||||
@@ -94,12 +94,10 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
|
||||
(changedProps.has("_config") && this._config?.entities)
|
||||
) {
|
||||
const computedStyles = getComputedStyle(this);
|
||||
if (this._config?.entities) {
|
||||
this._calendars = this._config.entities.map((entity, idx) => ({
|
||||
entity_id: entity,
|
||||
backgroundColor: getColorByIndex(idx, computedStyles),
|
||||
}));
|
||||
}
|
||||
this._calendars = this._config!.entities.map((entity, idx) => ({
|
||||
entity_id: entity,
|
||||
backgroundColor: getColorByIndex(idx, computedStyles),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,116 +1,63 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-icon";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import type { LovelaceCard } from "../types";
|
||||
import type { EmptyStateCardConfig } from "./types";
|
||||
|
||||
@customElement("hui-empty-state-card")
|
||||
export class HuiEmptyStateCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import("../editor/config-elements/hui-empty-state-card-editor");
|
||||
return document.createElement("hui-empty-state-card-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(): EmptyStateCardConfig {
|
||||
return {
|
||||
type: "empty-state",
|
||||
title: "Welcome Home",
|
||||
content: "This is an empty state card.",
|
||||
};
|
||||
}
|
||||
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _config?: EmptyStateCardConfig;
|
||||
|
||||
public getCardSize(): number {
|
||||
return 2;
|
||||
}
|
||||
|
||||
public setConfig(config: EmptyStateCardConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
public setConfig(_config: EmptyStateCardConfig): void {}
|
||||
|
||||
protected render() {
|
||||
if (!this.hass || !this._config) {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
class=${classMap({
|
||||
"content-only": this._config.content_only ?? false,
|
||||
})}
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.lovelace.cards.empty_state.title"
|
||||
)}
|
||||
>
|
||||
<div class="container">
|
||||
${this._config.icon
|
||||
? html`<ha-icon .icon=${this._config.icon}></ha-icon>`
|
||||
: nothing}
|
||||
${this._config.title ? html`<h1>${this._config.title}</h1>` : nothing}
|
||||
${this._config.content
|
||||
? html`<p>${this._config.content}</p>`
|
||||
: nothing}
|
||||
${this._config.tap_action && this._config.action_button_text
|
||||
? html`
|
||||
<ha-button @click=${this._handleAction}>
|
||||
${this._config.action_button_text}
|
||||
</ha-button>
|
||||
`
|
||||
: nothing}
|
||||
<div class="card-content">
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.cards.empty_state.no_devices"
|
||||
)}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-button appearance="plain" href="/config/integrations/dashboard">
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.cards.empty_state.go_to_integrations_page"
|
||||
)}
|
||||
</ha-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleAction(): void {
|
||||
if (this._config?.tap_action && this.hass) {
|
||||
handleAction(this, this.hass, this._config, "tap");
|
||||
}
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
.content {
|
||||
margin-top: -1em;
|
||||
padding: 16px;
|
||||
}
|
||||
ha-card {
|
||||
height: 100%;
|
||||
|
||||
.card-actions a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
padding: var(--ha-space-8) var(--ha-space-4);
|
||||
box-sizing: border-box;
|
||||
gap: var(--ha-space-4);
|
||||
max-width: 640px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
ha-icon {
|
||||
--mdc-icon-size: var(--ha-space-12);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: var(--ha-font-size-xl);
|
||||
font-weight: 500;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.content-only {
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
|
||||
ha-button {
|
||||
margin-left: -8px;
|
||||
margin-inline-start: -8px;
|
||||
margin-inline-end: initial;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -8,10 +8,7 @@ import { createSearchParam } from "../../../common/url/search-params";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-tooltip";
|
||||
import {
|
||||
getEnergyDataCollection,
|
||||
getSuggestedPeriod,
|
||||
} from "../../../data/energy";
|
||||
import { getEnergyDataCollection } from "../../../data/energy";
|
||||
import type {
|
||||
Statistics,
|
||||
StatisticsMetaData,
|
||||
@@ -29,7 +26,10 @@ import { hasConfigOrEntitiesChanged } from "../common/has-changed";
|
||||
import { processConfigEntities } from "../common/process-config-entities";
|
||||
import type { EntityConfig } from "../entity-rows/types";
|
||||
import type { LovelaceCard, LovelaceGridOptions } from "../types";
|
||||
import { getSuggestedMax } from "./energy/common/energy-chart-options";
|
||||
import {
|
||||
getSuggestedMax,
|
||||
getSuggestedPeriod,
|
||||
} from "./energy/common/energy-chart-options";
|
||||
import type { StatisticsGraphCardConfig } from "./types";
|
||||
|
||||
export const DEFAULT_DAYS_TO_SHOW = 30;
|
||||
@@ -268,7 +268,9 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
|
||||
return (
|
||||
this._config?.period ??
|
||||
(this._energyStart && this._energyEnd
|
||||
? getSuggestedPeriod(this._energyStart, this._energyEnd)
|
||||
? getSuggestedPeriod(
|
||||
differenceInDays(this._energyEnd, this._energyStart)
|
||||
)
|
||||
: undefined)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -58,12 +58,8 @@ export interface ConditionalCardConfig extends LovelaceCardConfig {
|
||||
}
|
||||
|
||||
export interface EmptyStateCardConfig extends LovelaceCardConfig {
|
||||
content_only?: boolean;
|
||||
icon?: string;
|
||||
content: string;
|
||||
title?: string;
|
||||
content?: string;
|
||||
action_button_text?: string;
|
||||
tap_action?: ActionConfig;
|
||||
}
|
||||
|
||||
export interface EntityCardConfig extends LovelaceCardConfig {
|
||||
@@ -421,6 +417,7 @@ export interface ClockCardConfig extends LovelaceCardConfig {
|
||||
time_format?: TimeFormat;
|
||||
time_zone?: string;
|
||||
no_background?: boolean;
|
||||
date?: "none" | "short" | "long" | "day" | "day-month" | "day-month-long";
|
||||
// Analog clock options
|
||||
border?: boolean;
|
||||
ticks?: "none" | "quarter" | "hour" | "minute";
|
||||
|
||||
@@ -21,8 +21,5 @@ export const confirmAction = async (
|
||||
hass.localize("ui.panel.lovelace.cards.actions.action_confirmation", {
|
||||
action,
|
||||
}),
|
||||
title: config.title,
|
||||
dismissText: config.dismiss_text,
|
||||
confirmText: config.confirm_text,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -368,7 +368,6 @@ export const generateViewConfig = (
|
||||
path: string,
|
||||
title: string | undefined,
|
||||
icon: string | undefined,
|
||||
show_icon_and_title: boolean | undefined,
|
||||
entities: HassEntities
|
||||
): LovelaceViewConfig => {
|
||||
const ungroupedEntitites: Record<string, string[]> = {};
|
||||
@@ -498,9 +497,6 @@ export const generateViewConfig = (
|
||||
if (icon) {
|
||||
view.icon = icon;
|
||||
}
|
||||
if (show_icon_and_title) {
|
||||
view.show_icon_and_title = show_icon_and_title;
|
||||
}
|
||||
|
||||
return view;
|
||||
};
|
||||
@@ -521,7 +517,6 @@ export const generateDefaultViewConfig = (
|
||||
const path = "default_view";
|
||||
const title = "Home";
|
||||
const icon = undefined;
|
||||
const show_icon_and_title = undefined;
|
||||
|
||||
// In the case of a default view, we want to use the group order attribute
|
||||
const groupOrders = {};
|
||||
@@ -571,7 +566,6 @@ export const generateDefaultViewConfig = (
|
||||
path,
|
||||
title,
|
||||
icon,
|
||||
show_icon_and_title,
|
||||
splittedByGroups.ungrouped
|
||||
);
|
||||
|
||||
|
||||
@@ -7,11 +7,6 @@ const calcPoints = (
|
||||
height: number,
|
||||
limits?: { minX?: number; maxX?: number; minY?: number; maxY?: number }
|
||||
) => {
|
||||
// handling empty history (for example unavailable for long time)
|
||||
if (history.length === 0) {
|
||||
return { points: [], yAxisOrigin: height };
|
||||
}
|
||||
|
||||
let yAxisOrigin = height;
|
||||
let minY = limits?.minY ?? history[0][1];
|
||||
let maxY = limits?.maxY ?? history[0][1];
|
||||
|
||||
@@ -89,9 +89,6 @@ export const handleAction = async (
|
||||
) ||
|
||||
actionConfig.action,
|
||||
}),
|
||||
title: actionConfig.confirmation.title,
|
||||
dismissText: actionConfig.confirmation.dismiss_text,
|
||||
confirmText: actionConfig.confirmation.confirm_text,
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
|
||||
@@ -39,6 +39,19 @@ const cardConfigStruct = assign(
|
||||
time_zone: optional(enums(Object.keys(timezones))),
|
||||
show_seconds: optional(boolean()),
|
||||
no_background: optional(boolean()),
|
||||
date: optional(
|
||||
defaulted(
|
||||
union([
|
||||
literal("none"),
|
||||
literal("short"),
|
||||
literal("long"),
|
||||
literal("day"),
|
||||
literal("day-month"),
|
||||
literal("day-month-long"),
|
||||
]),
|
||||
literal("none")
|
||||
)
|
||||
),
|
||||
// Analog clock options
|
||||
border: optional(defaulted(boolean(), false)),
|
||||
ticks: optional(
|
||||
@@ -93,7 +106,7 @@ export class HuiClockCardEditor
|
||||
name: "clock_style",
|
||||
selector: {
|
||||
select: {
|
||||
mode: "dropdown",
|
||||
mode: "box",
|
||||
options: ["digital", "analog"].map((value) => ({
|
||||
value,
|
||||
label: localize(
|
||||
@@ -119,6 +132,27 @@ export class HuiClockCardEditor
|
||||
},
|
||||
{ name: "show_seconds", selector: { boolean: {} } },
|
||||
{ name: "no_background", selector: { boolean: {} } },
|
||||
{
|
||||
name: "date",
|
||||
selector: {
|
||||
select: {
|
||||
mode: "dropdown",
|
||||
options: [
|
||||
"none",
|
||||
"short",
|
||||
"long",
|
||||
"day",
|
||||
"day-month",
|
||||
"day-month-long",
|
||||
].map((value) => ({
|
||||
value,
|
||||
label: localize(
|
||||
`ui.panel.lovelace.editor.card.clock.dates.${value}`
|
||||
),
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
...(clockStyle === "digital"
|
||||
? ([
|
||||
{
|
||||
@@ -260,13 +294,14 @@ export class HuiClockCardEditor
|
||||
] as const satisfies readonly HaFormSchema[]
|
||||
);
|
||||
|
||||
private _data = memoizeOne((config) => ({
|
||||
private _data = memoizeOne((config: ClockCardConfig) => ({
|
||||
clock_style: "digital",
|
||||
clock_size: "small",
|
||||
time_zone: "auto",
|
||||
time_format: "auto",
|
||||
show_seconds: false,
|
||||
no_background: false,
|
||||
date: "none",
|
||||
// Analog clock options
|
||||
border: false,
|
||||
ticks: "hour",
|
||||
@@ -290,8 +325,9 @@ export class HuiClockCardEditor
|
||||
.data=${this._data(this._config)}
|
||||
.schema=${this._schema(
|
||||
this.hass.localize,
|
||||
this._data(this._config).clock_style,
|
||||
this._data(this._config).ticks,
|
||||
this._data(this._config)
|
||||
.clock_style as ClockCardConfig["clock_style"],
|
||||
this._data(this._config).ticks as ClockCardConfig["ticks"],
|
||||
this._data(this._config).show_seconds
|
||||
)}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@@ -367,6 +403,10 @@ export class HuiClockCardEditor
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.clock.no_background`
|
||||
);
|
||||
case "date":
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.clock.date.label`
|
||||
);
|
||||
case "border":
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.clock.border.label`
|
||||
@@ -392,6 +432,10 @@ export class HuiClockCardEditor
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) => {
|
||||
switch (schema.name) {
|
||||
case "date":
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.clock.date.description`
|
||||
);
|
||||
case "border":
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.clock.border.description`
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
import { mdiGestureTap } from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { assert, assign, boolean, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import type {
|
||||
HaFormSchema,
|
||||
SchemaUnion,
|
||||
} from "../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { EmptyStateCardConfig } from "../../cards/types";
|
||||
import type { LovelaceCardEditor } from "../../types";
|
||||
import { actionConfigStruct } from "../structs/action-struct";
|
||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||
|
||||
const cardConfigStruct = assign(
|
||||
baseLovelaceCardConfig,
|
||||
object({
|
||||
content_only: optional(boolean()),
|
||||
icon: optional(string()),
|
||||
title: optional(string()),
|
||||
content: optional(string()),
|
||||
action_button_text: optional(string()),
|
||||
tap_action: optional(actionConfigStruct),
|
||||
})
|
||||
);
|
||||
|
||||
@customElement("hui-empty-state-card-editor")
|
||||
export class HuiEmptyStateCardEditor
|
||||
extends LitElement
|
||||
implements LovelaceCardEditor
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _config?: EmptyStateCardConfig;
|
||||
|
||||
public setConfig(config: EmptyStateCardConfig): void {
|
||||
assert(config, cardConfigStruct);
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(localize: LocalizeFunc) =>
|
||||
[
|
||||
{
|
||||
name: "style",
|
||||
selector: {
|
||||
select: {
|
||||
mode: "box",
|
||||
options: (
|
||||
[
|
||||
{ value: "card", image: "card" },
|
||||
{ value: "content-only", image: "text_only" },
|
||||
] as const
|
||||
).map((style) => ({
|
||||
label: localize(
|
||||
`ui.panel.lovelace.editor.card.empty_state.style_options.${style.value}`
|
||||
),
|
||||
image: {
|
||||
src: `/static/images/form/markdown_${style.image}.svg`,
|
||||
src_dark: `/static/images/form/markdown_${style.image}_dark.svg`,
|
||||
flip_rtl: true,
|
||||
},
|
||||
value: style.value,
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
{ name: "icon", selector: { icon: {} } },
|
||||
{ name: "title", selector: { text: {} } },
|
||||
{ name: "content", selector: { text: { multiline: true } } },
|
||||
{
|
||||
name: "interactions",
|
||||
type: "expandable",
|
||||
flatten: true,
|
||||
iconPath: mdiGestureTap,
|
||||
schema: [
|
||||
{ name: "action_button_text", selector: { text: {} } },
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
] as const satisfies readonly HaFormSchema[]
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this.hass || !this._config) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const data = {
|
||||
...this._config,
|
||||
style: this._config.content_only ? "content-only" : "card",
|
||||
};
|
||||
|
||||
const schema = this._schema(this.hass.localize);
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.schema=${schema}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
const config = { ...ev.detail.value };
|
||||
|
||||
if (config.style === "content-only") {
|
||||
config.content_only = true;
|
||||
} else {
|
||||
delete config.content_only;
|
||||
}
|
||||
delete config.style;
|
||||
|
||||
fireEvent(this, "config-changed", { config });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) => {
|
||||
switch (schema.name) {
|
||||
case "style":
|
||||
case "content":
|
||||
case "action_button_text":
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.empty_state.${schema.name}`
|
||||
);
|
||||
default:
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.generic.${schema.name}`
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-empty-state-card-editor": HuiEmptyStateCardEditor;
|
||||
}
|
||||
}
|
||||
@@ -73,12 +73,6 @@ export class HuiViewEditor extends LitElement {
|
||||
icon: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "show_icon_and_title",
|
||||
selector: {
|
||||
boolean: {},
|
||||
},
|
||||
},
|
||||
{ name: "path", selector: { text: {} } },
|
||||
{ name: "theme", selector: { theme: {} } },
|
||||
{
|
||||
@@ -213,7 +207,6 @@ export class HuiViewEditor extends LitElement {
|
||||
case "path":
|
||||
return this.hass!.localize("ui.panel.lovelace.editor.card.generic.url");
|
||||
case "type":
|
||||
case "show_icon_and_title":
|
||||
case "subview":
|
||||
case "max_columns":
|
||||
case "dense_section_placement":
|
||||
@@ -234,7 +227,6 @@ export class HuiViewEditor extends LitElement {
|
||||
) => {
|
||||
switch (schema.name) {
|
||||
case "path":
|
||||
case "show_icon_and_title":
|
||||
case "subview":
|
||||
case "dense_section_placement":
|
||||
case "top_margin":
|
||||
|
||||
@@ -112,20 +112,7 @@ class HuiSelectEntityRow extends LitElement implements LovelaceRow {
|
||||
|
||||
forwardHaptic(this, "light");
|
||||
|
||||
setSelectOption(this.hass!, stateObj.entity_id, option)
|
||||
.catch((_err) => {
|
||||
// silently swallow exception
|
||||
})
|
||||
.finally(() =>
|
||||
setTimeout(() => {
|
||||
const newStateObj = this.hass!.states[this._config!.entity];
|
||||
if (newStateObj === stateObj) {
|
||||
const select = this.shadowRoot?.querySelector("ha-select");
|
||||
const index = select?.options.indexOf(stateObj.state) ?? -1;
|
||||
select?.select(index);
|
||||
}
|
||||
}, 2000)
|
||||
);
|
||||
setSelectOption(this.hass!, stateObj.entity_id, option);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -496,10 +496,6 @@ class HUIRoot extends LitElement {
|
||||
|
||||
const tabs = html`<ha-tab-group @wa-tab-show=${this._handleViewSelected}>
|
||||
${views.map((view, index) => {
|
||||
const icon_and_title =
|
||||
view.show_icon_and_title && view.icon && view.title;
|
||||
const icon_only = view.icon && !icon_and_title;
|
||||
const title_only = !icon_only && !icon_and_title;
|
||||
const hidden =
|
||||
!this._editMode && (view.subview || _isTabHiddenForUser(view));
|
||||
return html`
|
||||
@@ -510,8 +506,7 @@ class HUIRoot extends LitElement {
|
||||
.disabled=${hidden}
|
||||
aria-label=${ifDefined(view.title)}
|
||||
class=${classMap({
|
||||
"icon-only": Boolean(icon_only),
|
||||
"icon-and-title": Boolean(icon_and_title),
|
||||
icon: Boolean(view.icon),
|
||||
"hide-tab": Boolean(hidden),
|
||||
})}
|
||||
>
|
||||
@@ -528,20 +523,18 @@ class HUIRoot extends LitElement {
|
||||
></ha-icon-button-arrow-prev>
|
||||
`
|
||||
: nothing}
|
||||
${icon_only || icon_and_title
|
||||
? html`<ha-icon
|
||||
class=${classMap({
|
||||
"child-view-icon": Boolean(view.subview),
|
||||
})}
|
||||
title=${ifDefined(view.title)}
|
||||
.icon=${view.icon}
|
||||
></ha-icon>`
|
||||
: nothing}
|
||||
${icon_and_title ? view.title : nothing}
|
||||
${title_only
|
||||
? view.title ||
|
||||
this.hass.localize("ui.panel.lovelace.views.unnamed_view")
|
||||
: nothing}
|
||||
${view.icon
|
||||
? html`
|
||||
<ha-icon
|
||||
class=${classMap({
|
||||
"child-view-icon": Boolean(view.subview),
|
||||
})}
|
||||
title=${ifDefined(view.title)}
|
||||
.icon=${view.icon}
|
||||
></ha-icon>
|
||||
`
|
||||
: view.title ||
|
||||
this.hass.localize("ui.panel.lovelace.views.unnamed_view")}
|
||||
${this._editMode
|
||||
? html`
|
||||
<ha-icon-button
|
||||
@@ -1496,27 +1489,24 @@ class HUIRoot extends LitElement {
|
||||
ha-tab-group-tab {
|
||||
--ha-tab-group-tab-height: var(--header-height, 56px);
|
||||
}
|
||||
.tab-bar ha-tab-group-tab {
|
||||
--ha-tab-group-tab-height: var(--tab-bar-height, 56px);
|
||||
}
|
||||
ha-tab-group-tab[aria-selected="true"] .edit-icon {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
ha-tab-group-tab::part(base) {
|
||||
padding-inline-start: var(--ha-tab-padding-start, var(--wa-space-l));
|
||||
padding-inline-end: var(--ha-tab-padding-end, var(--wa-space-l));
|
||||
}
|
||||
ha-tab-group-tab::part(base) {
|
||||
padding-top: calc((var(--ha-tab-group-tab-height) - 20px) / 2);
|
||||
}
|
||||
ha-tab-group-tab.icon-only::part(base),
|
||||
ha-tab-group-tab.icon-and-title::part(base) {
|
||||
ha-tab-group-tab.icon::part(base) {
|
||||
padding-top: calc((var(--ha-tab-group-tab-height) - 20px) / 2 - 2px);
|
||||
padding-bottom: calc(
|
||||
(var(--ha-tab-group-tab-height) - 20px) / 2 - 4px
|
||||
);
|
||||
}
|
||||
ha-tab-group-tab.icon-and-title ha-icon {
|
||||
margin-inline-end: var(--ha-space-2);
|
||||
.tab-bar ha-tab-group-tab {
|
||||
--ha-tab-group-tab-height: var(--tab-bar-height, 56px);
|
||||
}
|
||||
.edit-mode ha-tab-group-tab[aria-selected="true"]::part(base) {
|
||||
padding: 0;
|
||||
|
||||
@@ -11,10 +11,7 @@ import type { LovelaceBadgeConfig } from "../../../../data/lovelace/config/badge
|
||||
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
|
||||
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type {
|
||||
EmptyStateCardConfig,
|
||||
HeadingCardConfig,
|
||||
} from "../../cards/types";
|
||||
import type { HeadingCardConfig } from "../../cards/types";
|
||||
import { computeAreaTileCardConfig } from "../areas/helpers/areas-strategy-helper";
|
||||
import {
|
||||
getSummaryLabel,
|
||||
@@ -357,26 +354,6 @@ export class HomeAreaViewStrategy extends ReactiveElement {
|
||||
});
|
||||
}
|
||||
|
||||
// No sections, show empty state
|
||||
if (sections.length === 0) {
|
||||
return {
|
||||
type: "panel",
|
||||
cards: [
|
||||
{
|
||||
type: "empty-state",
|
||||
icon: "mdi:sofa-outline",
|
||||
content_only: true,
|
||||
title: hass.localize(
|
||||
"ui.panel.lovelace.strategy.areas.empty_state_title"
|
||||
),
|
||||
content: hass.localize(
|
||||
"ui.panel.lovelace.strategy.areas.empty_state_content"
|
||||
),
|
||||
} as EmptyStateCardConfig,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import type { AreasDisplayValue } from "../../../../components/ha-areas-display-
|
||||
import { getEnergyPreferences } from "../../../../data/energy";
|
||||
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { EmptyStateCardConfig } from "../../cards/types";
|
||||
import { generateDefaultViewConfig } from "../../common/generate-lovelace-config";
|
||||
|
||||
export interface OriginalStatesViewStrategyConfig {
|
||||
@@ -65,33 +64,9 @@ export class OriginalStatesViewStrategy extends ReactiveElement {
|
||||
|
||||
// User has no entities
|
||||
if (view.cards!.length === 0) {
|
||||
return {
|
||||
type: "panel",
|
||||
cards: [
|
||||
{
|
||||
type: "empty-state",
|
||||
icon: "mdi:home-assistant",
|
||||
content_only: true,
|
||||
title: hass.localize(
|
||||
"ui.panel.lovelace.strategy.original-states.empty_state_title"
|
||||
),
|
||||
content: hass.localize(
|
||||
"ui.panel.lovelace.strategy.original-states.empty_state_content"
|
||||
),
|
||||
...(hass.user?.is_admin
|
||||
? {
|
||||
action_button_text: hass.localize(
|
||||
"ui.panel.lovelace.strategy.original-states.empty_state_action"
|
||||
),
|
||||
tap_action: {
|
||||
action: "navigate",
|
||||
navigation_path: "/config/integrations/dashboard",
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
} as EmptyStateCardConfig,
|
||||
],
|
||||
};
|
||||
view.cards!.push({
|
||||
type: "empty-state",
|
||||
});
|
||||
}
|
||||
|
||||
return view;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ActionDetail } from "@material/mwc-list";
|
||||
import {
|
||||
mdiAlphaABoxOutline,
|
||||
mdiArrowLeft,
|
||||
mdiDotsVertical,
|
||||
mdiGrid,
|
||||
mdiListBoxOutline,
|
||||
@@ -96,6 +97,7 @@ class PanelMediaBrowser extends LitElement {
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
slot="navigationIcon"
|
||||
.path=${mdiArrowLeft}
|
||||
@click=${this._goBack}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`
|
||||
|
||||
@@ -156,9 +156,6 @@ export const semanticColorStyles = css`
|
||||
/* Surfaces */
|
||||
--ha-color-surface-default: var(--ha-color-neutral-95);
|
||||
--ha-color-on-surface-default: var(--ha-color-neutral-05);
|
||||
|
||||
/* Scrollable fade */
|
||||
--ha-color-shadow-scrollable-fade: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -50,9 +50,7 @@ export class HaStateControlHumidifierHumidity extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private get _step() {
|
||||
return this.stateObj.attributes.target_humidity_step ?? 1;
|
||||
}
|
||||
private _step = 1;
|
||||
|
||||
private get _min() {
|
||||
return this.stateObj.attributes.min_humidity ?? 0;
|
||||
|
||||
@@ -2172,8 +2172,7 @@
|
||||
},
|
||||
"other": {
|
||||
"title": "Other",
|
||||
"my_link": "get My Home Assistant link",
|
||||
"show_shortcuts": "show keyboard shortcuts"
|
||||
"my_link": "get My Home Assistant link"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2319,7 +2318,7 @@
|
||||
},
|
||||
"companion": {
|
||||
"main": "Companion app",
|
||||
"secondary": "Sensors, widgets, notifications and more"
|
||||
"secondary": "Location and notifications"
|
||||
},
|
||||
"system": {
|
||||
"main": "System",
|
||||
@@ -2383,7 +2382,6 @@
|
||||
"updates_refreshed": "State of {count} {count, plural,\n one {update}\n other {updates}\n} refreshed",
|
||||
"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}",
|
||||
"unable_to_fetch": "Unable to load updates",
|
||||
"more_updates": "Show all updates",
|
||||
"show": "show",
|
||||
@@ -3341,8 +3339,7 @@
|
||||
"type": "Type",
|
||||
"editable": "Editable",
|
||||
"category": "Category",
|
||||
"area": "Area",
|
||||
"voice_assistants": "Voice assistants"
|
||||
"area": "Area"
|
||||
},
|
||||
"create_helper": "Create helper",
|
||||
"no_helpers": "Looks like you don't have any helpers yet!",
|
||||
@@ -3983,8 +3980,7 @@
|
||||
"state": "State",
|
||||
"category": "Category",
|
||||
"area": "Area",
|
||||
"icon": "Icon",
|
||||
"voice_assistants": "Voice assistants"
|
||||
"icon": "Icon"
|
||||
},
|
||||
"bulk_action": "Action",
|
||||
"bulk_actions": {
|
||||
@@ -5001,8 +4997,7 @@
|
||||
"state": "State",
|
||||
"category": "Category",
|
||||
"area": "Area",
|
||||
"icon": "Icon",
|
||||
"voice_assistants": "Voice assistants"
|
||||
"icon": "Icon"
|
||||
},
|
||||
"edit_category": "[%key:ui::panel::config::automation::picker::edit_category%]",
|
||||
"assign_category": "[%key:ui::panel::config::automation::picker::assign_category%]",
|
||||
@@ -5130,8 +5125,7 @@
|
||||
"category": "Category",
|
||||
"editable": "[%key:ui::panel::config::helpers::picker::headers::editable%]",
|
||||
"area": "Area",
|
||||
"icon": "Icon",
|
||||
"voice_assistants": "Voice assistants"
|
||||
"icon": "Icon"
|
||||
},
|
||||
"edit_category": "[%key:ui::panel::config::automation::picker::edit_category%]",
|
||||
"assign_category": "[%key:ui::panel::config::automation::picker::assign_category%]",
|
||||
@@ -5584,8 +5578,7 @@
|
||||
"domain": "Domain",
|
||||
"availability": "Availability",
|
||||
"visibility": "Visibility",
|
||||
"enabled": "Enabled",
|
||||
"voice_assistants": "Voice assistants"
|
||||
"enabled": "Enabled"
|
||||
},
|
||||
"selected": "{number} selected",
|
||||
"enable_selected": {
|
||||
@@ -6018,31 +6011,26 @@
|
||||
},
|
||||
"bluetooth": {
|
||||
"title": "Bluetooth",
|
||||
"settings_title": "Bluetooth adapters",
|
||||
"settings_title": "Bluetooth settings",
|
||||
"option_flow": "Configure Bluetooth options",
|
||||
"advertisement_monitor": "Advertisement monitor",
|
||||
"advertisement_monitor_details": "The advertisement monitor listens for Bluetooth advertisements and displays the data in a structured format.",
|
||||
"connection_slot_allocations_monitor": "Connection slot allocations monitor",
|
||||
"connection_slot_allocations_monitor_description": "The connection slot allocations monitor displays the (GATT) connection slot allocations for each adapter. Each remote Bluetooth device that requires an active connection will use one connection slot while the Bluetooth device is connecting or connected.",
|
||||
"connection_slot_allocations_monitor_details": "The connection slot allocations monitor displays the (GATT) connection slot allocations for the adapter. This adapter supports up to {slots} simultaneous connections. Each remote Bluetooth device that requires an active connection will use one connection slot while the Bluetooth device is connecting or connected.",
|
||||
"connection_monitor": "Connection monitor",
|
||||
"visualization": "Visualization",
|
||||
"used_connection_slot_allocations": "Used connection slot allocations",
|
||||
"no_connections": "No active connections",
|
||||
"active_connections": "connections",
|
||||
"no_advertisements_found": "No matching Bluetooth advertisements found",
|
||||
"no_connection_slot_allocations": "No connection slot allocations information available",
|
||||
"no_connection_slots": "No connection slots",
|
||||
"no_active_connection_support": "This adapter does not support making active (GATT) connections.",
|
||||
"no_scanner_state_available": "No scanner state available",
|
||||
"scanner_state_unknown": "State unknown",
|
||||
"current_scanning_mode": "Current scanning mode",
|
||||
"requested_scanning_mode": "Requested scanning mode",
|
||||
"scanning_mode_none": "none",
|
||||
"scanning_mode_active": "active",
|
||||
"scanning_mode_passive": "passive",
|
||||
"scanning_mode_active_label": "Active scanning",
|
||||
"scanning_mode_passive_label": "Passive scanning",
|
||||
"scanning_mode_none_label": "No scanning",
|
||||
"scanner_mode_mismatch": "{name} requested {requested} mode but is operating in {current} mode. The scanner is in a bad state and needs to be power cycled.",
|
||||
"scanner_mode_mismatch": "Scanner requested {requested} mode but is operating in {current} mode. The scanner is in a bad state and needs to be power cycled.",
|
||||
"scanner_mode_mismatch_remote": "For proxies: reboot the device",
|
||||
"scanner_mode_mismatch_usb": "For USB adapters: unplug and plug back in",
|
||||
"scanner_mode_mismatch_uart": "For UART/onboard adapters: power down the system completely and power it back up",
|
||||
@@ -6845,15 +6833,9 @@
|
||||
},
|
||||
"matter": {
|
||||
"panel": {
|
||||
"thread_panel": "Visit Thread panel",
|
||||
"add_device": "Add device",
|
||||
"status_title": "status",
|
||||
"status_online": "online",
|
||||
"status_offline": "offline",
|
||||
"devices": "{count, plural,\n one {# device}\n other {# devices}\n}",
|
||||
"developer_tools_title": "Developer tools",
|
||||
"developer_tools_description": "Advanced commissioning options for development",
|
||||
"developer_tools_info": "These options are intended for developers and advanced users. In most cases, use the \"Add device\" button in the bottom right corner instead.",
|
||||
"thread_panel": "Visit Thread Panel",
|
||||
"experimental_note": "Matter is still in the early phase of development, it is not meant to be used in production. This panel is for development only.",
|
||||
"add_devices": "You can add Matter devices by commissioning them if they are not set up yet, or share them from another controller and enter the sharing code.",
|
||||
"mobile_app_commisioning": "Commission device with mobile app",
|
||||
"commission_device": "Commission device",
|
||||
"add_shared_device": "Add shared device",
|
||||
@@ -7233,14 +7215,10 @@
|
||||
"lovelace": {
|
||||
"strategy": {
|
||||
"original-states": {
|
||||
"helpers": "[%key:ui::panel::config::helpers::caption%]",
|
||||
"empty_state_title": "Welcome Home",
|
||||
"empty_state_content": "This page allows you to control your devices, however it looks like you have no devices set up yet.",
|
||||
"empty_state_action": "Go to the integrations page"
|
||||
"helpers": "[%key:ui::panel::config::helpers::caption%]"
|
||||
},
|
||||
"areas": {
|
||||
"empty_state_title": "No devices",
|
||||
"empty_state_content": "There are no devices assigned to this area yet. Assign devices to this area to see them here.",
|
||||
"no_entities": "No entities in this area.",
|
||||
"sensors": "Sensors",
|
||||
"sensors_description": "To display temperature and humidity sensors in the overview and in the area view, add a sensor to that area and {edit_the_area} to configure related sensors.",
|
||||
"edit_the_area": "edit the area",
|
||||
@@ -7312,6 +7290,11 @@
|
||||
"no_url": "No URL to open specified",
|
||||
"no_action": "No action to run specified"
|
||||
},
|
||||
"empty_state": {
|
||||
"title": "Welcome Home",
|
||||
"no_devices": "This page allows you to control your devices, however it looks like you have no devices set up yet. Head to the integrations page to get started.",
|
||||
"go_to_integrations_page": "Go to the integrations page."
|
||||
},
|
||||
"entities": {
|
||||
"never_triggered": "Never triggered"
|
||||
},
|
||||
@@ -7609,7 +7592,6 @@
|
||||
"sidebar": "Sidebar",
|
||||
"panel": "Panel (single card)"
|
||||
},
|
||||
"show_icon_and_title": "Show icon and title",
|
||||
"subview": "Subview",
|
||||
"max_columns": "Max number of sections wide",
|
||||
"section_specifics": "Sections view specific settings",
|
||||
@@ -7617,7 +7599,6 @@
|
||||
"dense_section_placement_helper": "Will try to fill gaps with sections that fit the gap. This may make section placement less predictable.",
|
||||
"top_margin": "Add additional space above",
|
||||
"top_margin_helper": "Helps reveal more of the background",
|
||||
"show_icon_and_title_helper": "Show both icon and text title.",
|
||||
"subview_helper": "Subviews don't appear in tabs and have a back button.",
|
||||
"path_helper": "This value will become part of the URL path to open this view.",
|
||||
"edit_ui": "Edit in visual editor",
|
||||
@@ -7983,17 +7964,6 @@
|
||||
"name": "Entity",
|
||||
"description": "The Entity card gives you a quick overview of your entity's state."
|
||||
},
|
||||
"empty_state": {
|
||||
"name": "Empty state",
|
||||
"description": "The Empty state card displays a centered message with an optional icon and action button.",
|
||||
"style": "Style",
|
||||
"style_options": {
|
||||
"card": "Card",
|
||||
"content-only": "Content only"
|
||||
},
|
||||
"content": "Content",
|
||||
"action_text": "Action button text"
|
||||
},
|
||||
"button": {
|
||||
"name": "Button",
|
||||
"description": "The Button card allows you to add buttons to perform tasks.",
|
||||
@@ -8241,6 +8211,18 @@
|
||||
"large": "Large"
|
||||
},
|
||||
"show_seconds": "Display seconds",
|
||||
"date": {
|
||||
"label": "Date",
|
||||
"description": "Whether to show the date on the clock. Can also be a custom date format."
|
||||
},
|
||||
"dates": {
|
||||
"none": "No date",
|
||||
"short": "Short",
|
||||
"long": "Long",
|
||||
"day": "Day only",
|
||||
"day-month": "Day and month",
|
||||
"day-month-long": "Day and month (long)"
|
||||
},
|
||||
"time_format": "[%key:ui::panel::profile::time_format::dropdown_label%]",
|
||||
"time_formats": {
|
||||
"auto": "Use user settings",
|
||||
@@ -9260,9 +9242,6 @@
|
||||
"active_listeners": "Active listeners",
|
||||
"count_listeners": "({count} {count, plural,\n one {listener}\n other {listeners}\n})",
|
||||
"listen_to_events": "Listen to events",
|
||||
"filter_events": "Filter events",
|
||||
"filter_helper": "Only capture events containing the filter string.",
|
||||
"filter_ignored": "Ignored {count} events.",
|
||||
"listening_to": "Listening to",
|
||||
"subscribe_to": "Event to subscribe to",
|
||||
"start_listening": "Start listening",
|
||||
|
||||
538
yarn.lock
538
yarn.lock
@@ -1204,14 +1204,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@bundle-stats/plugin-webpack-filter@npm:4.21.8":
|
||||
version: 4.21.8
|
||||
resolution: "@bundle-stats/plugin-webpack-filter@npm:4.21.8"
|
||||
"@bundle-stats/plugin-webpack-filter@npm:4.21.7":
|
||||
version: 4.21.7
|
||||
resolution: "@bundle-stats/plugin-webpack-filter@npm:4.21.7"
|
||||
dependencies:
|
||||
tslib: "npm:2.8.1"
|
||||
peerDependencies:
|
||||
core-js: ^3.0.0
|
||||
checksum: 10/909d1ca31880b8f40fdad2fdc60484f086c3167d1654688be1fdd3c280a94ca73ee31dcde5ea118dcc0ee5dfe0dbfedcdec521cff831f13c69baa5af79beabcf
|
||||
checksum: 10/c63f2d0742bc0729450b08523cf0b38711c336cb284c9e2beeae4509cbdf891626000e60e4f771ca53f0de6fcb8824998d01bb99016b5f3be7b70cfff8dc845f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1282,15 +1282,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@codemirror/view@npm:6.39.9, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0":
|
||||
version: 6.39.9
|
||||
resolution: "@codemirror/view@npm:6.39.9"
|
||||
"@codemirror/view@npm:6.39.8, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0":
|
||||
version: 6.39.8
|
||||
resolution: "@codemirror/view@npm:6.39.8"
|
||||
dependencies:
|
||||
"@codemirror/state": "npm:^6.5.0"
|
||||
crelt: "npm:^1.0.6"
|
||||
style-mod: "npm:^4.1.0"
|
||||
w3c-keyname: "npm:^2.2.4"
|
||||
checksum: 10/9e86b35f31fd4f8b4c2fe608fa6116ddc71261acd842c405de41de1f752268c47ea8e0c400818b4d0481a629e1f773dda9e6f0d24d38ed6a9f6b3d58b2dff669
|
||||
checksum: 10/a15941940fabc9b595da00a7760947cf7ce83f3f819be31250a73d2a1de5d1b5528a5803aa19c74656d2d7cbc39f47daec4962190ffc0849f4f359e45b4f1c3a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1582,18 +1582,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-community/eslint-utils@npm:^4.8.0, @eslint-community/eslint-utils@npm:^4.9.1":
|
||||
version: 4.9.1
|
||||
resolution: "@eslint-community/eslint-utils@npm:4.9.1"
|
||||
"@eslint-community/eslint-utils@npm:^4.7.0, @eslint-community/eslint-utils@npm:^4.8.0":
|
||||
version: 4.9.0
|
||||
resolution: "@eslint-community/eslint-utils@npm:4.9.0"
|
||||
dependencies:
|
||||
eslint-visitor-keys: "npm:^3.4.3"
|
||||
peerDependencies:
|
||||
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
|
||||
checksum: 10/863b5467868551c9ae34d03eefe634633d08f623fc7b19d860f8f26eb6f303c1a5934253124163bee96181e45ed22bf27473dccc295937c3078493a4a8c9eddd
|
||||
checksum: 10/89b1eb3137e14c379865e60573f524fcc0ee5c4b0c7cd21090673e75e5a720f14b92f05ab2d02704c2314b67e67b6f96f3bb209ded6b890ced7b667aa4bf1fa2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-community/regexpp@npm:^4.12.1, @eslint-community/regexpp@npm:^4.12.2":
|
||||
"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.12.1":
|
||||
version: 4.12.2
|
||||
resolution: "@eslint-community/regexpp@npm:4.12.2"
|
||||
checksum: 10/049b280fddf71dd325514e0a520024969431dc3a8b02fa77476e6820e9122f28ab4c9168c11821f91a27982d2453bcd7a66193356ea84e84fb7c8d793be1ba0c
|
||||
@@ -1708,166 +1708,166 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/ecma402-abstract@npm:3.0.8":
|
||||
version: 3.0.8
|
||||
resolution: "@formatjs/ecma402-abstract@npm:3.0.8"
|
||||
"@formatjs/ecma402-abstract@npm:3.0.7":
|
||||
version: 3.0.7
|
||||
resolution: "@formatjs/ecma402-abstract@npm:3.0.7"
|
||||
dependencies:
|
||||
"@formatjs/fast-memoize": "npm:3.0.3"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.5"
|
||||
"@formatjs/fast-memoize": "npm:3.0.2"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.4"
|
||||
decimal.js: "npm:^10.4.3"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/5b8e00790f79c011a6928644baee1409b86353576a91f7a8e9b4562a4a5d486997036ff04cd8451891ab8b7691e683c34cd40d76d4a1751e7df6052d46c54f06
|
||||
checksum: 10/a79f43d3c3e5301722d3288806e6910d8598e2f0c849775398f6e20bac78b568db9c03b6605c9db8eb3aa80dff4fc260cb153e67b167924a547e0d29a1b2d8c3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/fast-memoize@npm:3.0.3":
|
||||
version: 3.0.3
|
||||
resolution: "@formatjs/fast-memoize@npm:3.0.3"
|
||||
"@formatjs/fast-memoize@npm:3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "@formatjs/fast-memoize@npm:3.0.2"
|
||||
dependencies:
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/466aa1250eee77897455b6e0282bd178357e326790e18f59e33c77371ebb6099cbf8c520300193ef10ab4909174286ab1d922fa930f02b81dddf08d28eaef1a1
|
||||
checksum: 10/381afd816ca67d7e3e333247f1115ede11420226d72f109c54e8300741212b9bb08a6bacec17d0f0f2172789e39d0cef61fcae0a484e1dff2e9c33eee930694e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/icu-messageformat-parser@npm:3.3.0":
|
||||
version: 3.3.0
|
||||
resolution: "@formatjs/icu-messageformat-parser@npm:3.3.0"
|
||||
"@formatjs/icu-messageformat-parser@npm:3.2.1":
|
||||
version: 3.2.1
|
||||
resolution: "@formatjs/icu-messageformat-parser@npm:3.2.1"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.8"
|
||||
"@formatjs/icu-skeleton-parser": "npm:2.0.8"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.7"
|
||||
"@formatjs/icu-skeleton-parser": "npm:2.0.7"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/a8f03e9799c9b4f682f6051ea09d3327abd93dbc1ce17b55e07ec45da7d6e3609c1c87a2ad1bfccbd7bdf54c903d98e8bbe06fa282c4dc44263c55b1927f154e
|
||||
checksum: 10/4e853881ad10472547e893648474841f7943b16c0262c9db7e92fd29d49214da0b5723e48f53872ad3673b3f4a9d11c6683e102f6a864efc78d3a4203a451dff
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/icu-skeleton-parser@npm:2.0.8":
|
||||
version: 2.0.8
|
||||
resolution: "@formatjs/icu-skeleton-parser@npm:2.0.8"
|
||||
"@formatjs/icu-skeleton-parser@npm:2.0.7":
|
||||
version: 2.0.7
|
||||
resolution: "@formatjs/icu-skeleton-parser@npm:2.0.7"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.8"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.7"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/9917502129e663da5ec8008a9a27c9032e01477adcf4c71788eb475225f57f4602711f9b8f3a3b669c801b0232e9eb53e58c0fdb11aa81e2339e1416f636be3f
|
||||
checksum: 10/4a496854f877af04f87ac9bcf36593bfe4ec949a58a695c40c87dcbfff4a15473941448d6812644ab6fc832fc96408d6374015de74d3842cb03654f136b783d1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-datetimeformat@npm:7.1.2":
|
||||
version: 7.1.2
|
||||
resolution: "@formatjs/intl-datetimeformat@npm:7.1.2"
|
||||
"@formatjs/intl-datetimeformat@npm:7.1.1":
|
||||
version: 7.1.1
|
||||
resolution: "@formatjs/intl-datetimeformat@npm:7.1.1"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.8"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.5"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.7"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.4"
|
||||
decimal.js: "npm:^10.4.3"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/e31d35aa8c38b5fd08254726d2d90629629534b5bbc134f2264b60eb1bce36224526112d6ceb2c4eeaed73b29e77c21d3fc8a906b767e28613798dd4ea0819cc
|
||||
checksum: 10/cda6fd9d6244ad11f9ed2d780971388643eba514397417d63de48a8aaac2ccc43ecc2a4331e4458f0ccc16d29516ae1d1addd5b790edbea49432d58ccdf4e10e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-displaynames@npm:7.1.2":
|
||||
version: 7.1.2
|
||||
resolution: "@formatjs/intl-displaynames@npm:7.1.2"
|
||||
"@formatjs/intl-displaynames@npm:7.1.1":
|
||||
version: 7.1.1
|
||||
resolution: "@formatjs/intl-displaynames@npm:7.1.1"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.8"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.5"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.7"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.4"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/8c2a98d8f5fb6361d380beaae107cf58ce9ab963ea291e37081dc896407686160683837259d634e9a46a4a41d6c0bee93403de33c929d91ec320ee7bcfaed20b
|
||||
checksum: 10/f7b44c546b26cdd50b60983f5a8cfa672ac5b0a03f9f3c85caac98bc20b6768721e67fde62b977cfa98c3720f9cb1dadfa5448be88a3ee5616f48b1f84b1474f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-durationformat@npm:0.9.2":
|
||||
version: 0.9.2
|
||||
resolution: "@formatjs/intl-durationformat@npm:0.9.2"
|
||||
"@formatjs/intl-durationformat@npm:0.9.1":
|
||||
version: 0.9.1
|
||||
resolution: "@formatjs/intl-durationformat@npm:0.9.1"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.8"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.5"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.7"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.4"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/7c4492640683aa30fbcbd844878e50eb44ef7aa0a415fd6cf10a1b8804cc80324437b4f8121bac2d56d3f4ec4d3c61e14e870852ec6b233a6c5747292fbc3585
|
||||
checksum: 10/b9fae713b39238c4658102b0edbb684e786007e2c95b8135435009b5c7c64c4a5d8cd50b92326f9d2c4e2b1fbbb61f49ee0709daad82dbf90636cd79713596d5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-enumerator@npm:2.1.2":
|
||||
version: 2.1.2
|
||||
resolution: "@formatjs/intl-enumerator@npm:2.1.2"
|
||||
"@formatjs/intl-enumerator@npm:2.1.1":
|
||||
version: 2.1.1
|
||||
resolution: "@formatjs/intl-enumerator@npm:2.1.1"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.8"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.7"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/1a551a3a3d4f113d071b503946ea8aaf6b18b69fa746cc6d7e2ca520023794f4f1b82777cd7e9632ea8980473b951c252d52b8375f264e5c151c6cac6a19d6e5
|
||||
checksum: 10/cabc2387bead8498c32e3068b98557a3e217777bbccf46ee2eb4918decab15ae750cf20f4b6186deecb3e0242873c0aba40e2294665683dabe1f1f67333b5195
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-getcanonicallocales@npm:3.1.2":
|
||||
version: 3.1.2
|
||||
resolution: "@formatjs/intl-getcanonicallocales@npm:3.1.2"
|
||||
"@formatjs/intl-getcanonicallocales@npm:3.1.1":
|
||||
version: 3.1.1
|
||||
resolution: "@formatjs/intl-getcanonicallocales@npm:3.1.1"
|
||||
dependencies:
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/490f92e9b4c8d887df9afc602fbc857dc47f9e4cca29e73fcb92e35083cdef6091bcacce59f9d8f75334a3010ce41d9ae16143c1b1827550e29d0d01558516b4
|
||||
checksum: 10/23e6a3c1cf42257f130fd6b1cc080ecaf7b267743e3990ea7eb9f6cc981c649f2c18c935d1f581593ec241095a982c248441ac10d0e871575463dbfebf942f5c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-listformat@npm:8.1.2":
|
||||
version: 8.1.2
|
||||
resolution: "@formatjs/intl-listformat@npm:8.1.2"
|
||||
"@formatjs/intl-listformat@npm:8.1.1":
|
||||
version: 8.1.1
|
||||
resolution: "@formatjs/intl-listformat@npm:8.1.1"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.8"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.5"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.7"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.4"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/ce9f5d821bdd9ec0b973f8869b17fcb84808442a1d43209099a6e4a0c44d5bd2a978c33d61a0929ba6e02aef5bbea2d36129e16c311f433b3cfa6ab7c07f51d2
|
||||
checksum: 10/29daf29492f396a6266324196af764123b9bbd19fcd25ed02dfccbc4f31dcc592be47e02c39d8b5c1f8b8411f22ca961f82b4c66a98e7e44dd8cfa3459ec42f3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-locale@npm:5.1.2":
|
||||
version: 5.1.2
|
||||
resolution: "@formatjs/intl-locale@npm:5.1.2"
|
||||
"@formatjs/intl-locale@npm:5.1.1":
|
||||
version: 5.1.1
|
||||
resolution: "@formatjs/intl-locale@npm:5.1.1"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.8"
|
||||
"@formatjs/intl-enumerator": "npm:2.1.2"
|
||||
"@formatjs/intl-getcanonicallocales": "npm:3.1.2"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.7"
|
||||
"@formatjs/intl-enumerator": "npm:2.1.1"
|
||||
"@formatjs/intl-getcanonicallocales": "npm:3.1.1"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/85b1257231461e9ccf7ba0b4fc65d97a7a4e4cbb7fdf50da209fe0b91fb56e00f19e3ca8bacf67eb581feda6ceb861112d9cc4624805e19c80e8d0b63535b821
|
||||
checksum: 10/02be5269e4ef1d058d5be8a0fe580f775e6a760ed4417ebf43a651bc6f0234833528ab76d64df276857d53d90ca7fcd022d0f5d667df3be485c85ae7a9bee617
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-localematcher@npm:0.7.5":
|
||||
version: 0.7.5
|
||||
resolution: "@formatjs/intl-localematcher@npm:0.7.5"
|
||||
"@formatjs/intl-localematcher@npm:0.7.4":
|
||||
version: 0.7.4
|
||||
resolution: "@formatjs/intl-localematcher@npm:0.7.4"
|
||||
dependencies:
|
||||
"@formatjs/fast-memoize": "npm:3.0.3"
|
||||
"@formatjs/fast-memoize": "npm:3.0.2"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/9899304f23206c6fceb66e25d073278f64b2f02b66b81d5f982f3d5b644e83d81ba74520352289b3db3f8afd932c959a4631784386bbcb74105b8126101ea9ac
|
||||
checksum: 10/e2eb759628b35b45d95bddfc33bc8e985afd5d17c3d22bd243ea537fcb3b22eba2bbd8ce9e0f5cd6e29c3b514fe077ba99f5e7e5a1730983c9bfad649dc76985
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-numberformat@npm:9.1.2":
|
||||
version: 9.1.2
|
||||
resolution: "@formatjs/intl-numberformat@npm:9.1.2"
|
||||
"@formatjs/intl-numberformat@npm:9.1.1":
|
||||
version: 9.1.1
|
||||
resolution: "@formatjs/intl-numberformat@npm:9.1.1"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.8"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.5"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.7"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.4"
|
||||
decimal.js: "npm:^10.4.3"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/9eef02a74154a9f7c8c9856073bbb588290e0382672da2d6075c003256549dd491657056e86cd5c33916cefb78af8c9a0fd6695e6051e711d4179418e4f427a7
|
||||
checksum: 10/dd2e0e331162f7d4253dcaebbfcead2e5a13669348890b042fe80d109088e930d08f09a6c74969ccd5e3fd3a3190a5d0c6013645e2cde289d8dc425ae1b3ea3a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-pluralrules@npm:6.1.2":
|
||||
version: 6.1.2
|
||||
resolution: "@formatjs/intl-pluralrules@npm:6.1.2"
|
||||
"@formatjs/intl-pluralrules@npm:6.1.1":
|
||||
version: 6.1.1
|
||||
resolution: "@formatjs/intl-pluralrules@npm:6.1.1"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.8"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.5"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.7"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.4"
|
||||
decimal.js: "npm:^10.4.3"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/e1f611094eb1d1dafccdf815933062e014ac73c1bfba2ded7654d429d1366be15d77b06f8a00e165d530e6b3488995ef38fb73114e0065edc71eda7cb1eb767c
|
||||
checksum: 10/94b900a5396bb066e61fc76af01e4634b60935a66ae27e57d148210dacc5ba7d95ee1dd2158ece1b240da50a9f88d44929af9bae95db6839f025f6ed03625dc9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-relativetimeformat@npm:12.1.2":
|
||||
version: 12.1.2
|
||||
resolution: "@formatjs/intl-relativetimeformat@npm:12.1.2"
|
||||
"@formatjs/intl-relativetimeformat@npm:12.1.1":
|
||||
version: 12.1.1
|
||||
resolution: "@formatjs/intl-relativetimeformat@npm:12.1.1"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.8"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.5"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.7"
|
||||
"@formatjs/intl-localematcher": "npm:0.7.4"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/bb324d20baf9e20588425196bef49f6096578bb7b90317b5c356b775e4b36ea8eb39e11565d692bd02b9f20c2d3021f07c559bf58f03817d685ea0c322f851cd
|
||||
checksum: 10/102a694f7b9764aa3e43702af4a2e47e6c9df7116815810a3210d80919ffe4b60e8a2098ebd424d3074df052b5fc3b5846ff6691a09f53103186253a006e5354
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3993,92 +3993,92 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-darwin-arm64@npm:1.7.1":
|
||||
version: 1.7.1
|
||||
resolution: "@rspack/binding-darwin-arm64@npm:1.7.1"
|
||||
"@rspack/binding-darwin-arm64@npm:1.7.0":
|
||||
version: 1.7.0
|
||||
resolution: "@rspack/binding-darwin-arm64@npm:1.7.0"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-darwin-x64@npm:1.7.1":
|
||||
version: 1.7.1
|
||||
resolution: "@rspack/binding-darwin-x64@npm:1.7.1"
|
||||
"@rspack/binding-darwin-x64@npm:1.7.0":
|
||||
version: 1.7.0
|
||||
resolution: "@rspack/binding-darwin-x64@npm:1.7.0"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-arm64-gnu@npm:1.7.1":
|
||||
version: 1.7.1
|
||||
resolution: "@rspack/binding-linux-arm64-gnu@npm:1.7.1"
|
||||
"@rspack/binding-linux-arm64-gnu@npm:1.7.0":
|
||||
version: 1.7.0
|
||||
resolution: "@rspack/binding-linux-arm64-gnu@npm:1.7.0"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-arm64-musl@npm:1.7.1":
|
||||
version: 1.7.1
|
||||
resolution: "@rspack/binding-linux-arm64-musl@npm:1.7.1"
|
||||
"@rspack/binding-linux-arm64-musl@npm:1.7.0":
|
||||
version: 1.7.0
|
||||
resolution: "@rspack/binding-linux-arm64-musl@npm:1.7.0"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-x64-gnu@npm:1.7.1":
|
||||
version: 1.7.1
|
||||
resolution: "@rspack/binding-linux-x64-gnu@npm:1.7.1"
|
||||
"@rspack/binding-linux-x64-gnu@npm:1.7.0":
|
||||
version: 1.7.0
|
||||
resolution: "@rspack/binding-linux-x64-gnu@npm:1.7.0"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-x64-musl@npm:1.7.1":
|
||||
version: 1.7.1
|
||||
resolution: "@rspack/binding-linux-x64-musl@npm:1.7.1"
|
||||
"@rspack/binding-linux-x64-musl@npm:1.7.0":
|
||||
version: 1.7.0
|
||||
resolution: "@rspack/binding-linux-x64-musl@npm:1.7.0"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-wasm32-wasi@npm:1.7.1":
|
||||
version: 1.7.1
|
||||
resolution: "@rspack/binding-wasm32-wasi@npm:1.7.1"
|
||||
"@rspack/binding-wasm32-wasi@npm:1.7.0":
|
||||
version: 1.7.0
|
||||
resolution: "@rspack/binding-wasm32-wasi@npm:1.7.0"
|
||||
dependencies:
|
||||
"@napi-rs/wasm-runtime": "npm:1.0.7"
|
||||
conditions: cpu=wasm32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-win32-arm64-msvc@npm:1.7.1":
|
||||
version: 1.7.1
|
||||
resolution: "@rspack/binding-win32-arm64-msvc@npm:1.7.1"
|
||||
"@rspack/binding-win32-arm64-msvc@npm:1.7.0":
|
||||
version: 1.7.0
|
||||
resolution: "@rspack/binding-win32-arm64-msvc@npm:1.7.0"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-win32-ia32-msvc@npm:1.7.1":
|
||||
version: 1.7.1
|
||||
resolution: "@rspack/binding-win32-ia32-msvc@npm:1.7.1"
|
||||
"@rspack/binding-win32-ia32-msvc@npm:1.7.0":
|
||||
version: 1.7.0
|
||||
resolution: "@rspack/binding-win32-ia32-msvc@npm:1.7.0"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-win32-x64-msvc@npm:1.7.1":
|
||||
version: 1.7.1
|
||||
resolution: "@rspack/binding-win32-x64-msvc@npm:1.7.1"
|
||||
"@rspack/binding-win32-x64-msvc@npm:1.7.0":
|
||||
version: 1.7.0
|
||||
resolution: "@rspack/binding-win32-x64-msvc@npm:1.7.0"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding@npm:1.7.1":
|
||||
version: 1.7.1
|
||||
resolution: "@rspack/binding@npm:1.7.1"
|
||||
"@rspack/binding@npm:1.7.0":
|
||||
version: 1.7.0
|
||||
resolution: "@rspack/binding@npm:1.7.0"
|
||||
dependencies:
|
||||
"@rspack/binding-darwin-arm64": "npm:1.7.1"
|
||||
"@rspack/binding-darwin-x64": "npm:1.7.1"
|
||||
"@rspack/binding-linux-arm64-gnu": "npm:1.7.1"
|
||||
"@rspack/binding-linux-arm64-musl": "npm:1.7.1"
|
||||
"@rspack/binding-linux-x64-gnu": "npm:1.7.1"
|
||||
"@rspack/binding-linux-x64-musl": "npm:1.7.1"
|
||||
"@rspack/binding-wasm32-wasi": "npm:1.7.1"
|
||||
"@rspack/binding-win32-arm64-msvc": "npm:1.7.1"
|
||||
"@rspack/binding-win32-ia32-msvc": "npm:1.7.1"
|
||||
"@rspack/binding-win32-x64-msvc": "npm:1.7.1"
|
||||
"@rspack/binding-darwin-arm64": "npm:1.7.0"
|
||||
"@rspack/binding-darwin-x64": "npm:1.7.0"
|
||||
"@rspack/binding-linux-arm64-gnu": "npm:1.7.0"
|
||||
"@rspack/binding-linux-arm64-musl": "npm:1.7.0"
|
||||
"@rspack/binding-linux-x64-gnu": "npm:1.7.0"
|
||||
"@rspack/binding-linux-x64-musl": "npm:1.7.0"
|
||||
"@rspack/binding-wasm32-wasi": "npm:1.7.0"
|
||||
"@rspack/binding-win32-arm64-msvc": "npm:1.7.0"
|
||||
"@rspack/binding-win32-ia32-msvc": "npm:1.7.0"
|
||||
"@rspack/binding-win32-x64-msvc": "npm:1.7.0"
|
||||
dependenciesMeta:
|
||||
"@rspack/binding-darwin-arm64":
|
||||
optional: true
|
||||
@@ -4100,23 +4100,23 @@ __metadata:
|
||||
optional: true
|
||||
"@rspack/binding-win32-x64-msvc":
|
||||
optional: true
|
||||
checksum: 10/2ebdcfb4787aa6b41f143e386915c6afddea306ea7edddf094071030e8ec49ee4ba84d1bf1ddf4813b34dba8df884bd782eebb90df5244f3c33831f85369877f
|
||||
checksum: 10/152155585fe2b31c68efc1ddaa3272bcff04f483220d6b5c1ac4e35d69622261233657d66e3e0d73f28c08ccfe31742e4c284a9e35da82ee2cf32f896a6bd87b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/core@npm:1.7.1":
|
||||
version: 1.7.1
|
||||
resolution: "@rspack/core@npm:1.7.1"
|
||||
"@rspack/core@npm:1.7.0":
|
||||
version: 1.7.0
|
||||
resolution: "@rspack/core@npm:1.7.0"
|
||||
dependencies:
|
||||
"@module-federation/runtime-tools": "npm:0.22.0"
|
||||
"@rspack/binding": "npm:1.7.1"
|
||||
"@rspack/binding": "npm:1.7.0"
|
||||
"@rspack/lite-tapable": "npm:1.1.0"
|
||||
peerDependencies:
|
||||
"@swc/helpers": ">=0.5.1"
|
||||
peerDependenciesMeta:
|
||||
"@swc/helpers":
|
||||
optional: true
|
||||
checksum: 10/0fe6c0d1fa9ffe9c6b4c0032c6c69b50bdd4ff5d3dbe508ca68630053e1413ef249b3bc2a423e5e16c0bfa1de5da3c39337e87976a22eb86039fc0226ed1880d
|
||||
checksum: 10/79786219475e74ef2e8bb418b33e6fa39eb152639e64a83ee3887427c536d3eac4a09fda1b589f5806d62fda96b4b0350ae1bc8b26e93049be97eaae21dd6a02
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4949,138 +4949,138 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/eslint-plugin@npm:8.52.0":
|
||||
version: 8.52.0
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.52.0"
|
||||
"@typescript-eslint/eslint-plugin@npm:8.51.0":
|
||||
version: 8.51.0
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.51.0"
|
||||
dependencies:
|
||||
"@eslint-community/regexpp": "npm:^4.12.2"
|
||||
"@typescript-eslint/scope-manager": "npm:8.52.0"
|
||||
"@typescript-eslint/type-utils": "npm:8.52.0"
|
||||
"@typescript-eslint/utils": "npm:8.52.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.52.0"
|
||||
ignore: "npm:^7.0.5"
|
||||
"@eslint-community/regexpp": "npm:^4.10.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.51.0"
|
||||
"@typescript-eslint/type-utils": "npm:8.51.0"
|
||||
"@typescript-eslint/utils": "npm:8.51.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.51.0"
|
||||
ignore: "npm:^7.0.0"
|
||||
natural-compare: "npm:^1.4.0"
|
||||
ts-api-utils: "npm:^2.4.0"
|
||||
ts-api-utils: "npm:^2.2.0"
|
||||
peerDependencies:
|
||||
"@typescript-eslint/parser": ^8.52.0
|
||||
"@typescript-eslint/parser": ^8.51.0
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/4f2a2ada2597cfa22c913d436a4ce3f0d20fa17445dda0c6b3eb6088c4b0d1d8ba0ebc0a72c6a1577a3e58c96f7a2f260c201646cb1fb0308a0e248cc9d81cca
|
||||
checksum: 10/cb6c129a9f7e723218458ec86acb86176f7cddc1b41924416478a31fd633c2991849be56e21dca8df0ce535ea2014b361073b043085e39795816de528747c80b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/parser@npm:8.52.0":
|
||||
version: 8.52.0
|
||||
resolution: "@typescript-eslint/parser@npm:8.52.0"
|
||||
"@typescript-eslint/parser@npm:8.51.0":
|
||||
version: 8.51.0
|
||||
resolution: "@typescript-eslint/parser@npm:8.51.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager": "npm:8.52.0"
|
||||
"@typescript-eslint/types": "npm:8.52.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.52.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.52.0"
|
||||
debug: "npm:^4.4.3"
|
||||
"@typescript-eslint/scope-manager": "npm:8.51.0"
|
||||
"@typescript-eslint/types": "npm:8.51.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.51.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.51.0"
|
||||
debug: "npm:^4.3.4"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/f221411fb3cc6c5a9e9fa6bec45cd16d5e5d7c1eeba331c97dae97756103bd4b5f67956e2288d478ad96cce7bc4c3c91b510b06d54283c7c0c86acaf4cdb4abf
|
||||
checksum: 10/ad2cb5d9cea5749fca712699ef971d68506f6170766d465ecc5864b5b5a15401c85d4042231df9edcc200f14b2579e6d4b2fbe66db824d925193004cde02ba79
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/project-service@npm:8.52.0":
|
||||
version: 8.52.0
|
||||
resolution: "@typescript-eslint/project-service@npm:8.52.0"
|
||||
"@typescript-eslint/project-service@npm:8.51.0":
|
||||
version: 8.51.0
|
||||
resolution: "@typescript-eslint/project-service@npm:8.51.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.52.0"
|
||||
"@typescript-eslint/types": "npm:^8.52.0"
|
||||
debug: "npm:^4.4.3"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.51.0"
|
||||
"@typescript-eslint/types": "npm:^8.51.0"
|
||||
debug: "npm:^4.3.4"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/bfa786007ed4a603fb8f31c6e354f0ba0cca576b03e402584ae3cf0d674f07adfbde9e976a5bf165fa44c484d4b4f310bd18b34d1b0e75b4210253edbdaabb87
|
||||
checksum: 10/f8b38bf1c92c3a5d33ae0304fc920b8ac20b24c308c056ea70fb5b257f9447be62fe54e9595260fd8611b36c8a66229e4ca7ef859edad42e1321434986132aac
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/scope-manager@npm:8.52.0":
|
||||
version: 8.52.0
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.52.0"
|
||||
"@typescript-eslint/scope-manager@npm:8.51.0":
|
||||
version: 8.51.0
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.51.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.52.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.52.0"
|
||||
checksum: 10/89d9c04cd2567e6aa9adcbe85e2eab24fbc64bde5a33c688764e7c896e9a02c06aad2ec88e8bdc4d5bfabadbc510906a0cb4f3e0b73a5b80d10218f7a6a4ea27
|
||||
"@typescript-eslint/types": "npm:8.51.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.51.0"
|
||||
checksum: 10/3d873589cd5b39f2bdc1e5b4f0052095f2cc5b2d59115ceea99533346839539ad7123ab0ba43ff4dd28c6afd4e8ae77798beb3618c93f24010104d38dedad6ad
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.52.0, @typescript-eslint/tsconfig-utils@npm:^8.52.0":
|
||||
version: 8.52.0
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.52.0"
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.51.0, @typescript-eslint/tsconfig-utils@npm:^8.51.0":
|
||||
version: 8.51.0
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.51.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/5b26227ab549e20a6b15725a4f8373acb70ae1c83570c8d670e242bfcd22ac0c9111d4d28ea16ee3939572caacce50e113388ce943f238fc2ca17f6c5a040cd2
|
||||
checksum: 10/1a423e2b8bb6900e86f54ef78e967f17e6f51a528c64d78460338b916fda4e44bb8c73a7aba6b129295c59c3f364edbf27b34580f525df700674afbf0fc034df
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/type-utils@npm:8.52.0":
|
||||
version: 8.52.0
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.52.0"
|
||||
"@typescript-eslint/type-utils@npm:8.51.0":
|
||||
version: 8.51.0
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.51.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.52.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.52.0"
|
||||
"@typescript-eslint/utils": "npm:8.52.0"
|
||||
debug: "npm:^4.4.3"
|
||||
ts-api-utils: "npm:^2.4.0"
|
||||
"@typescript-eslint/types": "npm:8.51.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.51.0"
|
||||
"@typescript-eslint/utils": "npm:8.51.0"
|
||||
debug: "npm:^4.3.4"
|
||||
ts-api-utils: "npm:^2.2.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/e46d77192e2678561e2cdefe2c2b1ba8965458a88e6dd0d1967656ff5dcb00b75217ec6b084323710028215f64a65ba6ec288e5b021a0c9a325450b4bcfc4f43
|
||||
checksum: 10/1dd5b72e4c7d6ffeafff1c24f745c335dc4073ec2a5c7e40f1a05be3be91097246c96be8d927389d8e4ccfa15db946a45ac85dbfd0c788d6cf003dced1830c7d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:8.52.0, @typescript-eslint/types@npm:^8.52.0":
|
||||
version: 8.52.0
|
||||
resolution: "@typescript-eslint/types@npm:8.52.0"
|
||||
checksum: 10/05a630c5d25cce74d1bfa51027f1232f2e8a97a8f483ce0274e928373b4633cdf713be53eca39926f0372d52a3335f13786c7910d2edfd546a0cf1d66b3bcf51
|
||||
"@typescript-eslint/types@npm:8.51.0, @typescript-eslint/types@npm:^8.51.0":
|
||||
version: 8.51.0
|
||||
resolution: "@typescript-eslint/types@npm:8.51.0"
|
||||
checksum: 10/34c4b602d2f07edb72879e8af5359b0d54d3787030ae0a8325691972b07f2c6429bef9586938d18ff7df03a2be858064c7890a42136514ff0f2b7847c1432193
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:8.52.0":
|
||||
version: 8.52.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.52.0"
|
||||
"@typescript-eslint/typescript-estree@npm:8.51.0":
|
||||
version: 8.51.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.51.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service": "npm:8.52.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.52.0"
|
||||
"@typescript-eslint/types": "npm:8.52.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.52.0"
|
||||
debug: "npm:^4.4.3"
|
||||
minimatch: "npm:^9.0.5"
|
||||
semver: "npm:^7.7.3"
|
||||
"@typescript-eslint/project-service": "npm:8.51.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.51.0"
|
||||
"@typescript-eslint/types": "npm:8.51.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.51.0"
|
||||
debug: "npm:^4.3.4"
|
||||
minimatch: "npm:^9.0.4"
|
||||
semver: "npm:^7.6.0"
|
||||
tinyglobby: "npm:^0.2.15"
|
||||
ts-api-utils: "npm:^2.4.0"
|
||||
ts-api-utils: "npm:^2.2.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/4e699f44a05e9c487531557a1eaf6412a97f370ec946a03596c8d445f584c3d17e9aa34cde5ce8998ae9d6908c1daffb2c9b523cb07e5988cf249eae6dea50fd
|
||||
checksum: 10/4f9f82b18b9a62e3ffe1253e7914a396ec1c550fec36f862eff9d3d49be4bf01349eb2540be68ecc35b245aab7f72843d2b5cd1e73c8e820db5cf6f3c6542916
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/utils@npm:8.52.0":
|
||||
version: 8.52.0
|
||||
resolution: "@typescript-eslint/utils@npm:8.52.0"
|
||||
"@typescript-eslint/utils@npm:8.51.0":
|
||||
version: 8.51.0
|
||||
resolution: "@typescript-eslint/utils@npm:8.51.0"
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils": "npm:^4.9.1"
|
||||
"@typescript-eslint/scope-manager": "npm:8.52.0"
|
||||
"@typescript-eslint/types": "npm:8.52.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.52.0"
|
||||
"@eslint-community/eslint-utils": "npm:^4.7.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.51.0"
|
||||
"@typescript-eslint/types": "npm:8.51.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.51.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/11a02ab0fd26bb1284dfa8c02d40c54cabd3aa795e82ab26b060ea3839998c28a41822b075f9d23fb51e291e465147213166c8ddaf3c8d5807e70b0a4345d967
|
||||
checksum: 10/e5ced67d08129e62fd6866a72b94fa685cd33aa7f5d1fc3bb9a34187d0fc0b469062ec8dfd44cd8c06494af070a050a314a6db672dda392fbc907b64b1ecc57f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:8.52.0":
|
||||
version: 8.52.0
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.52.0"
|
||||
"@typescript-eslint/visitor-keys@npm:8.51.0":
|
||||
version: 8.51.0
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.51.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.52.0"
|
||||
"@typescript-eslint/types": "npm:8.51.0"
|
||||
eslint-visitor-keys: "npm:^4.2.1"
|
||||
checksum: 10/4d841402cc65e876382ede464b68cf167c7d24905b15225c472516bb759140abbef02f250c6335ca35327f7328975ff3b28c3249a5183319cfd01f1d5541e3c1
|
||||
checksum: 10/922fb3d2e59f6e8a9583a1bc7b13e66d4ec7bd7df3256602810465a7017549b890cd1a7897efc71ed64cc287c718be5dddbc8071b74e8ee41fe441f6d5251d3d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -6887,7 +6887,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.1, debug@npm:^4.4.3":
|
||||
"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.1":
|
||||
version: 4.4.3
|
||||
resolution: "debug@npm:4.4.3"
|
||||
dependencies:
|
||||
@@ -9003,25 +9003,25 @@ __metadata:
|
||||
"@babel/preset-env": "npm:7.28.5"
|
||||
"@babel/runtime": "npm:7.28.4"
|
||||
"@braintree/sanitize-url": "npm:7.1.1"
|
||||
"@bundle-stats/plugin-webpack-filter": "npm:4.21.8"
|
||||
"@bundle-stats/plugin-webpack-filter": "npm:4.21.7"
|
||||
"@codemirror/autocomplete": "npm:6.20.0"
|
||||
"@codemirror/commands": "npm:6.10.1"
|
||||
"@codemirror/language": "npm:6.12.1"
|
||||
"@codemirror/legacy-modes": "npm:6.5.2"
|
||||
"@codemirror/search": "npm:6.5.11"
|
||||
"@codemirror/state": "npm:6.5.3"
|
||||
"@codemirror/view": "npm:6.39.9"
|
||||
"@codemirror/view": "npm:6.39.8"
|
||||
"@date-fns/tz": "npm:1.4.1"
|
||||
"@egjs/hammerjs": "npm:2.0.17"
|
||||
"@formatjs/intl-datetimeformat": "npm:7.1.2"
|
||||
"@formatjs/intl-displaynames": "npm:7.1.2"
|
||||
"@formatjs/intl-durationformat": "npm:0.9.2"
|
||||
"@formatjs/intl-getcanonicallocales": "npm:3.1.2"
|
||||
"@formatjs/intl-listformat": "npm:8.1.2"
|
||||
"@formatjs/intl-locale": "npm:5.1.2"
|
||||
"@formatjs/intl-numberformat": "npm:9.1.2"
|
||||
"@formatjs/intl-pluralrules": "npm:6.1.2"
|
||||
"@formatjs/intl-relativetimeformat": "npm:12.1.2"
|
||||
"@formatjs/intl-datetimeformat": "npm:7.1.1"
|
||||
"@formatjs/intl-displaynames": "npm:7.1.1"
|
||||
"@formatjs/intl-durationformat": "npm:0.9.1"
|
||||
"@formatjs/intl-getcanonicallocales": "npm:3.1.1"
|
||||
"@formatjs/intl-listformat": "npm:8.1.1"
|
||||
"@formatjs/intl-locale": "npm:5.1.1"
|
||||
"@formatjs/intl-numberformat": "npm:9.1.1"
|
||||
"@formatjs/intl-pluralrules": "npm:6.1.1"
|
||||
"@formatjs/intl-relativetimeformat": "npm:12.1.1"
|
||||
"@fullcalendar/core": "npm:6.1.20"
|
||||
"@fullcalendar/daygrid": "npm:6.1.20"
|
||||
"@fullcalendar/interaction": "npm:6.1.20"
|
||||
@@ -9066,7 +9066,7 @@ __metadata:
|
||||
"@octokit/rest": "npm:22.0.1"
|
||||
"@replit/codemirror-indentation-markers": "npm:6.5.3"
|
||||
"@rsdoctor/rspack-plugin": "npm:1.4.0"
|
||||
"@rspack/core": "npm:1.7.1"
|
||||
"@rspack/core": "npm:1.7.0"
|
||||
"@rspack/dev-server": "npm:1.1.5"
|
||||
"@swc/helpers": "npm:0.5.18"
|
||||
"@thomasloven/round-slider": "npm:0.6.0"
|
||||
@@ -9136,7 +9136,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.9"
|
||||
intl-messageformat: "npm:11.0.8"
|
||||
js-yaml: "npm:4.1.1"
|
||||
jsdom: "npm:27.4.0"
|
||||
jszip: "npm:3.10.1"
|
||||
@@ -9144,9 +9144,9 @@ __metadata:
|
||||
leaflet-draw: "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||
leaflet.markercluster: "npm:1.5.3"
|
||||
lint-staged: "npm:16.2.7"
|
||||
lit: "npm:3.3.2"
|
||||
lit: "npm:3.3.1"
|
||||
lit-analyzer: "npm:2.0.3"
|
||||
lit-html: "npm:3.3.2"
|
||||
lit-html: "npm:3.3.1"
|
||||
lodash.merge: "npm:4.6.2"
|
||||
lodash.template: "npm:4.5.0"
|
||||
luxon: "npm:3.7.2"
|
||||
@@ -9173,7 +9173,7 @@ __metadata:
|
||||
tinykeys: "npm:3.0.0"
|
||||
ts-lit-plugin: "npm:2.0.2"
|
||||
typescript: "npm:5.9.3"
|
||||
typescript-eslint: "npm:8.52.0"
|
||||
typescript-eslint: "npm:8.51.0"
|
||||
ua-parser-js: "npm:2.0.7"
|
||||
vite-tsconfig-paths: "npm:6.0.3"
|
||||
vitest: "npm:4.0.16"
|
||||
@@ -9447,7 +9447,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ignore@npm:^7.0.3, ignore@npm:^7.0.5":
|
||||
"ignore@npm:^7.0.0, ignore@npm:^7.0.3":
|
||||
version: 7.0.5
|
||||
resolution: "ignore@npm:7.0.5"
|
||||
checksum: 10/f134b96a4de0af419196f52c529d5c6120c4456ff8a6b5a14ceaaa399f883e15d58d2ce651c9b69b9388491d4669dda47285d307e827de9304a53a1824801bc6
|
||||
@@ -9543,15 +9543,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"intl-messageformat@npm:11.0.9":
|
||||
version: 11.0.9
|
||||
resolution: "intl-messageformat@npm:11.0.9"
|
||||
"intl-messageformat@npm:11.0.8":
|
||||
version: 11.0.8
|
||||
resolution: "intl-messageformat@npm:11.0.8"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.8"
|
||||
"@formatjs/fast-memoize": "npm:3.0.3"
|
||||
"@formatjs/icu-messageformat-parser": "npm:3.3.0"
|
||||
"@formatjs/ecma402-abstract": "npm:3.0.7"
|
||||
"@formatjs/fast-memoize": "npm:3.0.2"
|
||||
"@formatjs/icu-messageformat-parser": "npm:3.2.1"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/36481eabd854d623ae9941476cad1dce720efa9c40dbb8cb036b11b1ffda6804689b0cf9da5d2421268cb1138a49f7a0b553fcf8fed03b5a37af283baf326bb4
|
||||
checksum: 10/e6b1317c683732c5c9677d897e049669b89569422f96edd8160d11ae252a5795ba62f27812b3ec2be57ca88e49ac336be6608642edbd2c2b36515f55b13385e9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -10521,12 +10521,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lit-html@npm:3.3.2":
|
||||
version: 3.3.2
|
||||
resolution: "lit-html@npm:3.3.2"
|
||||
"lit-html@npm:3.3.1":
|
||||
version: 3.3.1
|
||||
resolution: "lit-html@npm:3.3.1"
|
||||
dependencies:
|
||||
"@types/trusted-types": "npm:^2.0.2"
|
||||
checksum: 10/5e938739641e312673e722eb41d5ca48dfcf69277e665c72dadd6514f53fc3dc4d5172cf8ca6af67736df1bce267f6189b3ef6fcb4125386b2490cd10c266b8f
|
||||
checksum: 10/7b438ca58670313beac29e4bb3b56f4ac8150def10b9fa222ad04f1caf6189a194ced8bfed3296cf1c94003a1bd1960ef4c22b00bbeda15fda6b7be398b27e9f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -10541,14 +10541,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lit@npm:3.3.2":
|
||||
version: 3.3.2
|
||||
resolution: "lit@npm:3.3.2"
|
||||
"lit@npm:3.3.1":
|
||||
version: 3.3.1
|
||||
resolution: "lit@npm:3.3.1"
|
||||
dependencies:
|
||||
"@lit/reactive-element": "npm:^2.1.0"
|
||||
lit-element: "npm:^4.2.0"
|
||||
lit-html: "npm:^3.3.0"
|
||||
checksum: 10/0af8e06a0ef5bb6a84c3dd58b64b6b554baf4bc195d725951ae60bbd9999618364815dc4438bf949051e7f15497b71b2bb9f7514772b33c4526882bdfb805858
|
||||
checksum: 10/ec70ff33db610537fd7cfc608cc7728126ecfae2d5593aa94844c614d2f6840448f1b995a58aeba593b0bf0e8169af5988036c11d3c5b55313fe8e722417c17d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -10948,7 +10948,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:^9.0.5":
|
||||
"minimatch@npm:^9.0.4":
|
||||
version: 9.0.5
|
||||
resolution: "minimatch@npm:9.0.5"
|
||||
dependencies:
|
||||
@@ -12686,7 +12686,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.7.3":
|
||||
"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.6.0, semver@npm:^7.7.3":
|
||||
version: 7.7.3
|
||||
resolution: "semver@npm:7.7.3"
|
||||
bin:
|
||||
@@ -13873,12 +13873,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ts-api-utils@npm:^2.4.0":
|
||||
version: 2.4.0
|
||||
resolution: "ts-api-utils@npm:2.4.0"
|
||||
"ts-api-utils@npm:^2.2.0":
|
||||
version: 2.3.0
|
||||
resolution: "ts-api-utils@npm:2.3.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4"
|
||||
checksum: 10/d6b2b3b6caad8d2f4ddc0c3785d22bb1a6041773335a1c71d73a5d67d11d993763fe8e4faefc4a4d03bb42b26c6126bbcf2e34826baed1def5369d0ebad358fa
|
||||
checksum: 10/33472e0aa22222947512bcbdad37f078a6a4fb8f79e5ce271267f3520e7303ab204067cc7eaa508b2e4f91539b0bb9ecb9254f5f62397d27cf52dc5e73540a8f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -14041,18 +14041,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript-eslint@npm:8.52.0":
|
||||
version: 8.52.0
|
||||
resolution: "typescript-eslint@npm:8.52.0"
|
||||
"typescript-eslint@npm:8.51.0":
|
||||
version: 8.51.0
|
||||
resolution: "typescript-eslint@npm:8.51.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.52.0"
|
||||
"@typescript-eslint/parser": "npm:8.52.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.52.0"
|
||||
"@typescript-eslint/utils": "npm:8.52.0"
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.51.0"
|
||||
"@typescript-eslint/parser": "npm:8.51.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.51.0"
|
||||
"@typescript-eslint/utils": "npm:8.51.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/c12d55948c903549b779bc5a46aab85825be63679118c6fefeb8dcab21173e17665b054ab23cf6e57ac0549fb2f6edcaace03cde165d61a9f0dccadb03e2ab59
|
||||
checksum: 10/491dc8b6fb56f69c94515c31f6ff1db961d79c06e647f5db55647cca34dacedff4bab879a211559fc46e925a7a85ff60a1a68b7eac1e8a5db37828c57a65c718
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user