mirror of
https://github.com/home-assistant/frontend.git
synced 2026-06-03 15:02:01 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5acc65aa01 | |||
| 1cbc1878fc |
@@ -13,7 +13,7 @@ Our dialogs are based on the latest version of Material Design. Please note that
|
||||
|
||||
- Dialogs have a max width of 560px. Alert and confirmation dialogs have a fixed width of 320px. If you need more width, consider a dedicated page instead.
|
||||
- The close X-icon is on the top left, on all screen sizes. Except for alert and confirmation dialogs, they only have buttons and no X-icon. This is different compared to the Material guidelines.
|
||||
- Dialogs can't be closed with ESC or clicked outside of the dialog when there is a form that the user **has made changes to**. Instead it will animate "no" by a little shake.
|
||||
- Dialogs can't be closed with ESC or clicked outside of the dialog when there is a form that the user needs to fill out. Instead it will animate "no" by a little shake.
|
||||
- Extra icon buttons are on the top right, for example help, settings and expand dialog. More than 2 icon buttons, they will be in an overflow menu.
|
||||
- The submit button is grouped with a cancel button at the bottom right, on all screen sizes. Fullscreen mobile dialogs have them sticky at the bottom.
|
||||
- Keep the labels short, for example `Save`, `Delete`, `Enable`.
|
||||
|
||||
@@ -13,28 +13,6 @@ export interface NetworkInfo {
|
||||
supervisor_internet: boolean;
|
||||
}
|
||||
|
||||
interface SupervisorJob {
|
||||
name: string;
|
||||
reference: string | null;
|
||||
uuid: string;
|
||||
progress: number; // float, 0–100
|
||||
stage: string | null;
|
||||
done: boolean;
|
||||
errors: {
|
||||
type: string;
|
||||
message: string;
|
||||
stage: string | null;
|
||||
}[];
|
||||
created: string; // ISO datetime string
|
||||
extra: Record<string, unknown> | null;
|
||||
child_jobs: SupervisorJob[];
|
||||
}
|
||||
|
||||
export interface SupervisorJobInfo {
|
||||
ignore_conditions: string[];
|
||||
jobs: SupervisorJob[];
|
||||
}
|
||||
|
||||
export const ALTERNATIVE_DNS_SERVERS: {
|
||||
ipv4: string[];
|
||||
ipv6: string[];
|
||||
@@ -79,15 +57,6 @@ export async function getSupervisorNetworkInfo(): Promise<NetworkInfo> {
|
||||
return responseData?.data;
|
||||
}
|
||||
|
||||
export async function getSupervisorJobsInfo(): Promise<
|
||||
HassioResponse<SupervisorJobInfo>
|
||||
> {
|
||||
const responseData = await handleFetchPromise<
|
||||
HassioResponse<SupervisorJobInfo>
|
||||
>(fetch("/supervisor-api/jobs/info"));
|
||||
return responseData;
|
||||
}
|
||||
|
||||
export const setSupervisorNetworkDns = async (
|
||||
dnsServerIndex: number,
|
||||
primaryInterface: string
|
||||
|
||||
@@ -2,9 +2,9 @@ import { mdiOpenInNew } from "@mdi/js";
|
||||
import { css, html, nothing, type PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { extractSearchParam } from "../../src/common/url/search-params";
|
||||
import "../../src/components/animation/ha-fade-in";
|
||||
import "../../src/components/ha-alert";
|
||||
import "../../src/components/ha-button";
|
||||
import "../../src/components/animation/ha-fade-in";
|
||||
import "../../src/components/ha-spinner";
|
||||
import "../../src/components/ha-svg-icon";
|
||||
import "../../src/components/progress/ha-progress-bar";
|
||||
@@ -15,7 +15,6 @@ import { haStyle } from "../../src/resources/styles";
|
||||
import "./components/landing-page-logs";
|
||||
import "./components/landing-page-network";
|
||||
import {
|
||||
getSupervisorJobsInfo,
|
||||
getSupervisorNetworkInfo,
|
||||
pingSupervisor,
|
||||
type NetworkInfo,
|
||||
@@ -25,7 +24,6 @@ import { LandingPageBaseElement } from "./landing-page-base-element";
|
||||
export const ASSUME_CORE_START_SECONDS = 60;
|
||||
const SCHEDULE_CORE_CHECK_SECONDS = 1;
|
||||
const SCHEDULE_FETCH_NETWORK_INFO_SECONDS = 5;
|
||||
const SCHEDULE_FETCH_JOBS_INFO_SECONDS = 2;
|
||||
|
||||
@customElement("ha-landing-page")
|
||||
class HaLandingPage extends LandingPageBaseElement {
|
||||
@@ -41,8 +39,6 @@ class HaLandingPage extends LandingPageBaseElement {
|
||||
|
||||
@state() private _coreCheckActive = false;
|
||||
|
||||
@state() private _progress = -1;
|
||||
|
||||
private _mobileApp =
|
||||
extractSearchParam("redirect_uri") === "homeassistant://auth-callback";
|
||||
|
||||
@@ -64,14 +60,7 @@ class HaLandingPage extends LandingPageBaseElement {
|
||||
${!networkIssue && !this._supervisorError
|
||||
? html`
|
||||
<p>${this.localize("subheader")}</p>
|
||||
<ha-progress-bar
|
||||
.indeterminate=${this._progress <= 0}
|
||||
.value=${this._progress > 0 ? this._progress : undefined}
|
||||
.loading=${this._progress >= 0}
|
||||
>${this._progress > 0
|
||||
? `${Math.round(this._progress)}%`
|
||||
: nothing}</ha-progress-bar
|
||||
>
|
||||
<ha-progress-bar indeterminate></ha-progress-bar>
|
||||
`
|
||||
: nothing}
|
||||
${networkIssue || this._networkInfoError
|
||||
@@ -137,7 +126,6 @@ class HaLandingPage extends LandingPageBaseElement {
|
||||
import("../../src/components/ha-language-picker");
|
||||
|
||||
this._fetchSupervisorInfo(true);
|
||||
this._fetchSupervisorJobsInfo();
|
||||
}
|
||||
|
||||
private _scheduleFetchSupervisorInfo() {
|
||||
@@ -150,13 +138,6 @@ class HaLandingPage extends LandingPageBaseElement {
|
||||
);
|
||||
}
|
||||
|
||||
private _scheduleFetchSupervisorJobsInfo() {
|
||||
setTimeout(
|
||||
() => this._fetchSupervisorJobsInfo(),
|
||||
SCHEDULE_FETCH_JOBS_INFO_SECONDS * 1000
|
||||
);
|
||||
}
|
||||
|
||||
private _scheduleTurnOffCoreCheck() {
|
||||
setTimeout(() => {
|
||||
this._coreCheckActive = false;
|
||||
@@ -184,7 +165,7 @@ class HaLandingPage extends LandingPageBaseElement {
|
||||
// assume supervisor update if ping fails -> don't show an error
|
||||
if (!this._coreCheckActive && err.message !== "ping-failed") {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to fetch supervisor info", err);
|
||||
console.error(err);
|
||||
this._networkInfoError = true;
|
||||
}
|
||||
}
|
||||
@@ -194,33 +175,6 @@ class HaLandingPage extends LandingPageBaseElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchSupervisorJobsInfo() {
|
||||
try {
|
||||
const jobsInfo = await getSupervisorJobsInfo();
|
||||
const coreInstallJob =
|
||||
jobsInfo.result === "ok"
|
||||
? jobsInfo.data.jobs.find(
|
||||
(job) => job.name === "home_assistant_core_install"
|
||||
)
|
||||
: undefined;
|
||||
if (coreInstallJob) {
|
||||
this._progress = coreInstallJob.progress;
|
||||
} else {
|
||||
this._progress = -1;
|
||||
}
|
||||
} catch (err: any) {
|
||||
await this._checkCoreAvailability();
|
||||
|
||||
if (!this._coreCheckActive) {
|
||||
this._progress = -1;
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to fetch supervisor jobs info", err);
|
||||
}
|
||||
}
|
||||
|
||||
this._scheduleFetchSupervisorJobsInfo();
|
||||
}
|
||||
|
||||
private async _checkCoreAvailability() {
|
||||
try {
|
||||
const response = await fetch("/manifest.json");
|
||||
@@ -268,27 +222,21 @@ class HaLandingPage extends LandingPageBaseElement {
|
||||
flex-direction: column;
|
||||
gap: var(--ha-space-4);
|
||||
}
|
||||
ha-language-picker {
|
||||
min-width: 200px;
|
||||
}
|
||||
ha-alert p {
|
||||
text-align: unset;
|
||||
}
|
||||
.footer ha-svg-icon {
|
||||
--mdc-icon-size: var(--ha-space-5);
|
||||
}
|
||||
ha-language-picker {
|
||||
margin-inline-start: calc(-1 * var(--ha-space-4));
|
||||
}
|
||||
ha-button {
|
||||
margin-inline-end: calc(-1 * var(--ha-space-2));
|
||||
}
|
||||
ha-fade-in {
|
||||
min-height: calc(100vh - 64px - 88px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
ha-progress-bar {
|
||||
--ha-progress-bar-track-height: 20px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
+4
-4
@@ -70,8 +70,8 @@
|
||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||
"@swc/helpers": "0.5.23",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@tsparticles/engine": "4.1.1",
|
||||
"@tsparticles/preset-links": "4.1.1",
|
||||
"@tsparticles/engine": "4.1.0",
|
||||
"@tsparticles/preset-links": "4.1.0",
|
||||
"@vibrant/color": "4.0.4",
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||
@@ -88,7 +88,7 @@
|
||||
"dialog-polyfill": "0.5.6",
|
||||
"echarts": "6.1.0",
|
||||
"element-internals-polyfill": "3.0.2",
|
||||
"fuse.js": "7.4.0",
|
||||
"fuse.js": "7.3.0",
|
||||
"google-timezones-json": "1.2.0",
|
||||
"gulp-zopfli-green": "7.0.0",
|
||||
"hls.js": "1.6.16",
|
||||
@@ -180,7 +180,7 @@
|
||||
"jsdom": "29.1.1",
|
||||
"jszip": "3.10.1",
|
||||
"license-checker-rseidelsohn": "5.0.1",
|
||||
"lint-staged": "17.0.7",
|
||||
"lint-staged": "17.0.5",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.18.1",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../ha-tooltip";
|
||||
|
||||
export type LiveTestState = "pass" | "fail" | "invalid" | "unknown";
|
||||
|
||||
@@ -12,6 +13,7 @@ export type LiveTestState = "pass" | "fail" | "invalid" | "unknown";
|
||||
*
|
||||
* @attr {"pass"|"fail"|"invalid"|"unknown"} state - The current live-test state. Defaults to `unknown`.
|
||||
* @attr {string} label - Accessible label announced by assistive technology.
|
||||
* @attr {string} message - Optional tooltip body shown on hover/focus.
|
||||
*/
|
||||
@customElement("ha-automation-row-live-test")
|
||||
export class HaAutomationRowLiveTest extends LitElement {
|
||||
@@ -19,6 +21,8 @@ export class HaAutomationRowLiveTest extends LitElement {
|
||||
|
||||
@property() public label = "";
|
||||
|
||||
@property() public message?: string;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div
|
||||
@@ -27,38 +31,39 @@ export class HaAutomationRowLiveTest extends LitElement {
|
||||
tabindex="0"
|
||||
aria-label=${this.label}
|
||||
></div>
|
||||
${this.message
|
||||
? html`<ha-tooltip for="indicator">${this.message}</ha-tooltip>`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
inset-inline-end: -6px;
|
||||
display: inline-block;
|
||||
}
|
||||
#indicator {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: var(--ha-border-radius-circle);
|
||||
border: var(--ha-border-width-md) solid;
|
||||
border: 3px solid;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--card-background-color);
|
||||
box-shadow: 0 0 0 2px var(--card-background-color);
|
||||
transition: all var(--ha-animation-duration-normal) ease-in-out;
|
||||
}
|
||||
:host([state="pass"]) #indicator {
|
||||
background-color: var(--ha-color-green-60);
|
||||
border-color: var(--ha-color-green-60);
|
||||
background-color: var(--ha-color-fill-success-loud-resting);
|
||||
border-color: var(--ha-color-fill-success-loud-resting);
|
||||
}
|
||||
:host([state="fail"]) #indicator {
|
||||
border-color: var(--ha-color-orange-60);
|
||||
border-color: var(--ha-color-fill-warning-loud-resting);
|
||||
}
|
||||
:host([state="invalid"]) #indicator {
|
||||
border-color: var(--ha-color-red-60);
|
||||
border-color: var(--ha-color-fill-danger-loud-resting);
|
||||
}
|
||||
:host([state="unknown"]) #indicator {
|
||||
border-color: var(--ha-color-neutral-60);
|
||||
border-color: var(--ha-color-fill-neutral-loud-resting);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -165,8 +165,7 @@ export class HaAutomationRow extends LitElement {
|
||||
::slotted([slot="leading-icon"]) {
|
||||
color: var(--ha-color-on-neutral-quiet);
|
||||
}
|
||||
:host([building-block]) ::slotted([slot="leading-icon"].action-icon),
|
||||
:host([building-block]) ::slotted(#condition-icon) {
|
||||
:host([building-block]) ::slotted([slot="leading-icon"]) {
|
||||
--mdc-icon-size: var(--ha-space-5);
|
||||
color: var(--white-color);
|
||||
transform: rotate(-45deg);
|
||||
|
||||
@@ -101,22 +101,18 @@ export class HaSankeyChart extends LitElement {
|
||||
const value = this.valueFormatter
|
||||
? this.valueFormatter(data.value)
|
||||
: data.value;
|
||||
// Keep numbers and units left-to-right, even in RTL locales.
|
||||
const formattedValue = html`<div style="direction:ltr; display: inline;">
|
||||
${value}
|
||||
</div>`;
|
||||
if (data.id) {
|
||||
const node = this.data.nodes.find((n) => n.id === data.id);
|
||||
return html`<ha-chart-tooltip-marker
|
||||
.color=${String(params.color ?? "")}
|
||||
></ha-chart-tooltip-marker>
|
||||
${node?.label ?? data.id}<br />${formattedValue}`;
|
||||
${node?.label ?? data.id}<br />${value}`;
|
||||
}
|
||||
if (data.source && data.target) {
|
||||
const source = this.data.nodes.find((n) => n.id === data.source);
|
||||
const target = this.data.nodes.find((n) => n.id === data.target);
|
||||
return html`${source?.label ?? data.source} →
|
||||
${target?.label ?? data.target}<br />${formattedValue}`;
|
||||
${target?.label ?? data.target}<br />${value}`;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -3,12 +3,13 @@ import type { TemplateResult } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { mainWindow } from "../common/dom/get_main_window";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-icon-button";
|
||||
import { consumeLocalize } from "../common/decorators/consume-context-entry";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
|
||||
@customElement("ha-icon-button-arrow-prev")
|
||||
export class HaIconButtonArrowPrev extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property() public label?: string;
|
||||
@@ -24,15 +25,11 @@ export class HaIconButtonArrowPrev extends LitElement {
|
||||
@state() private _icon =
|
||||
mainWindow.document.dir === "rtl" ? mdiArrowRight : mdiArrowLeft;
|
||||
|
||||
@state()
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-icon-button
|
||||
.disabled=${this.disabled}
|
||||
.label=${this.label || this._localize("ui.common.back") || "Back"}
|
||||
.label=${this.label || this.hass?.localize("ui.common.back") || "Back"}
|
||||
.path=${this._icon}
|
||||
.href=${this.href}
|
||||
.target=${this.target}
|
||||
|
||||
@@ -485,17 +485,6 @@ export const migrateAutomationTrigger = (
|
||||
}
|
||||
delete trigger.platform;
|
||||
}
|
||||
|
||||
if ("options" in trigger) {
|
||||
if (trigger.options && "behavior" in trigger.options) {
|
||||
if (trigger.options.behavior === "any") {
|
||||
trigger.options.behavior = "each";
|
||||
} else if (trigger.options.behavior === "last") {
|
||||
trigger.options.behavior = "all";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return trigger;
|
||||
};
|
||||
|
||||
|
||||
+2
-16
@@ -167,6 +167,7 @@ export interface BatterySourceTypeEnergyPreference {
|
||||
stat_rate?: string; // always available if power_config is set
|
||||
power_config?: PowerConfig;
|
||||
stat_soc?: string;
|
||||
capacity?: number; // usable capacity in kWh, used to weight the combined SOC
|
||||
name?: string;
|
||||
}
|
||||
export interface GasSourceTypeEnergyPreference {
|
||||
@@ -222,12 +223,6 @@ export interface EnergyPreferences {
|
||||
device_consumption_water: DeviceConsumptionEnergyPreference[];
|
||||
}
|
||||
|
||||
export const EMPTY_PREFERENCES: EnergyPreferences = {
|
||||
energy_sources: [],
|
||||
device_consumption: [],
|
||||
device_consumption_water: [],
|
||||
};
|
||||
|
||||
export interface EnergyInfo {
|
||||
cost_sensors: Record<string, string>;
|
||||
solar_forecast_domains: string[];
|
||||
@@ -808,16 +803,7 @@ export const getEnergyDataCollection = (
|
||||
if (!collection.prefs) {
|
||||
// This will raise if not found.
|
||||
// Detect by checking `e.code === "not_found"
|
||||
try {
|
||||
collection.prefs = await getEnergyPreferences(hass);
|
||||
} catch (err: any) {
|
||||
if (err.code === "not_found") {
|
||||
return {
|
||||
prefs: EMPTY_PREFERENCES,
|
||||
} as EnergyData;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
collection.prefs = await getEnergyPreferences(hass);
|
||||
}
|
||||
|
||||
scheduleHourlyRefresh(collection);
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import type {
|
||||
HASSDomCurrentTargetEvent,
|
||||
HASSDomEvent,
|
||||
} from "../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type {
|
||||
LocalizeFunc,
|
||||
LocalizeKeys,
|
||||
} from "../../common/translations/localize";
|
||||
import "../../components/ha-icon";
|
||||
import "../../components/ha-svg-icon";
|
||||
import type { HaListItemButton } from "../../components/item/ha-list-item-button";
|
||||
import "../../components/item/ha-list-item-button";
|
||||
import "../../components/list/ha-list-base";
|
||||
import { consumeLocalize } from "../../common/decorators/consume-context-entry";
|
||||
|
||||
export interface AddToActionListItem {
|
||||
name?: string;
|
||||
nameKey?: LocalizeKeys;
|
||||
description?: string;
|
||||
descriptionKey?: LocalizeKeys;
|
||||
icon?: string;
|
||||
iconPath?: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface AddToActionListSection<
|
||||
Item extends AddToActionListItem = AddToActionListItem,
|
||||
> {
|
||||
title?: string;
|
||||
titleKey?: LocalizeKeys;
|
||||
actions: readonly Item[];
|
||||
empty?: string;
|
||||
emptyKey?: LocalizeKeys;
|
||||
}
|
||||
|
||||
export interface AddToActionListActionSelectedDetail<
|
||||
Item extends AddToActionListItem = AddToActionListItem,
|
||||
> {
|
||||
action: Item;
|
||||
}
|
||||
|
||||
export type AddToActionListActionSelectedEvent<
|
||||
Item extends AddToActionListItem = AddToActionListItem,
|
||||
> = HASSDomEvent<AddToActionListActionSelectedDetail<Item>>;
|
||||
|
||||
@customElement("ha-add-to-action-list")
|
||||
class HaAddToActionList extends LitElement {
|
||||
@state()
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
|
||||
@property({ attribute: false })
|
||||
public sections: readonly AddToActionListSection[] = [];
|
||||
|
||||
protected render(): TemplateResult | typeof nothing {
|
||||
if (!this.sections.length) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`${this.sections.map((section, sectionIndex) =>
|
||||
this._renderSection(section, sectionIndex)
|
||||
)}`;
|
||||
}
|
||||
|
||||
private _renderSection(
|
||||
section: AddToActionListSection,
|
||||
sectionIndex: number
|
||||
): TemplateResult | typeof nothing {
|
||||
if (!section.actions.length && !section.empty && !section.emptyKey) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<h3 class="section-header">
|
||||
${this._localizeValue(section.title, section.titleKey)}
|
||||
</h3>
|
||||
${section.actions.length
|
||||
? html`<ha-list-base>
|
||||
${section.actions.map((action, actionIndex) =>
|
||||
this._renderActionItem(action, sectionIndex, actionIndex)
|
||||
)}
|
||||
</ha-list-base>`
|
||||
: html`<h4 class="empty">
|
||||
${this._localizeValue(section.empty, section.emptyKey)}
|
||||
</h4>`}
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderActionItem(
|
||||
action: AddToActionListItem,
|
||||
sectionIndex: number,
|
||||
actionIndex: number
|
||||
): TemplateResult {
|
||||
return html`
|
||||
<ha-list-item-button
|
||||
.disabled=${action.enabled === false}
|
||||
data-section-index=${sectionIndex}
|
||||
data-action-index=${actionIndex}
|
||||
.headline=${this._localizeValue(action.name, action.nameKey)}
|
||||
.supportingText=${this._localizeValue(
|
||||
action.description,
|
||||
action.descriptionKey
|
||||
)}
|
||||
@click=${this._actionSelected}
|
||||
>
|
||||
${action.icon
|
||||
? html`<ha-icon
|
||||
class="start-icon"
|
||||
slot="start"
|
||||
.icon=${action.icon}
|
||||
></ha-icon>`
|
||||
: action.iconPath
|
||||
? html`<ha-svg-icon
|
||||
class="start-icon"
|
||||
slot="start"
|
||||
.path=${action.iconPath}
|
||||
></ha-svg-icon>`
|
||||
: nothing}
|
||||
<ha-svg-icon class="plus" slot="end" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-list-item-button>
|
||||
`;
|
||||
}
|
||||
|
||||
private _localizeValue(
|
||||
value?: string,
|
||||
localizeKey?: LocalizeKeys
|
||||
): string | undefined {
|
||||
return value || (localizeKey ? this._localize(localizeKey) : undefined);
|
||||
}
|
||||
|
||||
private _actionSelected(
|
||||
ev: HASSDomCurrentTargetEvent<HaListItemButton>
|
||||
): void {
|
||||
const action =
|
||||
this.sections[Number(ev.currentTarget.dataset.sectionIndex)]?.actions[
|
||||
Number(ev.currentTarget.dataset.actionIndex)
|
||||
];
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.enabled === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
fireEvent(this, "add-to-list-action-selected", {
|
||||
action,
|
||||
});
|
||||
}
|
||||
|
||||
static styles: CSSResultGroup = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
padding: var(--ha-space-2) var(--ha-space-6) var(--ha-space-1);
|
||||
margin: 0;
|
||||
font-size: var(--ha-font-size-m);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: var(--ha-space-2) var(--ha-space-6) var(--ha-space-1);
|
||||
margin: 0;
|
||||
font-size: var(--ha-font-size-m);
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
ha-list-item-button {
|
||||
--ha-row-item-padding-inline: var(--ha-space-5);
|
||||
}
|
||||
|
||||
ha-icon,
|
||||
ha-svg-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.start-icon {
|
||||
color: var(--ha-color-text-secondary);
|
||||
}
|
||||
|
||||
.plus {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
ha-list-item-button[disabled] .start-icon,
|
||||
ha-list-item-button[disabled] .plus {
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-add-to-action-list": HaAddToActionList;
|
||||
}
|
||||
|
||||
interface HASSDomEvents {
|
||||
"add-to-list-action-selected": AddToActionListActionSelectedDetail;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,26 @@
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import type { LocalizeKeys } from "../../common/translations/localize";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { createSearchParam } from "../../common/url/search-params";
|
||||
import type { EntityRegistryEntry } from "../../data/entity/entity_registry";
|
||||
import { SCENE_IGNORED_DOMAINS, type SceneEntities } from "../../data/scene";
|
||||
import type { SingleHassServiceTarget } from "../../data/target";
|
||||
import {
|
||||
ADD_AUTOMATION_ELEMENT_AREA_TARGET_PARAM,
|
||||
ADD_AUTOMATION_ELEMENT_DEVICE_TARGET_PARAM,
|
||||
ADD_AUTOMATION_ELEMENT_ENTITY_TARGET_PARAM,
|
||||
ADD_AUTOMATION_ELEMENT_QUERY_PARAM,
|
||||
ADD_AUTOMATION_ELEMENT_ENTITY_TARGET_PARAM,
|
||||
} from "../../panels/config/automation/show-add-automation-element-dialog";
|
||||
import type { HomeAssistant, TranslationDict } from "../../types";
|
||||
|
||||
/** Add to action keys are the keys of the translation dictionary for the add to action options. */
|
||||
type AddToActionOptions =
|
||||
TranslationDict["ui"]["dialogs"]["more_info_control"]["add_to"]["action_options"];
|
||||
|
||||
export type AddToActionKey = Extract<keyof AddToActionOptions, string>;
|
||||
|
||||
export type AddToAutomationScriptActionKey = Exclude<AddToActionKey, "scene">;
|
||||
|
||||
/** Fully-qualified localize key for an add to action option label. */
|
||||
type AddToActionOptionLabelKey = LocalizeKeys &
|
||||
`ui.dialogs.more_info_control.add_to.action_options.${AddToActionKey}`;
|
||||
/** Add to action keys are the keys of the translation dictionary for the add to actions. */
|
||||
export type AddToActionKey =
|
||||
TranslationDict["ui"]["dialogs"]["more_info_control"]["add_to"]["actions"] extends infer Actions
|
||||
? keyof Actions
|
||||
: never;
|
||||
|
||||
interface BaseEntityAddToAction {
|
||||
/** Whether the action is enabled and can be selected. */
|
||||
enabled: boolean;
|
||||
/** Translated label of the action option */
|
||||
name?: string;
|
||||
/** Fully-qualified localize key for the action option label */
|
||||
nameKey?: AddToActionOptionLabelKey;
|
||||
/** Translated name of the action */
|
||||
name: string;
|
||||
/** Optional translated description of the action */
|
||||
description?: string;
|
||||
/** MDI icon name (e.g., "mdi:car") */
|
||||
@@ -42,7 +31,7 @@ export interface DefaultEntityAddToAction extends BaseEntityAddToAction {
|
||||
/** Type of action handled in the frontend */
|
||||
type: "default";
|
||||
/** Stable key used to resolve the action handler */
|
||||
key: AddToAutomationScriptActionKey;
|
||||
key: AddToActionKey;
|
||||
}
|
||||
|
||||
export interface ExternalEntityAddToAction extends BaseEntityAddToAction {
|
||||
@@ -59,11 +48,11 @@ export type EntityAddToAction =
|
||||
export type EntityAddToActions = EntityAddToAction[];
|
||||
|
||||
interface ActionDefinition {
|
||||
translation_key: AddToAutomationScriptActionKey;
|
||||
translation_key: AddToActionKey;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
const DEFAULT_ACTION_DEFS: ActionDefinition[] = [
|
||||
export const DEFAULT_ACTION_DEFS: ActionDefinition[] = [
|
||||
{
|
||||
translation_key: "automation_trigger",
|
||||
icon: "mdi:robot-outline",
|
||||
@@ -82,49 +71,33 @@ const DEFAULT_ACTION_DEFS: ActionDefinition[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const getDefaultAddToActions = (): EntityAddToActions =>
|
||||
export const getDefaultAddToActions = (
|
||||
states: HomeAssistant["states"],
|
||||
localize: LocalizeFunc,
|
||||
formatEntityName: HomeAssistant["formatEntityName"],
|
||||
entityId: string
|
||||
): EntityAddToActions =>
|
||||
DEFAULT_ACTION_DEFS.map(
|
||||
(def: ActionDefinition): EntityAddToAction => ({
|
||||
type: "default",
|
||||
key: def.translation_key,
|
||||
enabled: true,
|
||||
nameKey: `ui.dialogs.more_info_control.add_to.action_options.${def.translation_key}`,
|
||||
name: localize(
|
||||
`ui.dialogs.more_info_control.add_to.actions.${def.translation_key}`,
|
||||
{
|
||||
target:
|
||||
states[entityId] !== undefined
|
||||
? formatEntityName(states[entityId], undefined)
|
||||
: entityId,
|
||||
}
|
||||
),
|
||||
icon: def.icon,
|
||||
})
|
||||
);
|
||||
|
||||
export const createAddToSceneEntities = (
|
||||
entityIds: string[]
|
||||
): SceneEntities => {
|
||||
const entities: SceneEntities = {};
|
||||
for (const entityId of entityIds) {
|
||||
entities[entityId] = "";
|
||||
}
|
||||
return entities;
|
||||
};
|
||||
|
||||
export const filterAddToSceneEntityIds = (
|
||||
entityIds: string[],
|
||||
entityRegistry: readonly EntityRegistryEntry[],
|
||||
states: HomeAssistant["states"]
|
||||
): string[] => {
|
||||
const entityIdSet = new Set(entityIds);
|
||||
|
||||
return entityRegistry
|
||||
.filter((entry) => entityIdSet.has(entry.entity_id))
|
||||
.filter(
|
||||
(entry) =>
|
||||
!entry.entity_category &&
|
||||
!entry.hidden_by &&
|
||||
!SCENE_IGNORED_DOMAINS.includes(computeDomain(entry.entity_id)) &&
|
||||
states[entry.entity_id]
|
||||
)
|
||||
.map((entry) => entry.entity_id);
|
||||
};
|
||||
|
||||
/** Handler for adding a target to an automation/script. */
|
||||
export function addToActionHandler(
|
||||
key: AddToAutomationScriptActionKey,
|
||||
key: AddToActionKey,
|
||||
target: SingleHassServiceTarget
|
||||
): Promise<boolean> {
|
||||
const searchParams: Record<string, string> = {};
|
||||
@@ -1,35 +1,26 @@
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../components/ha-alert";
|
||||
import "../../components/ha-icon";
|
||||
import "../../components/ha-spinner";
|
||||
import "../../components/item/ha-list-item-button";
|
||||
import "../../components/list/ha-list-base";
|
||||
import type { HaListItemButton } from "../../components/item/ha-list-item-button";
|
||||
import { showToast } from "../../util/toast";
|
||||
|
||||
import type { HASSDomCurrentTargetEvent } from "../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { configContext } from "../../data/context";
|
||||
import "../add-to/ha-add-to-action-list";
|
||||
import type {
|
||||
AddToActionListActionSelectedEvent,
|
||||
AddToActionListSection,
|
||||
} from "../add-to/ha-add-to-action-list";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import {
|
||||
type EntityAddToAction,
|
||||
type EntityAddToActions,
|
||||
addToActionHandler,
|
||||
getDefaultAddToActions,
|
||||
} from "../add-to/add-to";
|
||||
import { consumeLocalize } from "../../common/decorators/consume-context-entry";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
} from "./add-to";
|
||||
|
||||
@customElement("ha-more-info-add-to")
|
||||
export class HaMoreInfoAddTo extends LitElement {
|
||||
@state()
|
||||
@consume({ context: configContext, subscribe: true })
|
||||
private _config?: ContextType<typeof configContext>;
|
||||
|
||||
@state()
|
||||
@consumeLocalize()
|
||||
private _localize!: LocalizeFunc;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public entityId!: string;
|
||||
|
||||
@@ -40,13 +31,18 @@ export class HaMoreInfoAddTo extends LitElement {
|
||||
@state() private _loading = true;
|
||||
|
||||
private async _loadActions() {
|
||||
this._defaultActions = getDefaultAddToActions();
|
||||
this._defaultActions = getDefaultAddToActions(
|
||||
this.hass.states,
|
||||
this.hass.localize,
|
||||
this.hass.formatEntityName,
|
||||
this.entityId
|
||||
);
|
||||
this._externalActions = [];
|
||||
|
||||
if (this._config?.auth.external?.config.hasEntityAddTo) {
|
||||
if (this.hass.auth.external?.config.hasEntityAddTo) {
|
||||
try {
|
||||
const response =
|
||||
await this._config.auth.external.sendMessage<"entity/add_to/get_actions">(
|
||||
await this.hass.auth.external?.sendMessage<"entity/add_to/get_actions">(
|
||||
{
|
||||
type: "entity/add_to/get_actions",
|
||||
payload: { entity_id: this.entityId },
|
||||
@@ -70,9 +66,13 @@ export class HaMoreInfoAddTo extends LitElement {
|
||||
}
|
||||
|
||||
private async _actionSelected(
|
||||
ev: AddToActionListActionSelectedEvent<EntityAddToAction>
|
||||
ev: HASSDomCurrentTargetEvent<
|
||||
HaListItemButton & {
|
||||
action: EntityAddToAction;
|
||||
}
|
||||
>
|
||||
) {
|
||||
const { action } = ev.detail;
|
||||
const action = ev.currentTarget.action;
|
||||
if (!action.enabled) {
|
||||
return;
|
||||
}
|
||||
@@ -82,10 +82,7 @@ export class HaMoreInfoAddTo extends LitElement {
|
||||
if (!action.payload) {
|
||||
throw new Error("Missing external action payload");
|
||||
}
|
||||
if (!this._config?.auth.external) {
|
||||
throw new Error("Missing external app connection");
|
||||
}
|
||||
this._config.auth.external.fireMessage({
|
||||
this.hass.auth.external!.fireMessage({
|
||||
type: "entity/add_to",
|
||||
payload: {
|
||||
entity_id: this.entityId,
|
||||
@@ -95,7 +92,7 @@ export class HaMoreInfoAddTo extends LitElement {
|
||||
fireEvent(this, "add-to-action-selected");
|
||||
} catch (err: unknown) {
|
||||
showToast(this, {
|
||||
message: this._localize(
|
||||
message: this.hass.localize(
|
||||
"ui.dialogs.more_info_control.add_to.action_failed",
|
||||
{
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
@@ -113,6 +110,24 @@ export class HaMoreInfoAddTo extends LitElement {
|
||||
addToActionHandler(action.key, { entity_id: this.entityId });
|
||||
}
|
||||
|
||||
private _renderActionItems(actions: EntityAddToActions) {
|
||||
return actions.map(
|
||||
(action) => html`
|
||||
<ha-list-item-button
|
||||
.disabled=${!action.enabled}
|
||||
.action=${action}
|
||||
@click=${this._actionSelected}
|
||||
>
|
||||
<ha-icon slot="start" .icon=${action.icon}></ha-icon>
|
||||
<span slot="headline">${action.name}</span>
|
||||
${action.description
|
||||
? html`<span slot="supporting-text">${action.description}</span>`
|
||||
: nothing}
|
||||
</ha-list-item-button>
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
protected async firstUpdated() {
|
||||
await this._loadActions();
|
||||
this._loading = false;
|
||||
@@ -130,38 +145,29 @@ export class HaMoreInfoAddTo extends LitElement {
|
||||
if (!this._defaultActions.length && !this._externalActions.length) {
|
||||
return html`
|
||||
<ha-alert alert-type="info">
|
||||
${this._localize("ui.dialogs.more_info_control.add_to.no_actions")}
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.add_to.no_actions"
|
||||
)}
|
||||
</ha-alert>
|
||||
`;
|
||||
}
|
||||
|
||||
const automationActions = this._defaultActions.filter(
|
||||
(action) => action.type === "default" && action.key !== "script_action"
|
||||
);
|
||||
const scriptActions = this._defaultActions.filter(
|
||||
(action) => action.type === "default" && action.key === "script_action"
|
||||
);
|
||||
|
||||
const sections: AddToActionListSection<EntityAddToAction>[] = [
|
||||
{
|
||||
titleKey: "ui.dialogs.more_info_control.add_to.automations_heading",
|
||||
actions: automationActions,
|
||||
},
|
||||
{
|
||||
titleKey: "ui.dialogs.more_info_control.add_to.scripts_heading",
|
||||
actions: scriptActions,
|
||||
},
|
||||
{
|
||||
titleKey: "ui.dialogs.more_info_control.add_to.app_actions",
|
||||
actions: this._externalActions,
|
||||
},
|
||||
];
|
||||
|
||||
return html`
|
||||
<ha-add-to-action-list
|
||||
.sections=${sections}
|
||||
@add-to-list-action-selected=${this._actionSelected}
|
||||
></ha-add-to-action-list>
|
||||
<ha-list-base>
|
||||
${this._renderActionItems(this._defaultActions)}
|
||||
</ha-list-base>
|
||||
${this._externalActions.length
|
||||
? html`
|
||||
<h2 class="section-title">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.add_to.app_actions"
|
||||
)}
|
||||
</h2>
|
||||
<ha-list-base>
|
||||
${this._renderActionItems(this._externalActions)}
|
||||
</ha-list-base>
|
||||
`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -177,6 +183,20 @@ export class HaMoreInfoAddTo extends LitElement {
|
||||
align-items: center;
|
||||
padding: var(--ha-space-8);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
padding: 0 var(--ha-space-6);
|
||||
margin: var(--ha-space-4) 0 var(--ha-space-1);
|
||||
font-size: var(--ha-font-size-m);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
line-height: var(--ha-line-height-normal);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
ha-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import { cache } from "lit/directives/cache";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||
import type { HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
@@ -518,7 +517,7 @@ export class MoreInfoDialog extends SubscribeMixin(
|
||||
await favoritesHandler.copy(favoritesContext);
|
||||
}
|
||||
|
||||
private _goToAddEntityTo(ev: CustomEvent<RequestSelectedDetail>) {
|
||||
private _goToAddEntityTo(ev) {
|
||||
// Only check for request-selected events (from menu items), not regular clicks (from icon button)
|
||||
if (
|
||||
ev.type === "request-selected" &&
|
||||
@@ -591,19 +590,10 @@ export class MoreInfoDialog extends SubscribeMixin(
|
||||
(v): v is string => Boolean(v)
|
||||
);
|
||||
const defaultTitle = breadcrumb.pop() || entityId;
|
||||
const addToTitle = this.hass.localize(
|
||||
"ui.dialogs.more_info_control.add_to.title",
|
||||
{ target: defaultTitle }
|
||||
);
|
||||
const addToMenuItem = this.hass.localize(
|
||||
"ui.dialogs.more_info_control.add_to.item"
|
||||
);
|
||||
const title =
|
||||
this._currView === "details"
|
||||
? this.hass.localize("ui.dialogs.more_info_control.details")
|
||||
: this._currView === "add_to"
|
||||
? addToTitle
|
||||
: this._childView?.viewTitle || defaultTitle;
|
||||
: this._childView?.viewTitle || defaultTitle;
|
||||
|
||||
const favoritesContext =
|
||||
this._entry && stateObj
|
||||
@@ -721,7 +711,9 @@ export class MoreInfoDialog extends SubscribeMixin(
|
||||
slot="icon"
|
||||
.path=${mdiPlusBoxMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
${addToMenuItem}
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.add_to.title"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
@@ -822,7 +814,9 @@ export class MoreInfoDialog extends SubscribeMixin(
|
||||
? html`
|
||||
<ha-icon-button
|
||||
slot="headerActionItems"
|
||||
.label=${addToMenuItem}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.add_to.title"
|
||||
)}
|
||||
.path=${mdiPlusBoxMultipleOutline}
|
||||
@click=${this._goToAddEntityTo}
|
||||
></ha-icon-button>
|
||||
@@ -912,6 +906,7 @@ export class MoreInfoDialog extends SubscribeMixin(
|
||||
: this._currView === "add_to"
|
||||
? html`
|
||||
<ha-more-info-add-to
|
||||
.hass=${this.hass}
|
||||
.entityId=${entityId}
|
||||
@add-to-action-selected=${this._goBack}
|
||||
></ha-more-info-add-to>
|
||||
|
||||
@@ -33,6 +33,7 @@ class HassErrorScreen extends LitElement {
|
||||
`
|
||||
: html`
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
@click=${this._handleBack}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`}
|
||||
|
||||
@@ -35,6 +35,7 @@ class HassLoadingScreen extends LitElement {
|
||||
`
|
||||
: html`
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
@click=${this._handleBack}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`}
|
||||
|
||||
@@ -43,10 +43,12 @@ class HassSubpage extends LitElement {
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
href=${this.backPath}
|
||||
.hass=${this.hass}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`
|
||||
: html`
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
@click=${this._backTapped}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`}
|
||||
|
||||
@@ -391,6 +391,7 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.localizeFunc=${this.localizeFunc}
|
||||
.narrow=${this.narrow}
|
||||
.isWide=${this.isWide}
|
||||
.backPath=${this.backPath}
|
||||
.backCallback=${this.backCallback}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { consume } from "@lit/context";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import {
|
||||
@@ -19,7 +18,6 @@ import "../components/ha-icon-button-arrow-prev";
|
||||
import "../components/ha-menu-button";
|
||||
import "../components/ha-svg-icon";
|
||||
import "../components/ha-tab";
|
||||
import { narrowViewportContext } from "../data/context";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../types";
|
||||
|
||||
@@ -61,9 +59,7 @@ export class HassTabsSubpage extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public tabs!: PageNavigation[];
|
||||
|
||||
@state()
|
||||
@consume({ context: narrowViewportContext, subscribe: true })
|
||||
private _narrow = false;
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true, attribute: "is-wide" })
|
||||
public isWide = false;
|
||||
@@ -120,7 +116,7 @@ export class HassTabsSubpage extends LitElement {
|
||||
<a href=${page.path} @click=${this._tabClicked}>
|
||||
<ha-tab
|
||||
.active=${page.path === activeTab?.path}
|
||||
.narrow=${this._narrow}
|
||||
.narrow=${this.narrow}
|
||||
.name=${page.translationKey
|
||||
? localizeFunc(page.translationKey)
|
||||
: page.name}
|
||||
@@ -139,8 +135,6 @@ export class HassTabsSubpage extends LitElement {
|
||||
);
|
||||
|
||||
public willUpdate(changedProperties: PropertyValues<this>) {
|
||||
this.toggleAttribute("narrow", this._narrow);
|
||||
|
||||
if (changedProperties.has("route")) {
|
||||
const currentPath = `${this.route.prefix}${this.route.path}`;
|
||||
this._activeTab = this.tabs.find((tab) =>
|
||||
@@ -157,37 +151,39 @@ export class HassTabsSubpage extends LitElement {
|
||||
this.hass.config.components,
|
||||
this.hass.language,
|
||||
this.hass.userData,
|
||||
this._narrow,
|
||||
this.narrow,
|
||||
this.localizeFunc || this.hass.localize
|
||||
);
|
||||
return html`
|
||||
<div class="toolbar ${classMap({ narrow: this._narrow })}">
|
||||
<div class="toolbar ${classMap({ narrow: this.narrow })}">
|
||||
<slot name="toolbar">
|
||||
<div class="toolbar-content">
|
||||
${this.mainPage || (!this.backPath && history.state?.root)
|
||||
? html`
|
||||
<ha-menu-button
|
||||
.hass=${this.hass}
|
||||
.narrow=${this._narrow}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
`
|
||||
: this.backPath
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
.href=${this.backPath}
|
||||
.hass=${this.hass}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`
|
||||
: html`
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
@click=${this._backTapped}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`}
|
||||
${this._narrow || !this.showTabs
|
||||
${this.narrow || !this.showTabs
|
||||
? html`<div class="main-title">
|
||||
<slot name="header">${!this.showTabs ? tabs[0] : ""}</slot>
|
||||
</div>`
|
||||
: ""}
|
||||
${this.showTabs && !this._narrow
|
||||
${this.showTabs && !this.narrow
|
||||
? html`<div id="tabbar">${tabs}</div>`
|
||||
: ""}
|
||||
<div id="toolbar-icon">
|
||||
@@ -195,7 +191,7 @@ export class HassTabsSubpage extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
</slot>
|
||||
${this.showTabs && this._narrow
|
||||
${this.showTabs && this.narrow
|
||||
? html`<div id="tabbar" class="bottom-bar">${tabs}</div>`
|
||||
: ""}
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,39 @@
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-dialog";
|
||||
import { DialogMixin } from "../../dialogs/dialog-mixin";
|
||||
import type { AppDialogParams } from "./show-app-dialog";
|
||||
|
||||
@customElement("app-dialog")
|
||||
class DialogApp extends DialogMixin<AppDialogParams>(LitElement) {
|
||||
class DialogApp extends LitElement {
|
||||
@property({ attribute: false }) public localize?: LocalizeFunc;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
public async showDialog(params: { localize: LocalizeFunc }): Promise<void> {
|
||||
this.localize = params.localize;
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this.localize = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.params?.localize) {
|
||||
if (!this.localize) {
|
||||
return nothing;
|
||||
}
|
||||
return html`<ha-dialog
|
||||
open
|
||||
header-title=${this.params.localize(
|
||||
.open=${this._open}
|
||||
header-title=${this.localize(
|
||||
"ui.panel.page-onboarding.welcome.download_app"
|
||||
) || "Click here to download the app"}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<div>
|
||||
<div class="app-qr">
|
||||
@@ -26,17 +45,13 @@ class DialogApp extends DialogMixin<AppDialogParams>(LitElement) {
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/static/images/appstore.svg"
|
||||
alt=${this.params.localize(
|
||||
"ui.panel.page-onboarding.welcome.appstore"
|
||||
)}
|
||||
alt=${this.localize("ui.panel.page-onboarding.welcome.appstore")}
|
||||
class="icon"
|
||||
/>
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/static/images/qr-appstore.svg"
|
||||
alt=${this.params.localize(
|
||||
"ui.panel.page-onboarding.welcome.appstore"
|
||||
)}
|
||||
alt=${this.localize("ui.panel.page-onboarding.welcome.appstore")}
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
@@ -47,17 +62,13 @@ class DialogApp extends DialogMixin<AppDialogParams>(LitElement) {
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/static/images/playstore.svg"
|
||||
alt=${this.params.localize(
|
||||
"ui.panel.page-onboarding.welcome.playstore"
|
||||
)}
|
||||
alt=${this.localize("ui.panel.page-onboarding.welcome.playstore")}
|
||||
class="icon"
|
||||
/>
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/static/images/qr-playstore.svg"
|
||||
alt=${this.params.localize(
|
||||
"ui.panel.page-onboarding.welcome.playstore"
|
||||
)}
|
||||
alt=${this.localize("ui.panel.page-onboarding.welcome.playstore")}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,87 +1,103 @@
|
||||
import { mdiAccountGroup, mdiOpenInNew } from "@mdi/js";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-svg-icon";
|
||||
import "../../components/item/ha-list-item-button";
|
||||
import "../../components/list/ha-list-nav";
|
||||
import { DialogMixin } from "../../dialogs/dialog-mixin";
|
||||
import type { CommunityDialogParams } from "./show-community-dialog";
|
||||
import "../../components/ha-list";
|
||||
import "../../components/ha-list-item";
|
||||
|
||||
@customElement("community-dialog")
|
||||
class DialogCommunity extends DialogMixin<CommunityDialogParams>(LitElement) {
|
||||
class DialogCommunity extends LitElement {
|
||||
@property({ attribute: false }) public localize?: LocalizeFunc;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
public async showDialog(params): Promise<void> {
|
||||
this.localize = params.localize;
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this.localize = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.params?.localize) {
|
||||
if (!this.localize) {
|
||||
return nothing;
|
||||
}
|
||||
return html`<ha-dialog
|
||||
open
|
||||
header-title=${this.params.localize(
|
||||
.open=${this._open}
|
||||
header-title=${this.localize(
|
||||
"ui.panel.page-onboarding.welcome.community"
|
||||
)}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<ha-list-nav>
|
||||
<ha-list-item-button
|
||||
<ha-list>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://community.home-assistant.io/"
|
||||
>
|
||||
<img
|
||||
src="/static/icons/favicon-192x192.png"
|
||||
slot="start"
|
||||
alt="Home Assistant Logo"
|
||||
/>
|
||||
<span slot="headline">
|
||||
${this.params.localize("ui.panel.page-onboarding.welcome.forums")}
|
||||
</span>
|
||||
<ha-svg-icon slot="end" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<img
|
||||
src="/static/icons/favicon-192x192.png"
|
||||
slot="graphic"
|
||||
alt="Home Assistant Logo"
|
||||
/>
|
||||
${this.localize("ui.panel.page-onboarding.welcome.forums")}
|
||||
<ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://newsletter.openhomefoundation.org/"
|
||||
>
|
||||
<img
|
||||
src="/static/icons/logo_ohf.svg"
|
||||
slot="start"
|
||||
alt="Open Home Foundation Logo"
|
||||
/>
|
||||
<span slot="headline">
|
||||
${this.params.localize(
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<img
|
||||
src="/static/icons/logo_ohf.svg"
|
||||
slot="graphic"
|
||||
alt="Open Home Foundation Logo"
|
||||
/>
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.welcome.open_home_newsletter"
|
||||
)}
|
||||
</span>
|
||||
<ha-svg-icon slot="end" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
<ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://www.home-assistant.io/join-chat"
|
||||
>
|
||||
<img
|
||||
src="/static/images/logo_discord.png"
|
||||
slot="start"
|
||||
alt="Discord Logo"
|
||||
/>
|
||||
<span slot="headline">
|
||||
${this.params.localize("ui.panel.page-onboarding.welcome.discord")}
|
||||
</span>
|
||||
<ha-svg-icon slot="end" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<img
|
||||
src="/static/images/logo_discord.png"
|
||||
slot="graphic"
|
||||
alt="Discord Logo"
|
||||
/>
|
||||
${this.localize("ui.panel.page-onboarding.welcome.discord")}
|
||||
<ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://fosstodon.org/@homeassistant"
|
||||
>
|
||||
<ha-svg-icon .path=${mdiAccountGroup} slot="start"></ha-svg-icon>
|
||||
<span slot="headline">
|
||||
${this.params.localize(
|
||||
"ui.panel.page-onboarding.welcome.social_media"
|
||||
)}
|
||||
</span>
|
||||
<ha-svg-icon slot="end" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</ha-list-item-button>
|
||||
</ha-list-nav>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon .path=${mdiAccountGroup} slot="graphic"></ha-svg-icon>
|
||||
${this.localize("ui.panel.page-onboarding.welcome.social_media")}
|
||||
<ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
</ha-list>
|
||||
</ha-dialog>`;
|
||||
}
|
||||
|
||||
@@ -89,12 +105,12 @@ class DialogCommunity extends DialogMixin<CommunityDialogParams>(LitElement) {
|
||||
ha-dialog {
|
||||
--dialog-content-padding: 0;
|
||||
}
|
||||
img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
ha-list-item {
|
||||
height: 56px;
|
||||
--mdc-list-item-meta-size: 20px;
|
||||
}
|
||||
ha-svg-icon {
|
||||
color: var(--ha-color-text-secondary);
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -3,18 +3,13 @@ import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
|
||||
export const loadAppDialog = () => import("./app-dialog");
|
||||
|
||||
export interface AppDialogParams {
|
||||
localize: LocalizeFunc;
|
||||
}
|
||||
|
||||
export const showAppDialog = (
|
||||
element: HTMLElement,
|
||||
params: AppDialogParams
|
||||
params: { localize: LocalizeFunc }
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "app-dialog",
|
||||
dialogImport: loadAppDialog,
|
||||
dialogParams: params,
|
||||
addHistory: false,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,18 +3,13 @@ import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
|
||||
export const loadCommunityDialog = () => import("./community-dialog");
|
||||
|
||||
export interface CommunityDialogParams {
|
||||
localize: LocalizeFunc;
|
||||
}
|
||||
|
||||
export const showCommunityDialog = (
|
||||
element: HTMLElement,
|
||||
params: CommunityDialogParams
|
||||
params: { localize: LocalizeFunc }
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "community-dialog",
|
||||
dialogImport: loadCommunityDialog,
|
||||
dialogParams: params,
|
||||
addHistory: false,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import "@home-assistant/webawesome/dist/components/tag/tag";
|
||||
import { mdiCheckCircle, mdiHelpCircleOutline } from "@mdi/js";
|
||||
import { mdiHelpCircleOutline } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
@@ -25,9 +25,7 @@ class SupervisorAppsCardContent extends LitElement {
|
||||
|
||||
@property() public stage: AddonStage = "stable";
|
||||
|
||||
@property() public state?: AddonState;
|
||||
|
||||
@property({ type: Boolean }) public installed = false;
|
||||
@property() public state: AddonState = null;
|
||||
|
||||
@property() public description?: string;
|
||||
|
||||
@@ -79,23 +77,13 @@ class SupervisorAppsCardContent extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${this.tags?.length || this.state !== undefined || this.installed
|
||||
${this.tags?.length || this.state
|
||||
? html`
|
||||
<div class="footer">
|
||||
${this.state !== undefined
|
||||
? html`<supervisor-apps-state
|
||||
.state=${this.state || "unknown"}
|
||||
></supervisor-apps-state>`
|
||||
: this.installed
|
||||
? html`<div class="installed">
|
||||
<ha-svg-icon .path=${mdiCheckCircle}></ha-svg-icon>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.apps.state.installed"
|
||||
)}</span
|
||||
>
|
||||
</div>`
|
||||
: html`<span></span>`}
|
||||
<supervisor-apps-state
|
||||
.state=${this.state || "unknown"}
|
||||
></supervisor-apps-state>
|
||||
|
||||
${this.tags?.length
|
||||
? html`<div class="tags">
|
||||
${this.tags.map(
|
||||
@@ -171,17 +159,6 @@ class SupervisorAppsCardContent extends LitElement {
|
||||
display: flex;
|
||||
gap: var(--ha-space-2);
|
||||
}
|
||||
.installed {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--ha-space-2);
|
||||
color: var(--ha-color-text-secondary);
|
||||
font-size: var(--ha-font-size-m);
|
||||
}
|
||||
.installed ha-svg-icon {
|
||||
--mdc-icon-size: 16px;
|
||||
color: var(--ha-color-on-success-normal);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -135,6 +135,7 @@ class HaConfigAppDashboard extends LitElement {
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${route}
|
||||
.tabs=${addonTabs}
|
||||
back-path=${this._fromStore ? "/config/apps/available" : "/config/apps"}
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import {
|
||||
mdiAlertDecagramOutline,
|
||||
mdiArrowUpBoldCircle,
|
||||
mdiArrowUpBoldCircleOutline,
|
||||
mdiFlask,
|
||||
mdiPuzzle,
|
||||
} from "@mdi/js";
|
||||
import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||
@@ -17,7 +10,6 @@ import type { HassioAddonRepository } from "../../../data/hassio/addon";
|
||||
import type { StoreAddon } from "../../../data/supervisor/store";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "./components/supervisor-apps-card-content";
|
||||
import type { AppTag } from "./components/supervisor-apps-card-content";
|
||||
import { filterAndSort } from "./components/supervisor-apps-filter";
|
||||
import { supervisorAppsStyle } from "./resources/supervisor-apps-style";
|
||||
|
||||
@@ -62,29 +54,21 @@ export class SupervisorAppsRepositoryEl extends LitElement {
|
||||
<div class="content">
|
||||
<h1>${repo.name}</h1>
|
||||
<div class="card-group">
|
||||
${addons.map((addon) => {
|
||||
const tags = this._getAppTags(addon);
|
||||
return html`
|
||||
${addons.map(
|
||||
(addon) => html`
|
||||
<ha-card
|
||||
outlined
|
||||
.addon=${addon}
|
||||
class=${addon.available ? "" : "not_available"}
|
||||
@click=${this._addonTapped}
|
||||
>
|
||||
<div
|
||||
class=${classMap({
|
||||
"card-content": true,
|
||||
"has-footer": tags.length > 0 || addon.installed,
|
||||
})}
|
||||
>
|
||||
<div class="card-content">
|
||||
<supervisor-apps-card-content
|
||||
.hass=${this.hass}
|
||||
.title=${addon.name}
|
||||
.stage=${addon.stage}
|
||||
.description=${addon.description}
|
||||
.available=${addon.available}
|
||||
.installed=${addon.installed}
|
||||
.tags=${tags}
|
||||
.icon=${addon.installed && addon.update_available
|
||||
? mdiArrowUpBoldCircle
|
||||
: mdiPuzzle}
|
||||
@@ -124,8 +108,8 @@ export class SupervisorAppsRepositoryEl extends LitElement {
|
||||
></supervisor-apps-card-content>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
})}
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -135,32 +119,6 @@ export class SupervisorAppsRepositoryEl extends LitElement {
|
||||
navigate(`/config/app/${ev.currentTarget.addon.slug}/info?store=true`);
|
||||
}
|
||||
|
||||
private _getAppTags(addon: StoreAddon): AppTag[] {
|
||||
const labels: AppTag[] = [];
|
||||
|
||||
if (addon.installed && addon.update_available) {
|
||||
labels.push({
|
||||
label: this.hass.localize(
|
||||
`ui.panel.config.apps.state.update_available`
|
||||
),
|
||||
variant: "brand",
|
||||
iconPath: mdiArrowUpBoldCircleOutline,
|
||||
});
|
||||
}
|
||||
if (addon.stage !== "stable") {
|
||||
labels.push({
|
||||
label: this.hass.localize(
|
||||
`ui.panel.config.apps.dashboard.capability.stages.${addon.stage}`
|
||||
),
|
||||
variant: addon.stage === "experimental" ? "warning" : "danger",
|
||||
iconPath:
|
||||
addon.stage === "experimental" ? mdiFlask : mdiAlertDecagramOutline,
|
||||
});
|
||||
}
|
||||
|
||||
return labels;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
supervisorAppsStyle,
|
||||
@@ -169,9 +127,6 @@ export class SupervisorAppsRepositoryEl extends LitElement {
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
.card-content.has-footer {
|
||||
padding: var(--ha-space-4) var(--ha-space-4) var(--ha-space-2);
|
||||
}
|
||||
.not_available {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
@@ -12,32 +12,22 @@ import {
|
||||
import { computeAreaName } from "../../../common/entity/compute_area_name";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-adaptive-dialog";
|
||||
import "../../../components/ha-list";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import {
|
||||
areasContext,
|
||||
internationalizationContext,
|
||||
} from "../../../data/context";
|
||||
import type { SceneEntities } from "../../../data/scene";
|
||||
import { showSceneEditor } from "../../../data/scene";
|
||||
import "../../../dialogs/add-to/ha-add-to-action-list";
|
||||
import type {
|
||||
AddToActionListActionSelectedEvent,
|
||||
AddToActionListItem,
|
||||
AddToActionListSection,
|
||||
} from "../../../dialogs/add-to/ha-add-to-action-list";
|
||||
import {
|
||||
addToActionHandler,
|
||||
createAddToSceneEntities,
|
||||
type AddToAutomationScriptActionKey,
|
||||
} from "../../../dialogs/add-to/add-to";
|
||||
type AddToActionKey,
|
||||
} from "../../../dialogs/more-info/add-to";
|
||||
import { haStyle, haStyleDialog } from "../../../resources/styles";
|
||||
import type { AreaAddToDialogParams } from "./show-dialog-area-add-to";
|
||||
|
||||
type AreaAddToAction =
|
||||
| (AddToActionListItem & {
|
||||
type: "automation";
|
||||
key: AddToAutomationScriptActionKey;
|
||||
})
|
||||
| (AddToActionListItem & { type: "scene" });
|
||||
|
||||
@customElement("dialog-area-add-to")
|
||||
class DialogAreaAddTo extends LitElement {
|
||||
@state()
|
||||
@@ -75,12 +65,7 @@ class DialogAreaAddTo extends LitElement {
|
||||
<ha-adaptive-dialog
|
||||
.open=${this._open}
|
||||
header-title=${this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.title",
|
||||
{
|
||||
target:
|
||||
computeAreaName(this._areas[this._params.areaId]) ||
|
||||
this._params.areaId,
|
||||
}
|
||||
"ui.dialogs.more_info_control.add_to.title"
|
||||
)}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
@@ -94,96 +79,108 @@ class DialogAreaAddTo extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const sections: AddToActionListSection<AreaAddToAction>[] = [
|
||||
{
|
||||
title: this._i18n.localize(
|
||||
"ui.panel.config.devices.automation.automations_heading"
|
||||
),
|
||||
actions: [
|
||||
{
|
||||
type: "automation",
|
||||
key: "automation_trigger",
|
||||
iconPath: mdiRobotOutline,
|
||||
name: this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.action_options.automation_trigger"
|
||||
),
|
||||
},
|
||||
{
|
||||
type: "automation",
|
||||
key: "automation_condition",
|
||||
iconPath: mdiPlaylistCheck,
|
||||
name: this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.action_options.automation_condition"
|
||||
),
|
||||
},
|
||||
{
|
||||
type: "automation",
|
||||
key: "automation_action",
|
||||
iconPath: mdiPlayCircleOutline,
|
||||
name: this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.action_options.automation_action"
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: this._i18n.localize(
|
||||
"ui.panel.config.devices.script.scripts_heading"
|
||||
),
|
||||
actions: [
|
||||
{
|
||||
type: "automation",
|
||||
key: "script_action",
|
||||
iconPath: mdiScriptTextOutline,
|
||||
name: this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.action_options.script_action"
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
if (this._params.canCreateScene && this._params.entityIds.length) {
|
||||
sections.push({
|
||||
title: this._i18n.localize(
|
||||
"ui.panel.config.devices.scene.scenes_heading"
|
||||
),
|
||||
actions: [
|
||||
{
|
||||
type: "scene",
|
||||
iconPath: mdiPalette,
|
||||
name: this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.action_options.scene"
|
||||
),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
const area = this._areas[this._params.areaId];
|
||||
const areaName = computeAreaName(area) || this._params.areaId;
|
||||
|
||||
return html`
|
||||
<ha-add-to-action-list
|
||||
.sections=${sections}
|
||||
@add-to-list-action-selected=${this._handleActionSelected}
|
||||
></ha-add-to-action-list>
|
||||
<h3 class="section-header">
|
||||
${this._i18n.localize(
|
||||
"ui.panel.config.devices.automation.automations_heading"
|
||||
)}
|
||||
</h3>
|
||||
<ha-list>
|
||||
${this._renderActionItem(
|
||||
"automation_trigger",
|
||||
mdiRobotOutline,
|
||||
"ui.dialogs.more_info_control.add_to.actions.automation_trigger",
|
||||
areaName
|
||||
)}
|
||||
${this._renderActionItem(
|
||||
"automation_condition",
|
||||
mdiPlaylistCheck,
|
||||
"ui.dialogs.more_info_control.add_to.actions.automation_condition",
|
||||
areaName
|
||||
)}
|
||||
${this._renderActionItem(
|
||||
"automation_action",
|
||||
mdiPlayCircleOutline,
|
||||
"ui.dialogs.more_info_control.add_to.actions.automation_action",
|
||||
areaName
|
||||
)}
|
||||
</ha-list>
|
||||
<h3 class="section-header">
|
||||
${this._i18n.localize("ui.panel.config.devices.script.scripts_heading")}
|
||||
</h3>
|
||||
<ha-list>
|
||||
${this._renderActionItem(
|
||||
"script_action",
|
||||
mdiScriptTextOutline,
|
||||
"ui.dialogs.more_info_control.add_to.actions.script_action",
|
||||
areaName
|
||||
)}
|
||||
</ha-list>
|
||||
${this._renderSceneSection(areaName)}
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleActionSelected(
|
||||
ev: AddToActionListActionSelectedEvent<AreaAddToAction>
|
||||
private _renderSceneSection(areaName: string) {
|
||||
if (!this._params?.entityIds.length) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<h3 class="section-header">
|
||||
${this._i18n.localize("ui.panel.config.devices.scene.scenes_heading")}
|
||||
</h3>
|
||||
<ha-list>
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._handleCreateScene}
|
||||
data-dialog="close"
|
||||
>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiPalette}></ha-svg-icon>
|
||||
${this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.actions.scene",
|
||||
{ target: areaName }
|
||||
)}
|
||||
</ha-list-item>
|
||||
</ha-list>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderActionItem(
|
||||
key: AddToActionKey,
|
||||
path: string,
|
||||
translationKey:
|
||||
| "ui.dialogs.more_info_control.add_to.actions.automation_trigger"
|
||||
| "ui.dialogs.more_info_control.add_to.actions.automation_condition"
|
||||
| "ui.dialogs.more_info_control.add_to.actions.automation_action"
|
||||
| "ui.dialogs.more_info_control.add_to.actions.script_action",
|
||||
areaName: string
|
||||
) {
|
||||
return html`
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
data-type=${key}
|
||||
@click=${this._handleAction}
|
||||
data-dialog="close"
|
||||
>
|
||||
<ha-svg-icon slot="graphic" .path=${path}></ha-svg-icon>
|
||||
${this._i18n.localize(translationKey, { target: areaName })}
|
||||
</ha-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleAction(ev: Event) {
|
||||
if (!this._params) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { action } = ev.detail;
|
||||
|
||||
if (action.type === "scene") {
|
||||
this._handleCreateScene();
|
||||
return;
|
||||
}
|
||||
const key = (ev.currentTarget as HTMLElement).dataset
|
||||
.type as AddToActionKey;
|
||||
|
||||
this.closeDialog();
|
||||
addToActionHandler(action.key, { area_id: this._params.areaId });
|
||||
addToActionHandler(key, { area_id: this._params.areaId });
|
||||
}
|
||||
|
||||
private _handleCreateScene() {
|
||||
@@ -191,11 +188,13 @@ class DialogAreaAddTo extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const entities: SceneEntities = {};
|
||||
for (const entityId of this._params.entityIds) {
|
||||
entities[entityId] = "";
|
||||
}
|
||||
|
||||
this.closeDialog();
|
||||
showSceneEditor(
|
||||
{ entities: createAddToSceneEntities(this._params.entityIds) },
|
||||
this._params.areaId
|
||||
);
|
||||
showSceneEditor({ entities }, this._params.areaId);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
@@ -206,6 +205,14 @@ class DialogAreaAddTo extends LitElement {
|
||||
ha-adaptive-dialog {
|
||||
--dialog-content-padding: 0;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
padding: var(--ha-space-2) var(--ha-space-4) 0;
|
||||
margin: 0;
|
||||
font-size: var(--ha-font-size-m);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -60,7 +60,6 @@ import type { SceneEntity } from "../../../data/scene";
|
||||
import type { ScriptEntity } from "../../../data/script";
|
||||
import type { RelatedResult } from "../../../data/search";
|
||||
import { findRelated } from "../../../data/search";
|
||||
import { filterAddToSceneEntityIds } from "../../../dialogs/add-to/add-to";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
|
||||
import "../../../layouts/hass-error-screen";
|
||||
@@ -440,7 +439,7 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
.path=${mdiPlus}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.add_to.item"
|
||||
"ui.dialogs.more_info_control.add_to.title"
|
||||
)}
|
||||
</ha-button>`
|
||||
: nothing}
|
||||
@@ -782,17 +781,9 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
if (!area) {
|
||||
return;
|
||||
}
|
||||
const sceneEntityIds = filterAddToSceneEntityIds(
|
||||
this._areaEntityIds,
|
||||
this._entityReg,
|
||||
this.hass.states
|
||||
);
|
||||
showAreaAddToDialog(this, {
|
||||
areaId: area.area_id,
|
||||
entityIds: sceneEntityIds,
|
||||
canCreateScene:
|
||||
isComponentLoaded(this.hass.config, "scene") &&
|
||||
sceneEntityIds.length > 0,
|
||||
entityIds: this._areaEntityIds,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,8 @@ export class HaConfigAreasDashboard extends LitElement {
|
||||
|
||||
@property({ attribute: "is-wide", type: Boolean }) public isWide = false;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@state() private _hierarchy?: AreasFloorHierarchy;
|
||||
@@ -167,6 +169,7 @@ export class HaConfigAreasDashboard extends LitElement {
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.isWide=${this.isWide}
|
||||
.backPath=${this._searchParms.has("historyBack")
|
||||
? undefined
|
||||
|
||||
@@ -3,7 +3,6 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
||||
export interface AreaAddToDialogParams {
|
||||
areaId: string;
|
||||
entityIds: string[];
|
||||
canCreateScene: boolean;
|
||||
}
|
||||
|
||||
export const loadAreaAddToDialog = () => import("./dialog-area-add-to");
|
||||
|
||||
@@ -52,7 +52,6 @@ import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-tooltip";
|
||||
import type {
|
||||
AutomationClipboard,
|
||||
Condition,
|
||||
@@ -212,27 +211,11 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
);
|
||||
|
||||
return html`
|
||||
<div id="condition-icon" class="icon-badge-wrapper" slot="leading-icon">
|
||||
<ha-condition-icon
|
||||
.hass=${this.hass}
|
||||
.condition=${this.condition.condition}
|
||||
></ha-condition-icon>
|
||||
${this.optionsInSidebar && this.condition.condition !== "trigger"
|
||||
? html`<ha-automation-row-live-test
|
||||
.state=${this._liveTestResult.state}
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.conditions.live_test_state.${this._liveTestResult.state}`
|
||||
)}
|
||||
></ha-automation-row-live-test>`
|
||||
: nothing}
|
||||
</div>
|
||||
${this.optionsInSidebar &&
|
||||
this.condition.condition !== "trigger" &&
|
||||
this._liveTestResult.message
|
||||
? html`<ha-tooltip for="condition-icon" slot="leading-icon"
|
||||
>${this._liveTestResult.message}</ha-tooltip
|
||||
>`
|
||||
: nothing}
|
||||
<ha-condition-icon
|
||||
slot="leading-icon"
|
||||
.hass=${this.hass}
|
||||
.condition=${this.condition.condition}
|
||||
></ha-condition-icon>
|
||||
<h3 slot="header">
|
||||
${capitalizeFirstLetter(
|
||||
describeCondition(this.condition, this.hass, this._entityReg)
|
||||
@@ -548,7 +531,17 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
@click=${this._toggleSidebar}
|
||||
@toggle-collapsed=${this._toggleCollapse}
|
||||
>${this._renderRow()}
|
||||
</ha-automation-row>`
|
||||
<ha-automation-row-live-test
|
||||
slot="icons"
|
||||
.state=${this.condition.condition !== "trigger"
|
||||
? this._liveTestResult.state
|
||||
: "unknown"}
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.conditions.live_test_state.${this.condition.condition !== "trigger" ? this._liveTestResult.state : "unknown"}`
|
||||
)}
|
||||
.message=${this._liveTestResult.message}
|
||||
></ha-automation-row-live-test
|
||||
></ha-automation-row>`
|
||||
: html`
|
||||
<ha-expansion-panel
|
||||
left-chevron
|
||||
|
||||
@@ -53,11 +53,6 @@ export const rowStyles = css`
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.icon-badge-wrapper {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.note-indicator {
|
||||
color: var(--ha-color-on-neutral-normal);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ class PanelDeveloperTools extends LitElement {
|
||||
<div class="toolbar">
|
||||
<ha-icon-button-arrow-prev
|
||||
slot="navigationIcon"
|
||||
.hass=${this.hass}
|
||||
@click=${this._handleBack}
|
||||
></ha-icon-button-arrow-prev>
|
||||
<div class="main-title">
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
import { computeDeviceNameDisplay } from "../../../../common/entity/compute_device_name";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-adaptive-dialog";
|
||||
import "../../../../components/ha-list";
|
||||
import "../../../../components/ha-list-item";
|
||||
import "../../../../components/ha-spinner";
|
||||
import type { AutomationConfig } from "../../../../data/automation";
|
||||
import { showAutomationEditor } from "../../../../data/automation";
|
||||
@@ -33,38 +35,15 @@ import {
|
||||
} from "../../../../data/device/device_automation";
|
||||
import type { ScriptConfig } from "../../../../data/script";
|
||||
import { showScriptEditor } from "../../../../data/script";
|
||||
import type { SceneEntities } from "../../../../data/scene";
|
||||
import { showSceneEditor } from "../../../../data/scene";
|
||||
import "../../../../dialogs/add-to/ha-add-to-action-list";
|
||||
import type {
|
||||
AddToActionListActionSelectedEvent,
|
||||
AddToActionListItem,
|
||||
AddToActionListSection,
|
||||
} from "../../../../dialogs/add-to/ha-add-to-action-list";
|
||||
import {
|
||||
addToActionHandler,
|
||||
createAddToSceneEntities,
|
||||
type AddToAutomationScriptActionKey,
|
||||
} from "../../../../dialogs/add-to/add-to";
|
||||
type AddToActionKey,
|
||||
} from "../../../../dialogs/more-info/add-to";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import type { DeviceAddToDialogParams } from "./show-dialog-device-add-to";
|
||||
|
||||
type DeviceLegacyAddToActionType =
|
||||
| "trigger"
|
||||
| "condition"
|
||||
| "automation_action"
|
||||
| "script_action";
|
||||
|
||||
type DeviceAddToAction =
|
||||
| (AddToActionListItem & {
|
||||
kind: "add-to";
|
||||
key: AddToAutomationScriptActionKey;
|
||||
})
|
||||
| (AddToActionListItem & {
|
||||
kind: "legacy";
|
||||
legacyType: DeviceLegacyAddToActionType;
|
||||
})
|
||||
| (AddToActionListItem & { kind: "scene" });
|
||||
|
||||
@customElement("dialog-device-add-to")
|
||||
export class DialogDeviceAddTo extends LitElement {
|
||||
@state()
|
||||
@@ -153,18 +132,11 @@ export class DialogDeviceAddTo extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const deviceName = computeDeviceNameDisplay(
|
||||
this._params.device,
|
||||
this._i18n.localize,
|
||||
this._states
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-adaptive-dialog
|
||||
.open=${this._open}
|
||||
header-title=${this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.title",
|
||||
{ target: deviceName }
|
||||
"ui.dialogs.more_info_control.add_to.title"
|
||||
)}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
@@ -179,62 +151,80 @@ export class DialogDeviceAddTo extends LitElement {
|
||||
if (!this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const sections: AddToActionListSection<DeviceAddToAction>[] = [
|
||||
{
|
||||
title: this._i18n.localize(
|
||||
"ui.panel.config.devices.automation.automations_heading"
|
||||
),
|
||||
actions: [
|
||||
{
|
||||
kind: "add-to",
|
||||
key: "automation_trigger",
|
||||
iconPath: mdiRobotOutline,
|
||||
name: this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.action_options.automation_trigger"
|
||||
),
|
||||
},
|
||||
{
|
||||
kind: "add-to",
|
||||
key: "automation_condition",
|
||||
iconPath: mdiPlaylistCheck,
|
||||
name: this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.action_options.automation_condition"
|
||||
),
|
||||
},
|
||||
{
|
||||
kind: "add-to",
|
||||
key: "automation_action",
|
||||
iconPath: mdiPlayCircleOutline,
|
||||
name: this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.action_options.automation_action"
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: this._i18n.localize(
|
||||
"ui.panel.config.devices.script.scripts_heading"
|
||||
),
|
||||
actions: [
|
||||
{
|
||||
kind: "add-to",
|
||||
key: "script_action",
|
||||
iconPath: mdiScriptTextOutline,
|
||||
name: this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.action_options.script_action"
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
this._addSceneSection(sections);
|
||||
const deviceName = computeDeviceNameDisplay(
|
||||
this._params.device,
|
||||
this._i18n.localize,
|
||||
this._states
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-add-to-action-list
|
||||
.sections=${sections}
|
||||
@add-to-list-action-selected=${this._handleActionSelected}
|
||||
></ha-add-to-action-list>
|
||||
<h3 class="section-header">
|
||||
${this._i18n.localize(
|
||||
"ui.panel.config.devices.automation.automations_heading"
|
||||
)}
|
||||
</h3>
|
||||
<ha-list>
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
data-type="automation_trigger"
|
||||
@click=${this._handleNewAction}
|
||||
data-dialog="close"
|
||||
>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiRobotOutline}></ha-svg-icon>
|
||||
${this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.actions.automation_trigger",
|
||||
{ target: deviceName }
|
||||
)}
|
||||
</ha-list-item>
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
data-type="automation_condition"
|
||||
@click=${this._handleNewAction}
|
||||
data-dialog="close"
|
||||
>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiPlaylistCheck}></ha-svg-icon>
|
||||
${this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.actions.automation_condition",
|
||||
{ target: deviceName }
|
||||
)}
|
||||
</ha-list-item>
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
data-type="automation_action"
|
||||
@click=${this._handleNewAction}
|
||||
data-dialog="close"
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiPlayCircleOutline}
|
||||
></ha-svg-icon>
|
||||
${this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.actions.automation_action",
|
||||
{ target: deviceName }
|
||||
)}
|
||||
</ha-list-item>
|
||||
</ha-list>
|
||||
<h3 class="section-header">
|
||||
${this._i18n.localize("ui.panel.config.devices.script.scripts_heading")}
|
||||
</h3>
|
||||
<ha-list>
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
data-type="script_action"
|
||||
@click=${this._handleNewAction}
|
||||
data-dialog="close"
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiScriptTextOutline}
|
||||
></ha-svg-icon>
|
||||
${this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.actions.script_action",
|
||||
{ target: deviceName }
|
||||
)}
|
||||
</ha-list-item>
|
||||
</ha-list>
|
||||
${this._renderSceneSection(deviceName)}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -252,6 +242,12 @@ export class DialogDeviceAddTo extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const deviceName = computeDeviceNameDisplay(
|
||||
this._params.device,
|
||||
this._i18n.localize,
|
||||
this._states
|
||||
);
|
||||
|
||||
const hasTriggers = Boolean(this._triggers?.length);
|
||||
const hasConditions = Boolean(this._conditions?.length);
|
||||
const hasActions = Boolean(this._actions?.length);
|
||||
@@ -267,138 +263,165 @@ export class DialogDeviceAddTo extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
const automationActions: DeviceAddToAction[] = [];
|
||||
if (hasTriggers) {
|
||||
automationActions.push({
|
||||
kind: "legacy",
|
||||
legacyType: "trigger",
|
||||
iconPath: mdiRobotOutline,
|
||||
name: this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.action_options.automation_trigger"
|
||||
),
|
||||
});
|
||||
}
|
||||
if (hasConditions) {
|
||||
automationActions.push({
|
||||
kind: "legacy",
|
||||
legacyType: "condition",
|
||||
iconPath: mdiPlaylistCheck,
|
||||
name: this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.action_options.automation_condition"
|
||||
),
|
||||
});
|
||||
}
|
||||
if (hasActions) {
|
||||
automationActions.push({
|
||||
kind: "legacy",
|
||||
legacyType: "automation_action",
|
||||
iconPath: mdiPlayCircleOutline,
|
||||
name: this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.action_options.automation_action"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
const scriptActions: DeviceAddToAction[] = hasActions
|
||||
? [
|
||||
{
|
||||
kind: "legacy",
|
||||
legacyType: "script_action",
|
||||
iconPath: mdiScriptTextOutline,
|
||||
name: this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.action_options.script_action"
|
||||
),
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const sections: AddToActionListSection<DeviceAddToAction>[] = [
|
||||
{
|
||||
title: this._i18n.localize(
|
||||
"ui.panel.config.devices.automation.automations_heading"
|
||||
),
|
||||
actions: automationActions,
|
||||
empty: automationActions.length
|
||||
? undefined
|
||||
: this._i18n.localize(
|
||||
"ui.panel.config.devices.automation.no_automations"
|
||||
),
|
||||
},
|
||||
{
|
||||
title: this._i18n.localize(
|
||||
"ui.panel.config.devices.script.scripts_heading"
|
||||
),
|
||||
actions: scriptActions,
|
||||
empty: scriptActions.length
|
||||
? undefined
|
||||
: this._i18n.localize("ui.panel.config.devices.script.no_scripts"),
|
||||
},
|
||||
];
|
||||
this._addSceneSection(sections);
|
||||
|
||||
return html`
|
||||
<ha-add-to-action-list
|
||||
.sections=${sections}
|
||||
@add-to-list-action-selected=${this._handleActionSelected}
|
||||
></ha-add-to-action-list>
|
||||
<h3 class="section-header">
|
||||
${this._i18n.localize(
|
||||
"ui.panel.config.devices.automation.automations_heading"
|
||||
)}
|
||||
</h3>
|
||||
${hasTriggers || hasConditions || hasActions
|
||||
? html`
|
||||
<ha-list>
|
||||
${hasTriggers
|
||||
? html`
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
data-type="trigger"
|
||||
@click=${this._handleLegacyAction}
|
||||
data-dialog="close"
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiRobotOutline}
|
||||
></ha-svg-icon>
|
||||
${this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.actions.automation_trigger",
|
||||
{ target: deviceName }
|
||||
)}
|
||||
</ha-list-item>
|
||||
`
|
||||
: nothing}
|
||||
${hasConditions
|
||||
? html`
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
data-type="condition"
|
||||
@click=${this._handleLegacyAction}
|
||||
data-dialog="close"
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiPlaylistCheck}
|
||||
></ha-svg-icon>
|
||||
${this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.actions.automation_condition",
|
||||
{ target: deviceName }
|
||||
)}
|
||||
</ha-list-item>
|
||||
`
|
||||
: nothing}
|
||||
${hasActions
|
||||
? html`
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
data-type="automation_action"
|
||||
@click=${this._handleLegacyAction}
|
||||
data-dialog="close"
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiPlayCircleOutline}
|
||||
></ha-svg-icon>
|
||||
${this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.actions.automation_action",
|
||||
{ target: deviceName }
|
||||
)}
|
||||
</ha-list-item>
|
||||
`
|
||||
: nothing}
|
||||
</ha-list>
|
||||
`
|
||||
: html`
|
||||
<ha-list>
|
||||
<ha-list-item noninteractive>
|
||||
${this._i18n.localize(
|
||||
"ui.panel.config.devices.automation.no_automations"
|
||||
)}
|
||||
</ha-list-item>
|
||||
</ha-list>
|
||||
`}
|
||||
<h3 class="section-header">
|
||||
${this._i18n.localize("ui.panel.config.devices.script.scripts_heading")}
|
||||
</h3>
|
||||
${hasActions
|
||||
? html`
|
||||
<ha-list>
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
data-type="script_action"
|
||||
@click=${this._handleLegacyAction}
|
||||
data-dialog="close"
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiScriptTextOutline}
|
||||
></ha-svg-icon>
|
||||
${this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.actions.script_action",
|
||||
{ target: deviceName }
|
||||
)}
|
||||
</ha-list-item>
|
||||
</ha-list>
|
||||
`
|
||||
: html`
|
||||
<ha-list>
|
||||
<ha-list-item noninteractive>
|
||||
${this._i18n.localize(
|
||||
"ui.panel.config.devices.script.no_scripts"
|
||||
)}
|
||||
</ha-list-item>
|
||||
</ha-list>
|
||||
`}
|
||||
${this._renderSceneSection(deviceName)}
|
||||
`;
|
||||
}
|
||||
|
||||
private _addSceneSection(
|
||||
sections: AddToActionListSection<DeviceAddToAction>[]
|
||||
): void {
|
||||
if (!this._params?.canCreateScene || !this._params.entityIds.length) {
|
||||
return;
|
||||
private _renderSceneSection(deviceName: string) {
|
||||
if (!this._params?.entityIds.length) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
sections.push({
|
||||
title: this._i18n.localize(
|
||||
"ui.panel.config.devices.scene.scenes_heading"
|
||||
),
|
||||
actions: [
|
||||
{
|
||||
kind: "scene",
|
||||
iconPath: mdiPalette,
|
||||
name: this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.action_options.scene"
|
||||
),
|
||||
},
|
||||
],
|
||||
});
|
||||
return html`
|
||||
<h3 class="section-header">
|
||||
${this._i18n.localize("ui.panel.config.devices.scene.scenes_heading")}
|
||||
</h3>
|
||||
<ha-list>
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._handleCreateScene}
|
||||
data-dialog="close"
|
||||
>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiPalette}></ha-svg-icon>
|
||||
${this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.actions.scene",
|
||||
{ target: deviceName }
|
||||
)}
|
||||
</ha-list-item>
|
||||
</ha-list>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleActionSelected(
|
||||
ev: AddToActionListActionSelectedEvent<DeviceAddToAction>
|
||||
) {
|
||||
private _handleNewAction(ev: Event) {
|
||||
if (!this._params) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { action } = ev.detail;
|
||||
if (action.kind === "scene") {
|
||||
this._handleCreateScene();
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.kind === "add-to") {
|
||||
this._handleAddToAction(action.key);
|
||||
return;
|
||||
}
|
||||
|
||||
this._handleLegacyAction(action.legacyType);
|
||||
}
|
||||
|
||||
private _handleAddToAction(key: AddToAutomationScriptActionKey) {
|
||||
if (!this._params) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = (ev.currentTarget as HTMLElement).dataset
|
||||
.type as AddToActionKey;
|
||||
this.closeDialog();
|
||||
addToActionHandler(key, { device_id: this._params.device.id });
|
||||
}
|
||||
|
||||
// When new_triggers_conditions labs feature is promoted, this whole method can be removed.
|
||||
private _handleLegacyAction(type: DeviceLegacyAddToActionType) {
|
||||
private _handleLegacyAction(ev: Event) {
|
||||
if (!this._params) {
|
||||
return;
|
||||
}
|
||||
const type = (ev.currentTarget as HTMLElement).dataset.type as
|
||||
| "trigger"
|
||||
| "condition"
|
||||
| "automation_action"
|
||||
| "script_action";
|
||||
|
||||
this.closeDialog();
|
||||
|
||||
if (type === "script_action") {
|
||||
@@ -407,28 +430,29 @@ export class DialogDeviceAddTo extends LitElement {
|
||||
newScript.sequence = [this._actions[0]];
|
||||
}
|
||||
showScriptEditor(newScript, true);
|
||||
return;
|
||||
} else {
|
||||
const newAutomation = {} as AutomationConfig;
|
||||
if (type === "trigger" && this._triggers?.length) {
|
||||
newAutomation.triggers = [this._triggers[0]];
|
||||
} else if (type === "condition" && this._conditions?.length) {
|
||||
newAutomation.conditions = [this._conditions[0]];
|
||||
} else if (type === "automation_action" && this._actions?.length) {
|
||||
newAutomation.actions = [this._actions[0]];
|
||||
}
|
||||
showAutomationEditor(newAutomation, true);
|
||||
}
|
||||
|
||||
const newAutomation = {} as AutomationConfig;
|
||||
if (type === "trigger" && this._triggers?.length) {
|
||||
newAutomation.triggers = [this._triggers[0]];
|
||||
} else if (type === "condition" && this._conditions?.length) {
|
||||
newAutomation.conditions = [this._conditions[0]];
|
||||
} else if (type === "automation_action" && this._actions?.length) {
|
||||
newAutomation.actions = [this._actions[0]];
|
||||
}
|
||||
showAutomationEditor(newAutomation, true);
|
||||
}
|
||||
|
||||
private _handleCreateScene() {
|
||||
if (!this._params) {
|
||||
return;
|
||||
}
|
||||
const entities: SceneEntities = {};
|
||||
for (const entityId of this._params.entityIds) {
|
||||
entities[entityId] = "";
|
||||
}
|
||||
this.closeDialog();
|
||||
showSceneEditor({
|
||||
entities: createAddToSceneEntities(this._params.entityIds),
|
||||
});
|
||||
showSceneEditor({ entities });
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
@@ -445,6 +469,14 @@ export class DialogDeviceAddTo extends LitElement {
|
||||
padding: var(--ha-space-4);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
padding: var(--ha-space-2) var(--ha-space-4) 0;
|
||||
margin: 0;
|
||||
font-size: var(--ha-font-size-m);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ export interface DeviceAddToDialogParams {
|
||||
device: DeviceRegistryEntry;
|
||||
newTriggersConditions: boolean;
|
||||
entityIds: string[];
|
||||
canCreateScene: boolean;
|
||||
}
|
||||
|
||||
export const loadDeviceAddToDialog = () => import("./ha-device-add-to-dialog");
|
||||
|
||||
@@ -86,7 +86,6 @@ import { domainToName } from "../../../data/integration";
|
||||
import { regenerateEntityIds } from "../../../data/regenerate_entity_ids";
|
||||
import type { RelatedResult } from "../../../data/search";
|
||||
import { findRelated } from "../../../data/search";
|
||||
import { filterAddToSceneEntityIds } from "../../../dialogs/add-to/add-to";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
@@ -425,11 +424,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
this._entityReg,
|
||||
this.hass.devices
|
||||
);
|
||||
const sceneEntityIds = filterAddToSceneEntityIds(
|
||||
this._entityIds(entities),
|
||||
this._entityReg,
|
||||
this.hass.states
|
||||
);
|
||||
const entitiesByCategory = this._entitiesByCategory(entities);
|
||||
const quickLinkCounts = this._getQuickLinkCounts(entities, this._related);
|
||||
const batteryEntity = this._batteryEntity(entities);
|
||||
@@ -537,7 +531,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
: this.hass.localize("ui.panel.config.devices.add_prompt_enabled");
|
||||
|
||||
const hasSceneSupport =
|
||||
isComponentLoaded(this.hass.config, "scene") && sceneEntityIds.length;
|
||||
isComponentLoaded(this.hass.config, "scene") && entities.length;
|
||||
|
||||
const relatedCard =
|
||||
isComponentLoaded(this.hass.config, "automation") ||
|
||||
@@ -557,7 +551,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.add_to.item"
|
||||
"ui.dialogs.more_info_control.add_to.title"
|
||||
)}
|
||||
</ha-button>
|
||||
</h1>
|
||||
@@ -1372,18 +1366,10 @@ export class HaConfigDevicePage extends LitElement {
|
||||
this._entityReg,
|
||||
this.hass.devices
|
||||
).map((entity) => entity.entity_id);
|
||||
const sceneEntityIds = filterAddToSceneEntityIds(
|
||||
entityIds,
|
||||
this._entityReg,
|
||||
this.hass.states
|
||||
);
|
||||
showDeviceAddToDialog(this, {
|
||||
device,
|
||||
newTriggersConditions: this._newTriggersConditions,
|
||||
entityIds: sceneEntityIds,
|
||||
canCreateScene:
|
||||
isComponentLoaded(this.hass.config, "scene") &&
|
||||
sceneEntityIds.length > 0,
|
||||
entityIds,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -231,6 +231,24 @@ export class DialogEnergyBatterySettings
|
||||
@value-changed=${this._statisticSocChanged}
|
||||
></ha-statistic-picker>
|
||||
|
||||
<ha-input
|
||||
.value=${this._source.capacity != null
|
||||
? String(this._source.capacity)
|
||||
: ""}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.energy.battery.dialog.capacity"
|
||||
)}
|
||||
.hint=${this.hass.localize(
|
||||
"ui.panel.config.energy.battery.dialog.capacity_helper"
|
||||
)}
|
||||
type="number"
|
||||
step="any"
|
||||
min="0"
|
||||
@input=${this._capacityChanged}
|
||||
>
|
||||
<span slot="end">kWh</span>
|
||||
</ha-input>
|
||||
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
@@ -314,6 +332,18 @@ export class DialogEnergyBatterySettings
|
||||
};
|
||||
}
|
||||
|
||||
private _capacityChanged(ev: InputEvent) {
|
||||
const rawValue = (ev.target as HaInput).value;
|
||||
const value = rawValue ? parseFloat(rawValue) : NaN;
|
||||
this._source = {
|
||||
...this._source!,
|
||||
capacity: Number.isFinite(value) && value > 0 ? value : undefined,
|
||||
};
|
||||
if (this._source.capacity === undefined) {
|
||||
delete this._source.capacity;
|
||||
}
|
||||
}
|
||||
|
||||
private async _save() {
|
||||
try {
|
||||
const source: BatterySourceTypeEnergyPreference = {
|
||||
@@ -334,6 +364,10 @@ export class DialogEnergyBatterySettings
|
||||
source.stat_soc = this._source!.stat_soc;
|
||||
}
|
||||
|
||||
if (this._source!.capacity != null) {
|
||||
source.capacity = this._source!.capacity;
|
||||
}
|
||||
|
||||
await this._params!.saveCallback(source);
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
@@ -351,7 +385,11 @@ export class DialogEnergyBatterySettings
|
||||
display: block;
|
||||
margin-bottom: var(--ha-space-4);
|
||||
}
|
||||
ha-statistic-picker:last-of-type {
|
||||
ha-input {
|
||||
margin-bottom: var(--ha-space-4);
|
||||
--ha-input-padding-bottom: 0;
|
||||
}
|
||||
ha-input:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
`,
|
||||
|
||||
@@ -118,6 +118,7 @@ class HaConfigEnergy extends LitElement {
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.backPath=${this._searchParms.has("historyBack")
|
||||
? undefined
|
||||
: "/config/lovelace/dashboards"}
|
||||
|
||||
@@ -58,6 +58,8 @@ const DATA_SET_CONFIG: SeriesOption = {
|
||||
class HaConfigHardwareOverview extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@state() private _error?: string;
|
||||
@@ -247,6 +249,7 @@ class HaConfigHardwareOverview extends SubscribeMixin(LitElement) {
|
||||
<hass-tabs-subpage
|
||||
back-path="/config/system"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
.tabs=${hardwareTabs(this.hass)}
|
||||
>
|
||||
|
||||
@@ -511,6 +511,7 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.backPath=${this._searchParams.has("historyBack")
|
||||
? undefined
|
||||
: "/config"}
|
||||
|
||||
+10
-12
@@ -25,7 +25,6 @@ import {
|
||||
type ExtEntityRegistryEntry,
|
||||
} from "../../../../../data/entity/entity_registry";
|
||||
import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box";
|
||||
import { OVERRIDE_DEVICE_CLASSES } from "../../../entities/entity-registry-settings-editor";
|
||||
import "./matter-add-device/matter-add-device-apple-home";
|
||||
import "./matter-add-device/matter-add-device-existing";
|
||||
import "./matter-add-device/matter-add-device-generic";
|
||||
@@ -140,17 +139,15 @@ class DialogMatterAddDevice extends LitElement {
|
||||
entityIds
|
||||
);
|
||||
|
||||
this._mainEntity = Object.values(entries).find((entry) => {
|
||||
if (entry.entity_category) return false;
|
||||
const domain = computeDomain(entry.entity_id);
|
||||
const deviceClasses = OVERRIDE_DEVICE_CLASSES[domain];
|
||||
if (!deviceClasses) return false;
|
||||
const deviceClass = entry.device_class ?? entry.original_device_class;
|
||||
if (!deviceClass) return false;
|
||||
return deviceClasses.some(
|
||||
(classes) => classes.length > 1 && classes.includes(deviceClass)
|
||||
);
|
||||
});
|
||||
const mainEntry = Object.values(entries).find(
|
||||
(e) => e.original_name === null
|
||||
);
|
||||
if (!mainEntry) return;
|
||||
|
||||
const domain = computeDomain(mainEntry.entity_id);
|
||||
if (domain === "cover" || domain === "binary_sensor") {
|
||||
this._mainEntity = mainEntry;
|
||||
}
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
@@ -375,6 +372,7 @@ class DialogMatterAddDevice extends LitElement {
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
slot="headerNavigationIcon"
|
||||
.hass=${this.hass}
|
||||
@click=${this._back}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`
|
||||
|
||||
@@ -39,6 +39,8 @@ export class HaConfigPerson extends LitElement {
|
||||
|
||||
@property({ attribute: "is-wide", type: Boolean }) public isWide = false;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@state() private _storageItems?: Person[];
|
||||
@@ -59,6 +61,7 @@ export class HaConfigPerson extends LitElement {
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
back-path="/config"
|
||||
.tabs=${configSections.persons}
|
||||
|
||||
@@ -27,6 +27,8 @@ export class HaConfigVoiceAssistantsAssistants extends LitElement {
|
||||
|
||||
@property({ attribute: "is-wide", type: Boolean }) public isWide = false;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
protected render() {
|
||||
@@ -37,6 +39,7 @@ export class HaConfigVoiceAssistantsAssistants extends LitElement {
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
back-path="/config"
|
||||
.route=${this.route}
|
||||
.tabs=${voiceAssistantTabs}
|
||||
|
||||
@@ -234,6 +234,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
.backPath=${this._searchParms.has("historyBack")
|
||||
? undefined
|
||||
|
||||
@@ -14,7 +14,6 @@ import "../lovelace/hui-root";
|
||||
import type { Lovelace } from "../lovelace/types";
|
||||
import "../lovelace/views/hui-view";
|
||||
import "../lovelace/views/hui-view-container";
|
||||
import { DEFAULT_POWER_COLLECTION_KEY } from "./constants";
|
||||
|
||||
@customElement("ha-panel-energy")
|
||||
class PanelEnergy extends LitElement {
|
||||
@@ -87,15 +86,7 @@ class PanelEnergy extends LitElement {
|
||||
|
||||
private async _setLovelace() {
|
||||
const config: LovelaceConfig = await generateLovelaceDashboardStrategy(
|
||||
{
|
||||
strategy: {
|
||||
type: "energy",
|
||||
default_collection:
|
||||
this.route?.path === "/now"
|
||||
? DEFAULT_POWER_COLLECTION_KEY
|
||||
: undefined,
|
||||
},
|
||||
},
|
||||
{ strategy: { type: "energy" } },
|
||||
this.hass
|
||||
);
|
||||
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import {
|
||||
EMPTY_PREFERENCES,
|
||||
getEnergyDataCollection,
|
||||
} from "../../../data/energy";
|
||||
import { getEnergyDataCollection } from "../../../data/energy";
|
||||
import type { EnergyPreferences } from "../../../data/energy";
|
||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||
import type { LovelaceConfig } from "../../../data/lovelace/config/types";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { LocalizeKeys } from "../../../common/translations/localize";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceStrategyDependency } from "../../lovelace/strategies/types";
|
||||
import {
|
||||
DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
DEFAULT_POWER_COLLECTION_KEY,
|
||||
@@ -62,20 +58,23 @@ const WIZARD_VIEW = {
|
||||
cards: [{ type: "custom:energy-setup-wizard-card" }],
|
||||
};
|
||||
|
||||
const EMPTY_PREFERENCES: EnergyPreferences = {
|
||||
energy_sources: [],
|
||||
device_consumption: [],
|
||||
device_consumption_water: [],
|
||||
};
|
||||
|
||||
export interface EnergyDashboardStrategyConfig extends LovelaceStrategyConfig {
|
||||
type: "energy";
|
||||
default_collection?: string;
|
||||
}
|
||||
|
||||
@customElement("energy-dashboard-strategy")
|
||||
export class EnergyDashboardStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [];
|
||||
|
||||
static async generate(
|
||||
_config: EnergyDashboardStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceConfig> {
|
||||
const prefs = await fetchEnergyPrefs(hass, _config.default_collection);
|
||||
const prefs = await fetchEnergyPrefs(hass);
|
||||
|
||||
if (
|
||||
!prefs ||
|
||||
@@ -148,19 +147,20 @@ export class EnergyDashboardStrategy extends ReactiveElement {
|
||||
}
|
||||
|
||||
async function fetchEnergyPrefs(
|
||||
hass: HomeAssistant,
|
||||
defaultCollection?: string
|
||||
hass: HomeAssistant
|
||||
): Promise<EnergyPreferences> {
|
||||
const collection = getEnergyDataCollection(hass, {
|
||||
key: defaultCollection || DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
});
|
||||
|
||||
return await new Promise<EnergyPreferences>((resolve) => {
|
||||
const unsub = collection.subscribe((data) => {
|
||||
unsub();
|
||||
resolve(data.prefs || EMPTY_PREFERENCES);
|
||||
});
|
||||
key: DEFAULT_ENERGY_COLLECTION_KEY,
|
||||
});
|
||||
try {
|
||||
await collection.refresh();
|
||||
} catch (err: any) {
|
||||
if (err.code === "not_found") {
|
||||
return EMPTY_PREFERENCES;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
return collection.prefs || EMPTY_PREFERENCES;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -5,13 +5,10 @@ import { getEnergyDataCollection } from "../../../data/energy";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||
import type { LovelaceStrategyDependency } from "../../lovelace/strategies/types";
|
||||
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../constants";
|
||||
|
||||
@customElement("energy-overview-view-strategy")
|
||||
export class EnergyOverviewViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [];
|
||||
|
||||
static async generate(
|
||||
_config: LovelaceStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -12,12 +12,9 @@ import {
|
||||
LARGE_SCREEN_CONDITION,
|
||||
SMALL_SCREEN_CONDITION,
|
||||
} from "../../lovelace/strategies/helpers/view-columns-conditions";
|
||||
import type { LovelaceStrategyDependency } from "../../lovelace/strategies/types";
|
||||
|
||||
@customElement("energy-view-strategy")
|
||||
export class EnergyViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [];
|
||||
|
||||
static async generate(
|
||||
_config: LovelaceStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -6,12 +6,9 @@ import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../constants";
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
import type { LovelaceStrategyDependency } from "../../lovelace/strategies/types";
|
||||
|
||||
@customElement("gas-view-strategy")
|
||||
export class GasViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [];
|
||||
|
||||
static async generate(
|
||||
_config: LovelaceStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -8,12 +8,9 @@ import { DEFAULT_ENERGY_COLLECTION_KEY } from "../constants";
|
||||
import { shouldShowFloorsAndAreas } from "./show-floors-and-areas";
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
import type { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge";
|
||||
import type { LovelaceStrategyDependency } from "../../lovelace/strategies/types";
|
||||
|
||||
@customElement("power-view-strategy")
|
||||
export class PowerViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [];
|
||||
|
||||
static async generate(
|
||||
_config: LovelaceStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
@@ -24,9 +21,7 @@ export class PowerViewStrategy extends ReactiveElement {
|
||||
const energyCollection = getEnergyDataCollection(hass, {
|
||||
key: collectionKey,
|
||||
});
|
||||
if (!energyCollection.prefs) {
|
||||
await energyCollection.refresh();
|
||||
}
|
||||
await energyCollection.refresh();
|
||||
const prefs = energyCollection.prefs;
|
||||
|
||||
const hasPowerSources = prefs?.energy_sources.some((source) => {
|
||||
|
||||
@@ -5,14 +5,11 @@ import type { LovelaceSectionConfig } from "../../../data/lovelace/config/sectio
|
||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceStrategyDependency } from "../../lovelace/strategies/types";
|
||||
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../constants";
|
||||
import { shouldShowFloorsAndAreas } from "./show-floors-and-areas";
|
||||
|
||||
@customElement("water-view-strategy")
|
||||
export class WaterViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [];
|
||||
|
||||
static async generate(
|
||||
_config: LovelaceStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -7,6 +7,7 @@ import { styleMap } from "lit/directives/style-map";
|
||||
import { atLeastVersion } from "../../common/config/version";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { deepEqual } from "../../common/util/deep-equal";
|
||||
import "../../components/ha-button";
|
||||
import "../../components/ha-svg-icon";
|
||||
import { updateAreaRegistryEntry } from "../../data/area/area_registry";
|
||||
@@ -25,10 +26,7 @@ import { showDeviceRegistryDetailDialog } from "../config/devices/device-registr
|
||||
import { showAddIntegrationDialog } from "../config/integrations/show-add-integration-dialog";
|
||||
import "../lovelace/hui-root";
|
||||
import type { ExtraActionItem } from "../lovelace/hui-root";
|
||||
import {
|
||||
checkStrategyShouldRegenerate,
|
||||
generateLovelaceDashboardStrategy,
|
||||
} from "../lovelace/strategies/get-strategy";
|
||||
import { expandLovelaceConfigStrategies } from "../lovelace/strategies/get-strategy";
|
||||
import type { Lovelace } from "../lovelace/types";
|
||||
import { showEditHomeDialog } from "./dialogs/show-dialog-edit-home";
|
||||
import { showNewOverviewDialog } from "./dialogs/show-dialog-new-overview";
|
||||
@@ -95,37 +93,33 @@ class PanelHome extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as this["hass"] | undefined;
|
||||
if (!oldHass) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Locale changed: regenerate to refresh translated content
|
||||
if (oldHass.localize !== this.hass.localize) {
|
||||
const oldHass = changedProps.get("hass") as this["hass"];
|
||||
if (oldHass && oldHass.localize !== this.hass.localize) {
|
||||
this._setLovelace();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.hass.config.state !== "RUNNING") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Home Assistant just started: run the full setup
|
||||
if (oldHass.config.state !== "RUNNING") {
|
||||
this._setup();
|
||||
return;
|
||||
}
|
||||
|
||||
// A registry the strategy depends on changed: regenerate
|
||||
if (
|
||||
checkStrategyShouldRegenerate(
|
||||
"dashboard",
|
||||
this._strategyConfig.strategy,
|
||||
oldHass,
|
||||
this.hass
|
||||
)
|
||||
) {
|
||||
this._debounceRegenerateStrategy();
|
||||
if (oldHass && this.hass) {
|
||||
// If the entity registry changed, ask the user if they want to refresh the config
|
||||
if (
|
||||
oldHass.entities !== this.hass.entities ||
|
||||
oldHass.devices !== this.hass.devices ||
|
||||
oldHass.areas !== this.hass.areas ||
|
||||
oldHass.floors !== this.hass.floors ||
|
||||
oldHass.panels !== this.hass.panels
|
||||
) {
|
||||
if (this.hass.config.state === "RUNNING") {
|
||||
this._debounceRegistriesChanged();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If ha started, refresh the config
|
||||
if (
|
||||
this.hass.config.state === "RUNNING" &&
|
||||
oldHass.config.state !== "RUNNING"
|
||||
) {
|
||||
this._setup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,12 +144,12 @@ class PanelHome extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _debounceRegenerateStrategy = debounce(
|
||||
() => this._regenerateStrategyConfig(),
|
||||
private _debounceRegistriesChanged = debounce(
|
||||
() => this._registriesChanged(),
|
||||
200
|
||||
);
|
||||
|
||||
private _regenerateStrategyConfig() {
|
||||
private _registriesChanged = async () => {
|
||||
// If on an area view that no longer exists, redirect to overview
|
||||
const path = this.route?.path?.split("/")[1];
|
||||
if (path?.startsWith("areas-")) {
|
||||
@@ -166,7 +160,7 @@ class PanelHome extends LitElement {
|
||||
}
|
||||
}
|
||||
this._setLovelace();
|
||||
}
|
||||
};
|
||||
|
||||
private _updateExtraActionItems() {
|
||||
const path = this.route?.path?.split("/")[1];
|
||||
@@ -326,8 +320,11 @@ class PanelHome extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private get _strategyConfig(): LovelaceDashboardStrategyConfig {
|
||||
return {
|
||||
private async _setLovelace() {
|
||||
if (this._loadConfigPromise) {
|
||||
await this._loadConfigPromise;
|
||||
}
|
||||
const strategyConfig: LovelaceDashboardStrategyConfig = {
|
||||
strategy: {
|
||||
type: "home",
|
||||
favorite_entities: this._config.favorite_entities,
|
||||
@@ -337,17 +334,16 @@ class PanelHome extends LitElement {
|
||||
shortcuts: this._config.shortcuts,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private async _setLovelace() {
|
||||
if (this._loadConfigPromise) {
|
||||
await this._loadConfigPromise;
|
||||
}
|
||||
const config = await generateLovelaceDashboardStrategy(
|
||||
this._strategyConfig,
|
||||
const config = await expandLovelaceConfigStrategies(
|
||||
strategyConfig,
|
||||
this.hass
|
||||
);
|
||||
|
||||
if (deepEqual(config, this._lovelace?.config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._lovelace = {
|
||||
config: config,
|
||||
rawConfig: config,
|
||||
|
||||
@@ -210,16 +210,37 @@ class HuiEnergyDistrubutionCard
|
||||
// card's data when the selected period extends to now. For historical
|
||||
// periods (yesterday, last week, ...) fall back to the generic icon.
|
||||
if (periodIncludesNow(this._data)) {
|
||||
const socValues = types
|
||||
.battery!.map((source) =>
|
||||
source.stat_soc
|
||||
const socBatteries = types
|
||||
.battery!.map((source) => ({
|
||||
soc: source.stat_soc
|
||||
? Number(this.hass.states[source.stat_soc]?.state)
|
||||
: NaN
|
||||
)
|
||||
.filter((value) => Number.isFinite(value));
|
||||
if (socValues.length) {
|
||||
averageBatterySoc =
|
||||
socValues.reduce((sum, value) => sum + value, 0) / socValues.length;
|
||||
: NaN,
|
||||
capacity: source.capacity,
|
||||
}))
|
||||
.filter((battery) => Number.isFinite(battery.soc));
|
||||
if (socBatteries.length) {
|
||||
// Weight each battery's SOC by its capacity so the combined value
|
||||
// reflects the total stored energy. Batteries without a configured
|
||||
// capacity assume the mean of the configured ones; when none are
|
||||
// configured this falls back to an equally weighted (simple) average.
|
||||
const configuredCapacities = socBatteries
|
||||
.map((battery) => battery.capacity)
|
||||
.filter((capacity) => capacity != null && capacity > 0) as number[];
|
||||
const meanCapacity = configuredCapacities.length
|
||||
? configuredCapacities.reduce((sum, value) => sum + value, 0) /
|
||||
configuredCapacities.length
|
||||
: 1;
|
||||
let weightSum = 0;
|
||||
let weightedSocSum = 0;
|
||||
socBatteries.forEach((battery) => {
|
||||
const capacity =
|
||||
battery.capacity != null && battery.capacity > 0
|
||||
? battery.capacity
|
||||
: meanCapacity;
|
||||
weightSum += capacity;
|
||||
weightedSocSum += battery.soc * capacity;
|
||||
});
|
||||
averageBatterySoc = weightedSocSum / weightSum;
|
||||
batteryIconPath = batteryLevelIconPath(averageBatterySoc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,7 +438,9 @@ class HuiEnergySankeyCard
|
||||
}
|
||||
|
||||
private _valueFormatter = (value: number) =>
|
||||
`${formatNumber(value, this.hass.locale, value < 0.1 ? { maximumFractionDigits: 3 } : undefined)} kWh`;
|
||||
`<div style="direction:ltr; display: inline;">
|
||||
${formatNumber(value, this.hass.locale, value < 0.1 ? { maximumFractionDigits: 3 } : undefined)}
|
||||
kWh</div>`;
|
||||
|
||||
private _handleNodeClick(ev: CustomEvent<{ node: Node }>) {
|
||||
const { node } = ev.detail;
|
||||
|
||||
@@ -580,7 +580,9 @@ class HuiPowerSankeyCard
|
||||
}
|
||||
|
||||
private _valueFormatter = (value: number) =>
|
||||
formatPowerShort(this.hass, value);
|
||||
`<div style="direction:ltr; display: inline;">
|
||||
${formatPowerShort(this.hass, value)}
|
||||
</div>`;
|
||||
|
||||
private _handleNodeClick(ev: CustomEvent<{ node: Node }>) {
|
||||
const { node } = ev.detail;
|
||||
|
||||
@@ -511,11 +511,9 @@ class HuiWaterFlowSankeyCard
|
||||
}
|
||||
|
||||
private _valueFormatter = (value: number) =>
|
||||
formatFlowRateShort(
|
||||
this.hass.locale,
|
||||
this.hass.config.unit_system.length,
|
||||
value
|
||||
);
|
||||
`<div style="direction:ltr; display: inline;">
|
||||
${formatFlowRateShort(this.hass.locale, this.hass.config.unit_system.length, value)}
|
||||
</div>`;
|
||||
|
||||
private _handleNodeClick(ev: CustomEvent<{ node: Node }>) {
|
||||
const { node } = ev.detail;
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { IFuseOptions } from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { until } from "lit/directives/until";
|
||||
@@ -66,19 +66,10 @@ export class HuiBadgePicker extends LitElement {
|
||||
|
||||
@state() private _height?: number;
|
||||
|
||||
@query("ha-input-search") private _searchInput?: HaInputSearch;
|
||||
|
||||
private _unusedEntities?: string[];
|
||||
|
||||
private _usedEntities?: string[];
|
||||
|
||||
public async focus(): Promise<void> {
|
||||
await this.updateComplete;
|
||||
// Wait for the input's inner wa-input to render so focus delegation works.
|
||||
await this._searchInput?.updateComplete;
|
||||
this._searchInput?.focus();
|
||||
}
|
||||
|
||||
private _filterBadges = memoizeOne(
|
||||
(badgeElements: BadgeElement[], filter?: string): BadgeElement[] => {
|
||||
if (!filter) {
|
||||
|
||||
@@ -62,17 +62,19 @@ export class HuiCardPicker extends LitElement {
|
||||
|
||||
@state() private _filter = "";
|
||||
|
||||
@query("ha-input-search") private _searchInput?: HaInputSearch;
|
||||
@query("ha-input-search") private _searchInput?: HTMLElement;
|
||||
|
||||
private _unusedEntities?: string[];
|
||||
|
||||
private _usedEntities?: string[];
|
||||
|
||||
public async focus(): Promise<void> {
|
||||
await this.updateComplete;
|
||||
// Wait for the input's inner wa-input to render so focus delegation works.
|
||||
await this._searchInput?.updateComplete;
|
||||
this._searchInput?.focus();
|
||||
if (this._searchInput) {
|
||||
this._searchInput.focus();
|
||||
} else {
|
||||
await this.updateComplete;
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private _filterCards = memoizeOne(
|
||||
|
||||
@@ -133,7 +133,6 @@ export class HuiCreateDialogCard
|
||||
this._currTab === "entity"
|
||||
? html`
|
||||
<hui-suggestion-picker
|
||||
?autofocus=${!this._narrow}
|
||||
.hass=${this.hass}
|
||||
.prioritizedCardTypes=${this._params.suggestedCards}
|
||||
@suggestion-picked=${this._handleSuggestionPicked}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { transform } from "../../../../common/decorators/transform";
|
||||
@@ -85,15 +85,6 @@ export class HuiSuggestionEntityTree extends LitElement {
|
||||
|
||||
@state() private _fuseIndex?: EntityFuseIndex;
|
||||
|
||||
@query("ha-input-search") private _searchInput?: HaInputSearch;
|
||||
|
||||
public async focus(): Promise<void> {
|
||||
await this.updateComplete;
|
||||
// Wait for the input's inner wa-input to render so focus delegation works.
|
||||
await this._searchInput?.updateComplete;
|
||||
this._searchInput?.focus();
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this._loadDomainTranslations();
|
||||
@@ -144,7 +135,7 @@ export class HuiSuggestionEntityTree extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.hass) return nothing;
|
||||
if (!this.hass || !this._tree) return nothing;
|
||||
|
||||
return html`
|
||||
<ha-input-search
|
||||
@@ -155,13 +146,11 @@ export class HuiSuggestionEntityTree extends LitElement {
|
||||
)}
|
||||
@input=${this._handleFilterChange}
|
||||
></ha-input-search>
|
||||
${this._tree
|
||||
? this._filter
|
||||
? this._renderSearchResults()
|
||||
: html`<div class="tree ha-scrollbar">
|
||||
${this._renderTree(this._tree)}
|
||||
</div>`
|
||||
: nothing}
|
||||
${this._filter
|
||||
? this._renderSearchResults()
|
||||
: html`<div class="tree ha-scrollbar">
|
||||
${this._renderTree(this._tree)}
|
||||
</div>`}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mdiClose, mdiViewGridPlus } from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
import type { CardSuggestion } from "../../card-suggestions/types";
|
||||
import "./hui-suggestion-card";
|
||||
import "./hui-suggestion-entity-tree";
|
||||
import type { HuiSuggestionEntityTree } from "./hui-suggestion-entity-tree";
|
||||
|
||||
@customElement("hui-suggestion-picker")
|
||||
export class HuiSuggestionPicker extends LitElement {
|
||||
@@ -39,14 +38,6 @@ export class HuiSuggestionPicker extends LitElement {
|
||||
|
||||
private _narrowMql?: MediaQueryList;
|
||||
|
||||
@query("hui-suggestion-entity-tree")
|
||||
private _entityTree?: HuiSuggestionEntityTree;
|
||||
|
||||
public async focus(): Promise<void> {
|
||||
await this.updateComplete;
|
||||
await this._entityTree?.focus();
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this._narrowMql = matchMedia("(max-width: 600px)");
|
||||
|
||||
@@ -30,7 +30,6 @@ import "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-tooltip";
|
||||
import "../../../../components/ha-yaml-editor";
|
||||
import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
@@ -231,28 +230,11 @@ export class HaCardConditionEditor extends LitElement {
|
||||
return html`
|
||||
<div class="container">
|
||||
<ha-expansion-panel left-chevron>
|
||||
<div
|
||||
id="condition-icon"
|
||||
class="icon-badge-wrapper"
|
||||
<ha-svg-icon
|
||||
slot="leading-icon"
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${ICON_CONDITION[condition.condition]}
|
||||
></ha-svg-icon>
|
||||
${hideLiveTest
|
||||
? nothing
|
||||
: html`<ha-automation-row-live-test
|
||||
.state=${this._liveTestResult.state}
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.lovelace.editor.condition-editor.live_test_state.${this._liveTestResult.state}`
|
||||
)}
|
||||
></ha-automation-row-live-test>`}
|
||||
</div>
|
||||
${!hideLiveTest && this._liveTestResult.message
|
||||
? html`<ha-tooltip for="condition-icon" slot="leading-icon"
|
||||
>${this._liveTestResult.message}</ha-tooltip
|
||||
>`
|
||||
: nothing}
|
||||
class="condition-icon"
|
||||
.path=${ICON_CONDITION[condition.condition]}
|
||||
></ha-svg-icon>
|
||||
<h3 slot="header">
|
||||
${this.hass.localize(
|
||||
`ui.panel.lovelace.editor.condition-editor.condition.${condition.condition}.label`
|
||||
@@ -273,6 +255,18 @@ export class HaCardConditionEditor extends LitElement {
|
||||
"ui.panel.lovelace.editor.condition-editor.testing_error"
|
||||
)}
|
||||
</ha-automation-row-event-chip>
|
||||
${hideLiveTest
|
||||
? nothing
|
||||
: html`
|
||||
<ha-automation-row-live-test
|
||||
slot="icons"
|
||||
.state=${this._liveTestResult.state}
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.lovelace.editor.condition-editor.live_test_state.${this._liveTestResult.state}`
|
||||
)}
|
||||
.message=${this._liveTestResult.message}
|
||||
></ha-automation-row-live-test>
|
||||
`}
|
||||
<ha-dropdown
|
||||
slot="icons"
|
||||
@wa-select=${this._handleAction}
|
||||
@@ -485,15 +479,17 @@ export class HaCardConditionEditor extends LitElement {
|
||||
--expansion-panel-summary-padding: 0 0 0 8px;
|
||||
--expansion-panel-content-padding: 0;
|
||||
}
|
||||
.icon-badge-wrapper {
|
||||
.condition-icon {
|
||||
display: none;
|
||||
}
|
||||
@media (min-width: 870px) {
|
||||
.icon-badge-wrapper {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
.condition-icon {
|
||||
display: inline-block;
|
||||
color: var(--secondary-text-color);
|
||||
opacity: 0.9;
|
||||
margin-right: 8px;
|
||||
margin-inline-end: 8px;
|
||||
margin-inline-start: initial;
|
||||
}
|
||||
}
|
||||
h3 {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
removeSearchParam,
|
||||
} from "../../common/url/search-params";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { deepEqual } from "../../common/util/deep-equal";
|
||||
import "../../components/ha-button";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import { subscribeLovelaceUpdates } from "../../data/lovelace";
|
||||
@@ -35,10 +36,7 @@ import { checkLovelaceConfig } from "./common/check-lovelace-config";
|
||||
import { loadLovelaceResources } from "./common/load-resources";
|
||||
import { showSaveDialog } from "./editor/show-save-config-dialog";
|
||||
import "./hui-root";
|
||||
import {
|
||||
checkStrategyShouldRegenerate,
|
||||
generateLovelaceDashboardStrategy,
|
||||
} from "./strategies/get-strategy";
|
||||
import { generateLovelaceDashboardStrategy } from "./strategies/get-strategy";
|
||||
import type { Lovelace } from "./types";
|
||||
import { generateDefaultView } from "./views/default-view";
|
||||
import { fetchDashboards } from "../../data/lovelace/dashboard";
|
||||
@@ -52,6 +50,12 @@ interface LovelacePanelConfig {
|
||||
let editorLoaded = false;
|
||||
let resourcesLoaded = false;
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"strategy-config-changed": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ha-panel-lovelace")
|
||||
export class LovelacePanel extends LitElement {
|
||||
@property({ attribute: false }) public panel?: PanelInfo<
|
||||
@@ -125,6 +129,7 @@ export class LovelacePanel extends LitElement {
|
||||
.route=${this.route}
|
||||
.narrow=${this.narrow}
|
||||
@config-refresh=${this._forceFetchConfig}
|
||||
@strategy-config-changed=${this._strategyConfigChanged}
|
||||
></hui-root>
|
||||
`;
|
||||
}
|
||||
@@ -190,26 +195,61 @@ export class LovelacePanel extends LitElement {
|
||||
this.lovelace &&
|
||||
isStrategyDashboard(this.lovelace.rawConfig)
|
||||
) {
|
||||
// If the entity registry changed, ask the user if they want to refresh the config
|
||||
if (
|
||||
oldHass.entities !== this.hass.entities ||
|
||||
oldHass.devices !== this.hass.devices ||
|
||||
oldHass.areas !== this.hass.areas ||
|
||||
oldHass.floors !== this.hass.floors
|
||||
) {
|
||||
if (this.hass.config.state === "RUNNING") {
|
||||
this._debounceRegistriesChanged();
|
||||
}
|
||||
}
|
||||
// If ha started, refresh the config
|
||||
if (
|
||||
this.hass.config.state === "RUNNING" &&
|
||||
(oldHass.config.state !== "RUNNING" ||
|
||||
checkStrategyShouldRegenerate(
|
||||
"dashboard",
|
||||
this.lovelace.rawConfig.strategy,
|
||||
oldHass,
|
||||
this.hass
|
||||
))
|
||||
oldHass.config.state !== "RUNNING"
|
||||
) {
|
||||
this._debounceRegenerateStrategy();
|
||||
this._regenerateStrategyConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _debounceRegenerateStrategy = debounce(
|
||||
() => this._regenerateStrategyConfig(),
|
||||
private _debounceRegistriesChanged = debounce(
|
||||
() => this._registriesChanged(),
|
||||
200
|
||||
);
|
||||
|
||||
private _registriesChanged = async () => {
|
||||
if (!this.hass || !this.lovelace) {
|
||||
return;
|
||||
}
|
||||
const rawConfig = this.lovelace.rawConfig;
|
||||
|
||||
if (!isStrategyDashboard(rawConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldConfig = this.lovelace.config;
|
||||
const generatedConfig = await generateLovelaceDashboardStrategy(
|
||||
rawConfig,
|
||||
this.hass!
|
||||
);
|
||||
|
||||
const newConfig = checkLovelaceConfig(generatedConfig) as LovelaceConfig;
|
||||
|
||||
// Regenerate if the config changed
|
||||
if (!deepEqual(newConfig, oldConfig)) {
|
||||
this._regenerateStrategyConfig();
|
||||
}
|
||||
};
|
||||
|
||||
private _strategyConfigChanged = (ev: CustomEvent) => {
|
||||
ev.stopPropagation();
|
||||
this._regenerateStrategyConfig();
|
||||
};
|
||||
|
||||
private async _regenerateStrategyConfig() {
|
||||
if (!this.hass || !this.lovelace) {
|
||||
return;
|
||||
|
||||
@@ -488,6 +488,7 @@ class HUIRoot extends LitElement {
|
||||
${this._editMode
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.edit_view.move_left"
|
||||
)}
|
||||
@@ -571,6 +572,7 @@ class HUIRoot extends LitElement {
|
||||
${isSubview || this.backButton
|
||||
? html`
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
slot="navigationIcon"
|
||||
@click=${this._goBack}
|
||||
></ha-icon-button-arrow-prev>
|
||||
|
||||
@@ -6,7 +6,6 @@ import { storage } from "../../../common/decorators/storage";
|
||||
import { deepEqual } from "../../../common/util/deep-equal";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import type { LovelaceSectionElement } from "../../../data/lovelace";
|
||||
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
@@ -26,10 +25,7 @@ import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog"
|
||||
import { addCard, replaceCard } from "../editor/config-util";
|
||||
import { performDeleteCard } from "../editor/delete-card";
|
||||
import { parseLovelaceCardPath } from "../editor/lovelace-path";
|
||||
import {
|
||||
checkStrategyShouldRegenerate,
|
||||
generateLovelaceSectionStrategy,
|
||||
} from "../strategies/get-strategy";
|
||||
import { generateLovelaceSectionStrategy } from "../strategies/get-strategy";
|
||||
import type { Lovelace } from "../types";
|
||||
import { DEFAULT_SECTION_LAYOUT } from "./const";
|
||||
|
||||
@@ -110,36 +106,9 @@ export class HuiSection extends ConditionalListenerMixin<LovelaceSectionConfig>(
|
||||
(!oldConfig || this.config !== oldConfig)
|
||||
) {
|
||||
this._initializeConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!changedProperties.has("hass")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHass = changedProperties.get("hass") as HomeAssistant | undefined;
|
||||
if (
|
||||
oldHass &&
|
||||
this.hass &&
|
||||
isStrategySection(this.config) &&
|
||||
this.hass.config.state === "RUNNING" &&
|
||||
(oldHass.config.state !== "RUNNING" ||
|
||||
checkStrategyShouldRegenerate(
|
||||
"section",
|
||||
this.config.strategy,
|
||||
oldHass,
|
||||
this.hass
|
||||
))
|
||||
) {
|
||||
this._debounceRefreshConfig();
|
||||
}
|
||||
}
|
||||
|
||||
private _debounceRefreshConfig = debounce(
|
||||
() => this._initializeConfig(),
|
||||
200
|
||||
);
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.removeEventListener(
|
||||
|
||||
@@ -6,7 +6,6 @@ import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
|
||||
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
|
||||
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { LovelaceStrategyDependency } from "../types";
|
||||
import {
|
||||
AREA_STRATEGY_GROUP_ICONS,
|
||||
computeAreaTileCardConfig,
|
||||
@@ -35,12 +34,6 @@ const computeHeadingCard = (
|
||||
|
||||
@customElement("area-view-strategy")
|
||||
export class AreaViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [
|
||||
"entities",
|
||||
"devices",
|
||||
"areas",
|
||||
];
|
||||
|
||||
static async generate(
|
||||
config: AreaViewStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -4,10 +4,7 @@ import { customElement } from "lit/decorators";
|
||||
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
|
||||
import type { LovelaceViewRawConfig } from "../../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type {
|
||||
LovelaceStrategyEditor,
|
||||
LovelaceStrategyDependency,
|
||||
} from "../types";
|
||||
import type { LovelaceStrategyEditor } from "../types";
|
||||
import type {
|
||||
AreaViewStrategyConfig,
|
||||
EntitiesDisplay,
|
||||
@@ -34,10 +31,6 @@ export interface AreasDashboardStrategyConfig {
|
||||
|
||||
@customElement("areas-dashboard-strategy")
|
||||
export class AreasDashboardStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [
|
||||
"areas",
|
||||
];
|
||||
|
||||
static async generate(
|
||||
config: AreasDashboardStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
type AreaControlDomain,
|
||||
} from "../../card-features/types";
|
||||
import type { AreaCardConfig, HeadingCardConfig } from "../../cards/types";
|
||||
import type { LovelaceStrategyDependency } from "../types";
|
||||
import type { EntitiesDisplay } from "./area-view-strategy";
|
||||
import {
|
||||
computeAreaPath,
|
||||
@@ -39,13 +38,6 @@ export interface AreasViewStrategyConfig {
|
||||
|
||||
@customElement("areas-overview-view-strategy")
|
||||
export class AreasOverviewViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [
|
||||
"entities",
|
||||
"devices",
|
||||
"areas",
|
||||
"floors",
|
||||
];
|
||||
|
||||
static async generate(
|
||||
config: AreasViewStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -23,20 +23,12 @@ import type {
|
||||
LovelaceDashboardStrategyGetCreateSuggestions,
|
||||
LovelaceSectionStrategy,
|
||||
LovelaceStrategy,
|
||||
LovelaceStrategyDependency,
|
||||
LovelaceViewStrategy,
|
||||
} from "./types";
|
||||
|
||||
const MAX_WAIT_STRATEGY_LOAD = 5000;
|
||||
const CUSTOM_PREFIX = "custom:";
|
||||
|
||||
const DEFAULT_REGISTRY_DEPENDENCIES: readonly LovelaceStrategyDependency[] = [
|
||||
"entities",
|
||||
"devices",
|
||||
"areas",
|
||||
"floors",
|
||||
];
|
||||
|
||||
const STRATEGIES: Record<LovelaceStrategyConfigType, Record<string, any>> = {
|
||||
dashboard: {
|
||||
"original-states": () =>
|
||||
@@ -90,47 +82,24 @@ type StrategyConfig<T extends LovelaceStrategyConfigType> = AsyncReturnType<
|
||||
Strategies[T]["generate"]
|
||||
>;
|
||||
|
||||
type StrategyTag =
|
||||
| { type: "builtin"; tag: string }
|
||||
| { type: "custom"; tag: string; legacyTag: string };
|
||||
|
||||
// Resolves the custom element tag(s) for a strategy. Custom strategies also
|
||||
// expose a legacy tag. `undefined` means the type is neither built-in nor a
|
||||
// custom strategy.
|
||||
const getStrategyTag = (
|
||||
configType: LovelaceStrategyConfigType,
|
||||
strategyType: string
|
||||
): StrategyTag | undefined => {
|
||||
if (strategyType in STRATEGIES[configType]) {
|
||||
return { type: "builtin", tag: `${strategyType}-${configType}-strategy` };
|
||||
}
|
||||
if (strategyType.startsWith(CUSTOM_PREFIX)) {
|
||||
const name = strategyType.slice(CUSTOM_PREFIX.length);
|
||||
return {
|
||||
type: "custom",
|
||||
tag: `ll-strategy-${configType}-${name}`,
|
||||
legacyTag: `ll-strategy-${name}`,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const getLovelaceStrategy = async <T extends LovelaceStrategyConfigType>(
|
||||
configType: T,
|
||||
strategyType: string
|
||||
): Promise<LovelaceStrategy> => {
|
||||
const tags = getStrategyTag(configType, strategyType);
|
||||
if (strategyType in STRATEGIES[configType]) {
|
||||
await STRATEGIES[configType][strategyType]();
|
||||
const tag = `${strategyType}-${configType}-strategy`;
|
||||
return customElements.get(tag) as unknown as Strategies[T];
|
||||
}
|
||||
|
||||
if (!tags) {
|
||||
if (!strategyType.startsWith(CUSTOM_PREFIX)) {
|
||||
throw new Error("Unknown strategy");
|
||||
}
|
||||
|
||||
if (tags.type === "builtin") {
|
||||
await STRATEGIES[configType][strategyType]();
|
||||
return customElements.get(tags.tag) as unknown as Strategies[T];
|
||||
}
|
||||
|
||||
const { tag, legacyTag } = tags;
|
||||
const legacyTag = `ll-strategy-${strategyType.slice(CUSTOM_PREFIX.length)}`;
|
||||
const tag = `ll-strategy-${configType}-${strategyType.slice(
|
||||
CUSTOM_PREFIX.length
|
||||
)}`;
|
||||
|
||||
if (
|
||||
(await Promise.race([
|
||||
@@ -272,41 +241,6 @@ export const generateLovelaceSectionStrategy = async (
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronously checks whether a strategy needs regeneration.
|
||||
* Strategies can implement `shouldRegenerate` for custom logic or declare
|
||||
* `registryDependencies` to opt in to the default reference-equality check.
|
||||
* The default list (entities, devices, areas, floors) is used when neither is
|
||||
* provided, preserving the previous behavior for third-party strategies.
|
||||
*/
|
||||
export const checkStrategyShouldRegenerate = (
|
||||
configType: LovelaceStrategyConfigType,
|
||||
strategyConfig: LovelaceStrategyConfig,
|
||||
oldHass: HomeAssistant,
|
||||
newHass: HomeAssistant
|
||||
): boolean => {
|
||||
const strategyType = strategyConfig.type;
|
||||
if (!strategyType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tags = getStrategyTag(configType, strategyType);
|
||||
const strategy = tags
|
||||
? ((customElements.get(tags.tag) ??
|
||||
(tags.type === "custom"
|
||||
? customElements.get(tags.legacyTag)
|
||||
: undefined)) as unknown as LovelaceStrategy | undefined)
|
||||
: undefined;
|
||||
|
||||
if (strategy?.shouldRegenerate) {
|
||||
return strategy.shouldRegenerate(strategyConfig, oldHass, newHass);
|
||||
}
|
||||
|
||||
const dependencies =
|
||||
strategy?.registryDependencies ?? DEFAULT_REGISTRY_DEPENDENCIES;
|
||||
return dependencies.some((key) => oldHass[key] !== newHass[key]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Find all references to strategies and replaces them with the generated output
|
||||
*/
|
||||
|
||||
@@ -17,7 +17,6 @@ import type {
|
||||
} from "../../cards/types";
|
||||
import type { ButtonHeadingBadgeConfig } from "../../heading-badges/types";
|
||||
import { computeAreaTileCardConfig } from "../areas/helpers/areas-strategy-helper";
|
||||
import type { LovelaceStrategyDependency } from "../types";
|
||||
import {
|
||||
getSummaryLabel,
|
||||
HOME_SUMMARIES,
|
||||
@@ -34,13 +33,6 @@ export interface HomeAreaViewStrategyConfig {
|
||||
|
||||
@customElement("home-area-view-strategy")
|
||||
export class HomeAreaViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [
|
||||
"entities",
|
||||
"devices",
|
||||
"areas",
|
||||
"panels",
|
||||
];
|
||||
|
||||
static async generate(
|
||||
config: HomeAreaViewStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -4,10 +4,7 @@ import { customElement } from "lit/decorators";
|
||||
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
|
||||
import type { LovelaceViewRawConfig } from "../../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type {
|
||||
LovelaceStrategyEditor,
|
||||
LovelaceStrategyDependency,
|
||||
} from "../types";
|
||||
import type { LovelaceStrategyEditor } from "../types";
|
||||
import {
|
||||
getSummaryLabel,
|
||||
HOME_SUMMARIES_ICONS,
|
||||
@@ -28,10 +25,6 @@ export interface HomeDashboardStrategyConfig {
|
||||
|
||||
@customElement("home-dashboard-strategy")
|
||||
export class HomeDashboardStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [
|
||||
"areas",
|
||||
];
|
||||
|
||||
static async generate(
|
||||
config: HomeDashboardStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -10,7 +10,6 @@ import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/
|
||||
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { MediaControlCardConfig } from "../../cards/types";
|
||||
import type { LovelaceStrategyDependency } from "../types";
|
||||
import { getAreasFloorHierarchy } from "../../../../common/areas/areas-floor-hierarchy";
|
||||
import { HOME_SUMMARIES_FILTERS } from "./helpers/home-summaries";
|
||||
|
||||
@@ -81,13 +80,6 @@ const processUnassignedEntities = (
|
||||
|
||||
@customElement("home-media-players-view-strategy")
|
||||
export class HomeMMediaPlayersViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [
|
||||
"entities",
|
||||
"devices",
|
||||
"areas",
|
||||
"floors",
|
||||
];
|
||||
|
||||
static async generate(
|
||||
_config: HomeMediaPlayersViewStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -15,7 +15,6 @@ import type {
|
||||
EntitiesCardConfig,
|
||||
HeadingCardConfig,
|
||||
} from "../../cards/types";
|
||||
import type { LovelaceStrategyDependency } from "../types";
|
||||
import { OTHER_DEVICES_FILTERS } from "./helpers/other-devices-filters";
|
||||
|
||||
export interface HomeOtherDevicesViewStrategyConfig {
|
||||
@@ -25,13 +24,6 @@ export interface HomeOtherDevicesViewStrategyConfig {
|
||||
|
||||
@customElement("home-other-devices-view-strategy")
|
||||
export class HomeOtherDevicesViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [
|
||||
"entities",
|
||||
"devices",
|
||||
"areas",
|
||||
"floors",
|
||||
];
|
||||
|
||||
static async generate(
|
||||
config: HomeOtherDevicesViewStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -35,7 +35,6 @@ import {
|
||||
LARGE_SCREEN_CONDITION,
|
||||
SMALL_SCREEN_CONDITION,
|
||||
} from "../helpers/view-columns-conditions";
|
||||
import type { LovelaceStrategyDependency } from "../types";
|
||||
import type { CommonControlsSectionStrategyConfig } from "../usage_prediction/common-controls-section-strategy";
|
||||
import { HOME_SUMMARIES_FILTERS } from "./helpers/home-summaries";
|
||||
import { OTHER_DEVICES_FILTERS } from "./helpers/other-devices-filters";
|
||||
@@ -80,14 +79,6 @@ const computeAreaCard = (
|
||||
|
||||
@customElement("home-overview-view-strategy")
|
||||
export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [
|
||||
"entities",
|
||||
"devices",
|
||||
"areas",
|
||||
"floors",
|
||||
"panels",
|
||||
];
|
||||
|
||||
static async generate(
|
||||
config: HomeOverviewViewStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
|
||||
import type {
|
||||
LovelaceStrategyEditor,
|
||||
LovelaceStrategyDependency,
|
||||
} from "../types";
|
||||
import type { LovelaceStrategyEditor } from "../types";
|
||||
import type { IframeViewStrategyConfig } from "./iframe-view-strategy";
|
||||
|
||||
export type IframeDashboardStrategyConfig = IframeViewStrategyConfig;
|
||||
|
||||
@customElement("iframe-dashboard-strategy")
|
||||
export class IframeDashboardStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [];
|
||||
|
||||
static async generate(
|
||||
config: IframeDashboardStrategyConfig
|
||||
): Promise<LovelaceConfig> {
|
||||
|
||||
@@ -2,7 +2,6 @@ import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
|
||||
import type { IframeCardConfig } from "../../cards/types";
|
||||
import type { LovelaceStrategyDependency } from "../types";
|
||||
|
||||
export interface IframeViewStrategyConfig {
|
||||
type: "iframe";
|
||||
@@ -12,8 +11,6 @@ export interface IframeViewStrategyConfig {
|
||||
|
||||
@customElement("iframe-view-strategy")
|
||||
export class IframeViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [];
|
||||
|
||||
static async generate(
|
||||
config: IframeViewStrategyConfig
|
||||
): Promise<LovelaceViewConfig> {
|
||||
|
||||
@@ -3,15 +3,12 @@ import { customElement } from "lit/decorators";
|
||||
import type { LovelaceDashboardSuggestions } from "../../../../data/lovelace/dashboard";
|
||||
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { LovelaceStrategyDependency } from "../types";
|
||||
import type { MapViewStrategyConfig } from "./map-view-strategy";
|
||||
|
||||
export type MapDashboardStrategyConfig = MapViewStrategyConfig;
|
||||
|
||||
@customElement("map-dashboard-strategy")
|
||||
export class MapDashboardStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [];
|
||||
|
||||
static async generate(
|
||||
config: MapDashboardStrategyConfig
|
||||
): Promise<LovelaceConfig> {
|
||||
|
||||
@@ -3,7 +3,6 @@ import { customElement } from "lit/decorators";
|
||||
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { MapCardConfig } from "../../cards/types";
|
||||
import type { LovelaceStrategyDependency } from "../types";
|
||||
|
||||
export interface MapViewStrategyConfig {
|
||||
type: "map";
|
||||
@@ -11,8 +10,6 @@ export interface MapViewStrategyConfig {
|
||||
|
||||
@customElement("map-view-strategy")
|
||||
export class MapViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [];
|
||||
|
||||
static async generate(
|
||||
_config: MapViewStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
+1
-6
@@ -1,10 +1,7 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
|
||||
import type {
|
||||
LovelaceStrategyEditor,
|
||||
LovelaceStrategyDependency,
|
||||
} from "../types";
|
||||
import type { LovelaceStrategyEditor } from "../types";
|
||||
import type { OriginalStatesViewStrategyConfig } from "./original-states-view-strategy";
|
||||
|
||||
export type OriginalStatesDashboardStrategyConfig =
|
||||
@@ -12,8 +9,6 @@ export type OriginalStatesDashboardStrategyConfig =
|
||||
|
||||
@customElement("original-states-dashboard-strategy")
|
||||
export class OriginalStatesDashboardStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [];
|
||||
|
||||
static async generate(
|
||||
config: OriginalStatesDashboardStrategyConfig
|
||||
): Promise<LovelaceConfig> {
|
||||
|
||||
@@ -9,7 +9,6 @@ import type { HomeAssistant } from "../../../../types";
|
||||
import type { EmptyStateCardConfig } from "../../cards/types";
|
||||
import { generateDefaultViewConfig } from "../../common/generate-lovelace-config";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
import type { LovelaceStrategyDependency } from "../types";
|
||||
|
||||
export interface OriginalStatesViewStrategyConfig {
|
||||
type: "original-states";
|
||||
@@ -20,13 +19,6 @@ export interface OriginalStatesViewStrategyConfig {
|
||||
|
||||
@customElement("original-states-view-strategy")
|
||||
export class OriginalStatesViewStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [
|
||||
"entities",
|
||||
"devices",
|
||||
"areas",
|
||||
"floors",
|
||||
];
|
||||
|
||||
static async generate(
|
||||
config: OriginalStatesViewStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -6,22 +6,8 @@ import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceGenericElementEditor } from "../types";
|
||||
|
||||
export type LovelaceStrategyDependency =
|
||||
| "entities"
|
||||
| "devices"
|
||||
| "areas"
|
||||
| "floors"
|
||||
| "labels"
|
||||
| "panels";
|
||||
|
||||
export interface LovelaceStrategy<T = any> {
|
||||
generate(config: LovelaceStrategyConfig, hass: HomeAssistant): Promise<T>;
|
||||
shouldRegenerate?(
|
||||
config: LovelaceStrategyConfig,
|
||||
oldHass: HomeAssistant,
|
||||
newHass: HomeAssistant
|
||||
): boolean;
|
||||
registryDependencies?: readonly LovelaceStrategyDependency[];
|
||||
getConfigElement?: () => LovelaceStrategyEditor;
|
||||
noEditor?: boolean;
|
||||
configRequired?: boolean;
|
||||
|
||||
@@ -6,7 +6,6 @@ import { getCommonControlsUsagePrediction } from "../../../../data/usage_predict
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { HeadingCardConfig, TileCardConfig } from "../../cards/types";
|
||||
import type { Condition } from "../../common/validate-condition";
|
||||
import type { LovelaceStrategyDependency } from "../types";
|
||||
|
||||
const DEFAULT_LIMIT = 8;
|
||||
|
||||
@@ -34,8 +33,6 @@ const toTileCard = (entity: string): TileCardConfig => ({
|
||||
|
||||
@customElement("common-controls-section-strategy")
|
||||
export class CommonControlsSectionStrategy extends ReactiveElement {
|
||||
static registryDependencies: readonly LovelaceStrategyDependency[] = [];
|
||||
|
||||
static async generate(
|
||||
config: CommonControlsSectionStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
|
||||
@@ -42,10 +42,7 @@ import { parseLovelaceCardPath } from "../editor/lovelace-path";
|
||||
import { createErrorSectionConfig } from "../sections/hui-error-section";
|
||||
import "../sections/hui-section";
|
||||
import type { HuiSection } from "../sections/hui-section";
|
||||
import {
|
||||
checkStrategyShouldRegenerate,
|
||||
generateLovelaceViewStrategy,
|
||||
} from "../strategies/get-strategy";
|
||||
import { generateLovelaceViewStrategy } from "../strategies/get-strategy";
|
||||
import type { Lovelace } from "../types";
|
||||
import { getViewType } from "./get-view-type";
|
||||
|
||||
@@ -188,13 +185,10 @@ export class HUIView extends ReactiveElement {
|
||||
if (oldHass && this.hass && this.lovelace && isStrategyView(viewConfig)) {
|
||||
if (
|
||||
this.hass.config.state === "RUNNING" &&
|
||||
(oldHass.config.state !== "RUNNING" ||
|
||||
checkStrategyShouldRegenerate(
|
||||
"view",
|
||||
viewConfig.strategy,
|
||||
oldHass,
|
||||
this.hass
|
||||
))
|
||||
(oldHass.entities !== this.hass.entities ||
|
||||
oldHass.devices !== this.hass.devices ||
|
||||
oldHass.areas !== this.hass.areas ||
|
||||
oldHass.floors !== this.hass.floors)
|
||||
) {
|
||||
this._debounceRefreshConfig();
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ class HaProfileSectionGeneral extends LitElement {
|
||||
<hass-tabs-subpage
|
||||
main-page
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.tabs=${profileSections}
|
||||
.route=${this.route}
|
||||
>
|
||||
|
||||
@@ -15,6 +15,8 @@ import "./ha-refresh-tokens-card";
|
||||
class HaProfileSectionSecurity extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@state() private _refreshTokens?: RefreshToken[];
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
@@ -35,6 +37,7 @@ class HaProfileSectionSecurity extends LitElement {
|
||||
<hass-tabs-subpage
|
||||
main-page
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.tabs=${profileSections}
|
||||
.route=${this.route}
|
||||
>
|
||||
|
||||
+10
-11
@@ -1693,17 +1693,14 @@
|
||||
"last_triggered": "Last triggered"
|
||||
},
|
||||
"add_to": {
|
||||
"title": "Add {target} to…",
|
||||
"item": "Add to…",
|
||||
"action_options": {
|
||||
"automation_trigger": "Create as a new trigger",
|
||||
"automation_condition": "Create as a new condition",
|
||||
"automation_action": "Create as a new action",
|
||||
"script_action": "Create as a new action",
|
||||
"scene": "Create as a new scene"
|
||||
"title": "Add to",
|
||||
"actions": {
|
||||
"automation_trigger": "Create new automation using {target} as a trigger",
|
||||
"automation_condition": "Create new automation using {target} as a condition",
|
||||
"automation_action": "Create new automation using {target} in an action",
|
||||
"script_action": "Create new script using {target} in an action",
|
||||
"scene": "Create new scene using {target}"
|
||||
},
|
||||
"automations_heading": "Automations",
|
||||
"scripts_heading": "Scripts",
|
||||
"app_actions": "App actions",
|
||||
"no_actions": "No actions available",
|
||||
"action_failed": "Failed to perform the action {error}"
|
||||
@@ -4243,6 +4240,8 @@
|
||||
"display_name": "[%key:ui::panel::config::energy::device_consumption::dialog::display_name%]",
|
||||
"state_of_charge": "Battery state of charge sensor",
|
||||
"state_of_charge_helper": "Sensor reporting battery state of charge as %.",
|
||||
"capacity": "Battery capacity",
|
||||
"capacity_helper": "Usable battery capacity in kWh. Used to weight the combined state of charge when you have multiple batteries of different sizes.",
|
||||
"power": "Battery power",
|
||||
"power_helper": "Pick a sensor which measures the electricity flowing into and out of the battery in either of {unit}. Positive values indicate discharging the battery, negative values indicate charging the battery.",
|
||||
"sensor_type": "Type of power measurement",
|
||||
@@ -11213,7 +11212,7 @@
|
||||
},
|
||||
"landing-page": {
|
||||
"header": "Preparing Home Assistant",
|
||||
"subheader": "The latest version of Home Assistant is being downloaded. This may take 20 minutes or more.",
|
||||
"subheader": "This may take 20 minutes or more",
|
||||
"show_details": "Show details",
|
||||
"hide_details": "Hide details",
|
||||
"network_issue": {
|
||||
|
||||
@@ -4041,161 +4041,161 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/basic@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/basic@npm:4.1.1"
|
||||
"@tsparticles/basic@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/basic@npm:4.1.0"
|
||||
dependencies:
|
||||
"@tsparticles/engine": "npm:4.1.1"
|
||||
"@tsparticles/plugin-blend": "npm:4.1.1"
|
||||
"@tsparticles/plugin-hex-color": "npm:4.1.1"
|
||||
"@tsparticles/plugin-hsl-color": "npm:4.1.1"
|
||||
"@tsparticles/plugin-move": "npm:4.1.1"
|
||||
"@tsparticles/plugin-rgb-color": "npm:4.1.1"
|
||||
"@tsparticles/shape-circle": "npm:4.1.1"
|
||||
"@tsparticles/updater-opacity": "npm:4.1.1"
|
||||
"@tsparticles/updater-out-modes": "npm:4.1.1"
|
||||
"@tsparticles/updater-paint": "npm:4.1.1"
|
||||
"@tsparticles/updater-size": "npm:4.1.1"
|
||||
checksum: 10/99191aeee4b9a3856aa82a2cea21e54d2099b6d58b4af3bacf4bb133277bd71de16ac07fe632ebabe08cc2a06be1aa6c00d82d593027276bf588870afc9182f5
|
||||
"@tsparticles/engine": "npm:4.1.0"
|
||||
"@tsparticles/plugin-blend": "npm:4.1.0"
|
||||
"@tsparticles/plugin-hex-color": "npm:4.1.0"
|
||||
"@tsparticles/plugin-hsl-color": "npm:4.1.0"
|
||||
"@tsparticles/plugin-move": "npm:4.1.0"
|
||||
"@tsparticles/plugin-rgb-color": "npm:4.1.0"
|
||||
"@tsparticles/shape-circle": "npm:4.1.0"
|
||||
"@tsparticles/updater-opacity": "npm:4.1.0"
|
||||
"@tsparticles/updater-out-modes": "npm:4.1.0"
|
||||
"@tsparticles/updater-paint": "npm:4.1.0"
|
||||
"@tsparticles/updater-size": "npm:4.1.0"
|
||||
checksum: 10/aae6ebde0377e9f5b2a150cc94c7e140012d61d33ce0b2ab135e2513a106dad923f4704db67e29f912374600dc474c206697a06d846173141e4830f89cff2661
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/canvas-utils@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/canvas-utils@npm:4.1.1"
|
||||
"@tsparticles/canvas-utils@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/canvas-utils@npm:4.1.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/2b5d7e9a55aa8086007f2dff940800c650233751ebb708fdbf9f91be4b0cd9d975400da42cdf531bb74860974dff5ea3c76199520ccd3f089904fba0d1bc0722
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/bdde613b64665756080e9937bd4013a4e026e23baea1e536a9ae6785ff1b14bef5bbebca4eed6a10404cffcc375737fc101cb27f068146b983a34b7967a1f729
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/engine@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/engine@npm:4.1.1"
|
||||
checksum: 10/74886de63046f8752515f097176cae2fa8d506fd9d2d6a84106d43a89ff688e047d99a5018e56e3fffc17d5d13a9061f63fafcb2b4ebe773f78379621d0d9855
|
||||
"@tsparticles/engine@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/engine@npm:4.1.0"
|
||||
checksum: 10/9b8fc1e8f6ae67541d8c230996af9c27e1da04ba2d8d6f9daf5de4f321a523dfc968a0fab033708c471a9cde6ea0f1c5634cbb5ef3a3d27ef4b182dc193212f1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/interaction-particles-links@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/interaction-particles-links@npm:4.1.1"
|
||||
"@tsparticles/interaction-particles-links@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/interaction-particles-links@npm:4.1.0"
|
||||
dependencies:
|
||||
"@tsparticles/canvas-utils": "npm:4.1.1"
|
||||
"@tsparticles/canvas-utils": "npm:4.1.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.1
|
||||
"@tsparticles/plugin-interactivity": 4.1.1
|
||||
checksum: 10/c0c7ad3740f2168b75ca7ae09341fefcc1d8c8d117fbee7a6519a622843dfe5d57bb65113a4eba40f26906904f069cbac196840f45fc7be12864fddfa8106184
|
||||
"@tsparticles/engine": 4.1.0
|
||||
"@tsparticles/plugin-interactivity": 4.1.0
|
||||
checksum: 10/2f74b25a1e585e6427034e38daba851880e2ae342da4558e3a9910bd37e0fb812438e19252ce11807dd0ed8bd53c3a5e48329b04450eac672310d1e058f720db
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-blend@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/plugin-blend@npm:4.1.1"
|
||||
"@tsparticles/plugin-blend@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/plugin-blend@npm:4.1.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/9a666dc1fe0ed9ff4743fc23ca3bf27bd5d66073cc0127203b566077d431321b61af440875e96c87950e18011b1e6b9b43d328e4200923dd4be9fd934c07a5cd
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/e1913556ada7d70bbcd97b9591315fb15dc29edad3e31f50ec26639d225a15b5c237108a06173796f8a8bbb87e5f3f37ed40f3dece77f86bee4bd274600f813e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-hex-color@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/plugin-hex-color@npm:4.1.1"
|
||||
"@tsparticles/plugin-hex-color@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/plugin-hex-color@npm:4.1.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/33fde7c2763affb1dd7fa033431a2553646bde79c9ab52106be6226a9716ae973c90c8b4bf381d96f661ab75467934019cd456fe27ccac59dca11b5c989c3a75
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/611992f350170e97daff323cecfa5f56625fbce0033d94dce5e579a5433ce9c46b1ceeb6381a714b58296fbd2802be806b8505d950af881f02e02ef8aac32f30
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-hsl-color@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/plugin-hsl-color@npm:4.1.1"
|
||||
"@tsparticles/plugin-hsl-color@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/plugin-hsl-color@npm:4.1.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/594f4a840f6f6f134a11d76c1a7fa80ed60a5d945534aad53e8ab5718341187f0fac73eb45d1c39246b98e982f1d60f9dbb95a083edcf8407e015048056b84e7
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/6e26d48733df47f5a91315b0144485440530fb5a6250b02254481157f05355ec59ded1fbfff6463d4dac40cb594a5ded660606a07b9376ce5f083eb92ff9aeac
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-interactivity@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/plugin-interactivity@npm:4.1.1"
|
||||
"@tsparticles/plugin-interactivity@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/plugin-interactivity@npm:4.1.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/448bd8f7c741ed0359ace49a6c58d58947d95660158a131c86c248427b9caa41645c6e8c9cea6a8f0f6a70cd25c0dd64017edba79c8f225b6db07f39b660eb4d
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/7fe688b0531395e4f2e4f103bdd490471fa3e65c12ea98ac420f5380d5b00d460b99407ada19f3c2f1dbcedaffe061c399e43446cbd684e9e4df5c112e08baee
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-move@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/plugin-move@npm:4.1.1"
|
||||
"@tsparticles/plugin-move@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/plugin-move@npm:4.1.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/b854cb6e2dcea2971f1abaca75c6e8cde40611178f13513559f60cc45da568e8b61c2f027619041d94e11c60af08a76eb4957d344a910b7a6255265937199094
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/97dc5d4ecde770c1d7fc7b3bb2dc5075eb44405e2c541ab6a64e07c14a6aebf038ad4bf0dd7583606c63908a9e58b92a140acc9e437862376975d8c5b52dfc94
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/plugin-rgb-color@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/plugin-rgb-color@npm:4.1.1"
|
||||
"@tsparticles/plugin-rgb-color@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/plugin-rgb-color@npm:4.1.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/2372dfe5ceec163b49c02b3b0169e6032fae34e4ade3c29d42bd26af23fac76b3416d70eed9cac9a0f2b470c763ee903d12d0e9156b7693b3c8908e25714cf76
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/1635482f8aa8664779fd30eaaab3cfd6d7ac53b2f5dafbbefd26a9151610eb40326c5240ed5439cb3f5e426a7111e80441554fd8a9848b8d6fe5176cb243ed0e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/preset-links@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/preset-links@npm:4.1.1"
|
||||
"@tsparticles/preset-links@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/preset-links@npm:4.1.0"
|
||||
dependencies:
|
||||
"@tsparticles/basic": "npm:4.1.1"
|
||||
"@tsparticles/engine": "npm:4.1.1"
|
||||
"@tsparticles/interaction-particles-links": "npm:4.1.1"
|
||||
"@tsparticles/plugin-interactivity": "npm:4.1.1"
|
||||
checksum: 10/26dd1dcd4ede3bae32cae3585a7f929e0089a75e389f83cb5b00213e977b647c3e7e743b5e83d3a5400beffcff9343bde7538412579708bad761d2e933708638
|
||||
"@tsparticles/basic": "npm:4.1.0"
|
||||
"@tsparticles/engine": "npm:4.1.0"
|
||||
"@tsparticles/interaction-particles-links": "npm:4.1.0"
|
||||
"@tsparticles/plugin-interactivity": "npm:4.1.0"
|
||||
checksum: 10/12e7da30de505a0f10f8caeafebdd6393e998cd3159d1b478cf28720a14f4eadd28ee82630eb08e10962987e8da3114f638dc500994fb45cff8d3c64e75433fb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/shape-circle@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/shape-circle@npm:4.1.1"
|
||||
"@tsparticles/shape-circle@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/shape-circle@npm:4.1.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/401e8a267cf9301dae8386e9bb1c5ff3a02dfb5b5f136187c73ed6b89e33f176f929fdc53acaa60b13f3ee1ed7f8c6f35f3f0e26ff6930a33d1949a0f23e4c1f
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/eff8a96c1816e183079b93b5734f7055cf85c4cbc42cc1cdd759ec114303d7c532299827bee6754644387b4894a78b9fb593551a3ef86e39f49eaef943ce856b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-opacity@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/updater-opacity@npm:4.1.1"
|
||||
"@tsparticles/updater-opacity@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/updater-opacity@npm:4.1.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/3fe157203e02dfec1ef9ac693d517cd9abcad8c28acaac8d4ca923b8301a99ae73129ed4559ea507c0d7d6ad626bdbbdb03f25868d7d28f852e2d8ffc7d13790
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/67394b4e63017db0ca758025ec2283c46ad09689310b6872d486395db77f8432de34a1a145547b8248a279d4becbda8d586684d44ddce8917ef3ac8b4f15a383
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-out-modes@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/updater-out-modes@npm:4.1.1"
|
||||
"@tsparticles/updater-out-modes@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/updater-out-modes@npm:4.1.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/bc5f074661c42acc20a87ee3ff093fe63d2fd87a7f7d3156c2fb23dc1d24881ccc9e9cb82313ff7265317fcdc447a5b17b2204930faf8ba627278e2dc0fab2c3
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/e308dbf854d65eb87fadd43c80e2c2d578399ea2e446327f2b5956a57d5c135e258251a4bdeaceace44d169abf326eabd8f7c4bcfcb027dcde2942b2a28c9ea4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-paint@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/updater-paint@npm:4.1.1"
|
||||
"@tsparticles/updater-paint@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/updater-paint@npm:4.1.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/77459f6337b869d654573696dbe2bc1d238ddf3410857d8b91df606f7402301acae34f4a334727faa552f9816f518353b7a98e0bda4854ea5419cb34e8d447d2
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/212e389e9e728a2d69a706bc606d6f6e6912ebd562fe4428236e1b72af3b49cf19043a4b72a26091230ab3ec128ed99cd05a8e6b70ad8cff02413d3dcdaabb6d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tsparticles/updater-size@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@tsparticles/updater-size@npm:4.1.1"
|
||||
"@tsparticles/updater-size@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@tsparticles/updater-size@npm:4.1.0"
|
||||
peerDependencies:
|
||||
"@tsparticles/engine": 4.1.1
|
||||
checksum: 10/33ae9d51394e299459478a9dedd369de1bd266ad9c2e2236ec2c20bb455aad9118efa13afc7d359b6d7d010a34bba5b60cbf2e3d8b853e124c0b53c2aae8db18
|
||||
"@tsparticles/engine": 4.1.0
|
||||
checksum: 10/8e513044cbc4fa84e00008a77d0069cdebcf5efed0414aad5ad2a09497226182fac9d85037594853468d0ef2ae210616852fecf35abef44d89edf24fedf87c51
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -7957,10 +7957,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fuse.js@npm:7.4.0":
|
||||
version: 7.4.0
|
||||
resolution: "fuse.js@npm:7.4.0"
|
||||
checksum: 10/dba0ef239be1f28ba5daefb3a17371c73291f4d0db3d1733b625848a7311e05aa58a795cd5b2fd9626c09857608a74e8c9620f5d1ce2d3d0b2d40155ae15e21e
|
||||
"fuse.js@npm:7.3.0":
|
||||
version: 7.3.0
|
||||
resolution: "fuse.js@npm:7.3.0"
|
||||
checksum: 10/b2cdc39e46acb9524fe900356af74c987ecb1dbc67412df651a5291fa072d212e6d74457f0b5d1c39baf79539481f62b613ac792afd8995dd1fc3d20b5354914
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8487,8 +8487,8 @@ __metadata:
|
||||
"@rspack/dev-server": "npm:2.0.3"
|
||||
"@swc/helpers": "npm:0.5.23"
|
||||
"@thomasloven/round-slider": "npm:0.6.0"
|
||||
"@tsparticles/engine": "npm:4.1.1"
|
||||
"@tsparticles/preset-links": "npm:4.1.1"
|
||||
"@tsparticles/engine": "npm:4.1.0"
|
||||
"@tsparticles/preset-links": "npm:4.1.0"
|
||||
"@types/chromecast-caf-receiver": "npm:6.0.26"
|
||||
"@types/chromecast-caf-sender": "npm:1.0.11"
|
||||
"@types/color-name": "npm:2.0.0"
|
||||
@@ -8534,7 +8534,7 @@ __metadata:
|
||||
eslint-plugin-wc: "npm:3.1.0"
|
||||
fancy-log: "npm:2.0.0"
|
||||
fs-extra: "npm:11.3.5"
|
||||
fuse.js: "npm:7.4.0"
|
||||
fuse.js: "npm:7.3.0"
|
||||
generate-license-file: "npm:4.2.1"
|
||||
glob: "npm:13.0.6"
|
||||
globals: "npm:17.6.0"
|
||||
@@ -8557,7 +8557,7 @@ __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"
|
||||
license-checker-rseidelsohn: "npm:5.0.1"
|
||||
lint-staged: "npm:17.0.7"
|
||||
lint-staged: "npm:17.0.5"
|
||||
lit: "npm:3.3.3"
|
||||
lit-analyzer: "npm:2.0.3"
|
||||
lit-html: "npm:3.3.3"
|
||||
@@ -9936,21 +9936,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lint-staged@npm:17.0.7":
|
||||
version: 17.0.7
|
||||
resolution: "lint-staged@npm:17.0.7"
|
||||
"lint-staged@npm:17.0.5":
|
||||
version: 17.0.5
|
||||
resolution: "lint-staged@npm:17.0.5"
|
||||
dependencies:
|
||||
listr2: "npm:^10.2.1"
|
||||
picomatch: "npm:^4.0.4"
|
||||
string-argv: "npm:^0.3.2"
|
||||
tinyexec: "npm:^1.2.4"
|
||||
yaml: "npm:^2.9.0"
|
||||
tinyexec: "npm:^1.1.2"
|
||||
yaml: "npm:^2.8.4"
|
||||
dependenciesMeta:
|
||||
yaml:
|
||||
optional: true
|
||||
bin:
|
||||
lint-staged: bin/lint-staged.js
|
||||
checksum: 10/4ed3cd01caa78ff5cc5da7ec69f77f091c43a0d5cbb1e084f7ffd3872a9e599675fb8b5f11fd5911faee0d330952889dd0e14378a26620d8f529eae401ce49b4
|
||||
checksum: 10/a0bea43689d68ec0bf6a56943884dbdb96b6b49e2677bf80654d802678b2edf9fc65338ca8ef3fc310f245933ea2a809db1ac94431dc445c57a4d49620d9d4da
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -13172,10 +13172,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tinyexec@npm:^1.0.2, tinyexec@npm:^1.2.4":
|
||||
version: 1.2.4
|
||||
resolution: "tinyexec@npm:1.2.4"
|
||||
checksum: 10/f20b3e6f56f24c3ebe0129d0b6e657e561d225df2cf93c1a10362996232dd6ad4b8af8c9e81d258a64d09020e723772baf6fe0be26512dba7c61bb366d67b1f9
|
||||
"tinyexec@npm:^1.0.2, tinyexec@npm:^1.1.2":
|
||||
version: 1.1.2
|
||||
resolution: "tinyexec@npm:1.1.2"
|
||||
checksum: 10/2bbe37f9001c6f5723ab39eb8dc1e88f77e830d7cf2e8f34bb75019eb505fcfe3b061b4799c502ff31fa63aa1a9adc649add5ff1e17b7fbd8c16e1afb75d0b9e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -14755,7 +14755,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yaml@npm:^2.9.0":
|
||||
"yaml@npm:^2.8.4":
|
||||
version: 2.9.0
|
||||
resolution: "yaml@npm:2.9.0"
|
||||
bin:
|
||||
|
||||
Reference in New Issue
Block a user