mirror of
https://github.com/home-assistant/frontend.git
synced 2026-05-26 19:17:03 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 854e56947f |
@@ -41,14 +41,14 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
uses: github/codeql-action/autobuild@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -62,4 +62,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
|
||||
+248
-248
File diff suppressed because one or more lines are too long
+1
-1
@@ -13,4 +13,4 @@ nodeLinker: node-modules
|
||||
|
||||
npmMinimalAgeGate: 3d
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.15.0.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.14.1.cjs
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
[build.environment]
|
||||
YARN_VERSION = "1.22.11"
|
||||
NODE_OPTIONS = "--max_old_space_size=6144"
|
||||
|
||||
+28
-27
@@ -38,30 +38,31 @@
|
||||
"@codemirror/search": "6.7.0",
|
||||
"@codemirror/state": "6.6.0",
|
||||
"@codemirror/view": "6.43.0",
|
||||
"@date-fns/tz": "1.5.0",
|
||||
"@date-fns/tz": "1.4.1",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "7.4.6",
|
||||
"@formatjs/intl-displaynames": "7.3.8",
|
||||
"@formatjs/intl-durationformat": "0.10.12",
|
||||
"@formatjs/intl-getcanonicallocales": "3.2.9",
|
||||
"@formatjs/intl-listformat": "8.3.8",
|
||||
"@formatjs/intl-locale": "5.3.8",
|
||||
"@formatjs/intl-numberformat": "9.3.9",
|
||||
"@formatjs/intl-pluralrules": "6.3.8",
|
||||
"@formatjs/intl-relativetimeformat": "12.3.8",
|
||||
"@formatjs/intl-datetimeformat": "7.4.5",
|
||||
"@formatjs/intl-displaynames": "7.3.7",
|
||||
"@formatjs/intl-durationformat": "0.10.11",
|
||||
"@formatjs/intl-getcanonicallocales": "3.2.8",
|
||||
"@formatjs/intl-listformat": "8.3.7",
|
||||
"@formatjs/intl-locale": "5.3.7",
|
||||
"@formatjs/intl-numberformat": "9.3.8",
|
||||
"@formatjs/intl-pluralrules": "6.3.7",
|
||||
"@formatjs/intl-relativetimeformat": "12.3.7",
|
||||
"@fullcalendar/core": "6.1.20",
|
||||
"@fullcalendar/daygrid": "6.1.20",
|
||||
"@fullcalendar/interaction": "6.1.20",
|
||||
"@fullcalendar/list": "6.1.20",
|
||||
"@fullcalendar/luxon3": "6.1.20",
|
||||
"@fullcalendar/timegrid": "6.1.20",
|
||||
"@home-assistant/webawesome": "3.7.0-ha.0",
|
||||
"@home-assistant/webawesome": "3.3.1-ha.3",
|
||||
"@lezer/highlight": "1.2.3",
|
||||
"@lit-labs/motion": "1.1.0",
|
||||
"@lit-labs/observers": "2.1.0",
|
||||
"@lit-labs/virtualizer": "2.1.1",
|
||||
"@lit/context": "1.1.6",
|
||||
"@lit/reactive-element": "2.1.2",
|
||||
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/mwc-base": "0.27.0",
|
||||
"@material/mwc-formfield": "patch:@material/mwc-formfield@npm%3A0.27.0#~/.yarn/patches/@material-mwc-formfield-npm-0.27.0-9528cb60f6.patch",
|
||||
"@material/mwc-list": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
|
||||
@@ -74,8 +75,8 @@
|
||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||
"@swc/helpers": "0.5.21",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@tsparticles/engine": "4.0.5",
|
||||
"@tsparticles/preset-links": "4.0.5",
|
||||
"@tsparticles/engine": "4.0.0",
|
||||
"@tsparticles/preset-links": "4.0.0",
|
||||
"@vibrant/color": "4.0.4",
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||
@@ -86,7 +87,7 @@
|
||||
"core-js": "3.49.0",
|
||||
"cropperjs": "1.6.2",
|
||||
"culori": "4.0.2",
|
||||
"date-fns": "4.3.0",
|
||||
"date-fns": "4.1.0",
|
||||
"deep-clone-simple": "1.1.1",
|
||||
"deep-freeze": "0.0.1",
|
||||
"dialog-polyfill": "0.5.6",
|
||||
@@ -97,8 +98,8 @@
|
||||
"gulp-zopfli-green": "7.0.0",
|
||||
"hls.js": "1.6.16",
|
||||
"home-assistant-js-websocket": "9.6.0",
|
||||
"idb-keyval": "6.2.4",
|
||||
"intl-messageformat": "11.2.7",
|
||||
"idb-keyval": "6.2.2",
|
||||
"intl-messageformat": "11.2.6",
|
||||
"js-yaml": "4.1.1",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
|
||||
@@ -106,7 +107,7 @@
|
||||
"lit": "3.3.3",
|
||||
"lit-html": "3.3.3",
|
||||
"luxon": "3.7.2",
|
||||
"marked": "18.0.4",
|
||||
"marked": "18.0.3",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "4.0.4",
|
||||
"object-hash": "3.0.0",
|
||||
@@ -118,7 +119,7 @@
|
||||
"sortablejs": "patch:sortablejs@npm%3A1.15.6#~/.yarn/patches/sortablejs-npm-1.15.6-3235a8f83b.patch",
|
||||
"stacktrace-js": "2.0.2",
|
||||
"superstruct": "2.0.2",
|
||||
"tinykeys": "4.0.0",
|
||||
"tinykeys": "3.0.0",
|
||||
"weekstart": "2.0.0",
|
||||
"workbox-cacheable-response": "7.4.1",
|
||||
"workbox-core": "7.4.1",
|
||||
@@ -141,7 +142,7 @@
|
||||
"@octokit/plugin-retry": "8.1.0",
|
||||
"@octokit/rest": "22.0.1",
|
||||
"@rsdoctor/rspack-plugin": "1.5.11",
|
||||
"@rspack/core": "2.0.4",
|
||||
"@rspack/core": "2.0.3",
|
||||
"@rspack/dev-server": "2.0.1",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.26",
|
||||
@@ -160,12 +161,12 @@
|
||||
"@types/sortablejs": "1.15.9",
|
||||
"@types/tar": "7.0.87",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@vitest/coverage-v8": "4.1.7",
|
||||
"@vitest/coverage-v8": "4.1.6",
|
||||
"babel-loader": "10.1.1",
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"browserslist-useragent-regexp": "4.1.4",
|
||||
"del": "8.0.1",
|
||||
"eslint": "10.4.0",
|
||||
"eslint": "10.3.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-import-resolver-webpack": "0.13.11",
|
||||
"eslint-plugin-import-x": "4.16.2",
|
||||
@@ -175,7 +176,7 @@
|
||||
"eslint-plugin-wc": "3.1.0",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.3.5",
|
||||
"generate-license-file": "4.2.1",
|
||||
"generate-license-file": "4.1.1",
|
||||
"glob": "13.0.6",
|
||||
"globals": "17.6.0",
|
||||
"gulp": "5.0.1",
|
||||
@@ -187,7 +188,7 @@
|
||||
"jsdom": "29.1.1",
|
||||
"jszip": "3.10.1",
|
||||
"license-checker-rseidelsohn": "4.4.2",
|
||||
"lint-staged": "17.0.5",
|
||||
"lint-staged": "17.0.4",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.18.1",
|
||||
@@ -201,9 +202,9 @@
|
||||
"terser-webpack-plugin": "5.6.0",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "8.59.4",
|
||||
"typescript-eslint": "8.59.3",
|
||||
"vite-tsconfig-paths": "6.1.1",
|
||||
"vitest": "4.1.7",
|
||||
"vitest": "4.1.6",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
"webpackbar": "7.0.0",
|
||||
"workbox-build": "patch:workbox-build@npm%3A7.4.1#~/.yarn/patches/workbox-build-npm-7.4.1-c84561662c.patch"
|
||||
@@ -219,8 +220,8 @@
|
||||
"@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
|
||||
"glob@^10.2.2": "^10.5.0"
|
||||
},
|
||||
"packageManager": "yarn@4.15.0",
|
||||
"packageManager": "yarn@4.14.1",
|
||||
"volta": {
|
||||
"node": "24.16.0"
|
||||
"node": "24.15.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,46 +18,7 @@
|
||||
"enabled": true,
|
||||
"schedule": ["on the 19th day of the month before 4am"]
|
||||
},
|
||||
"customDatasources": {
|
||||
"ha-core-python": {
|
||||
"defaultRegistryUrlTemplate": "https://raw.githubusercontent.com/home-assistant/core/dev/.python-version",
|
||||
"format": "plain"
|
||||
}
|
||||
},
|
||||
"customManagers": [
|
||||
{
|
||||
"description": "Keep PYTHON_VERSION in sync with home-assistant/core (patch + minor)",
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": ["/^\\.github/workflows/[^/]+\\.ya?ml$/"],
|
||||
"matchStrings": ["PYTHON_VERSION: \"(?<currentValue>[^\"]+)\""],
|
||||
"depNameTemplate": "python",
|
||||
"datasourceTemplate": "custom.ha-core-python",
|
||||
"versioningTemplate": "python"
|
||||
},
|
||||
{
|
||||
"description": "Keep devcontainer image and requires-python in sync with home-assistant/core (minor only)",
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": [
|
||||
"/^\\.devcontainer/Dockerfile$/",
|
||||
"/^pyproject\\.toml$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"devcontainers/python:(?<currentValue>[\\d.]+)",
|
||||
"requires-python = \">=(?<currentValue>[^\"]+)\""
|
||||
],
|
||||
"depNameTemplate": "python",
|
||||
"datasourceTemplate": "custom.ha-core-python",
|
||||
"versioningTemplate": "python",
|
||||
"extractVersionTemplate": "^(?<version>\\d+\\.\\d+)"
|
||||
}
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"description": "Group all Python version updates from home-assistant/core",
|
||||
"matchDepNames": ["python"],
|
||||
"matchDatasources": ["custom.ha-core-python"],
|
||||
"groupName": "Python version"
|
||||
},
|
||||
{
|
||||
"description": "MDC packages are pinned to the same version as MWC",
|
||||
"extends": ["monorepo:material-components-web"],
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @param arr - The array to get combinations of
|
||||
* @returns A multidimensional array of all possible combinations
|
||||
*/
|
||||
export function getAllCombinations<T>(arr: readonly T[]): T[][] {
|
||||
export function getAllCombinations<T>(arr: T[]) {
|
||||
return arr.reduce<T[][]>(
|
||||
(combinations, element) =>
|
||||
combinations.concat(
|
||||
|
||||
@@ -114,15 +114,6 @@ export const DOMAINS_WITH_DYNAMIC_PICTURE = new Set([
|
||||
export const UNIT_C = "°C";
|
||||
export const UNIT_F = "°F";
|
||||
|
||||
/** Length units. */
|
||||
export const UNIT_IN = "in";
|
||||
export const UNIT_KM = "km";
|
||||
export const UNIT_MM = "mm";
|
||||
|
||||
/** Pressure units. */
|
||||
export const UNIT_HPA = "hPa";
|
||||
export const UNIT_INHG = "inHg";
|
||||
|
||||
/** Entity ID of the default view. */
|
||||
export const DEFAULT_VIEW_ENTITY_ID = "group.default_view";
|
||||
|
||||
|
||||
@@ -1,20 +1,6 @@
|
||||
import timezones from "google-timezones-json";
|
||||
import { TimeZone } from "../../data/translation";
|
||||
|
||||
const RESOLVED_RAW = Intl.DateTimeFormat?.().resolvedOptions?.().timeZone;
|
||||
|
||||
// Some environments (e.g. Android emulator) return a UTC offset like "+00:00"
|
||||
// instead of an IANA zone name. Only accept values that are known IANA zones,
|
||||
// matching the list used by ha-timezone-picker.
|
||||
const RESOLVED_TIME_ZONE =
|
||||
RESOLVED_RAW &&
|
||||
(RESOLVED_RAW === "UTC" ||
|
||||
RESOLVED_RAW === "Etc/UTC" ||
|
||||
RESOLVED_RAW in timezones)
|
||||
? RESOLVED_RAW
|
||||
: undefined;
|
||||
|
||||
export const HAS_RESOLVED_IANA_TIME_ZONE = RESOLVED_TIME_ZONE !== undefined;
|
||||
const RESOLVED_TIME_ZONE = Intl.DateTimeFormat?.().resolvedOptions?.().timeZone;
|
||||
|
||||
// Browser time zone can be determined from Intl, with fallback to UTC for polyfill or no support.
|
||||
export const LOCAL_TIME_ZONE = RESOLVED_TIME_ZONE ?? "UTC";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity/entity";
|
||||
import { isUnavailableState } from "../../data/entity/entity";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
interface EntityUnitStubConfig {
|
||||
@@ -21,24 +21,32 @@ export const computeEntityUnitDisplay = (
|
||||
stateObj: HassEntity | undefined,
|
||||
config: EntityUnitStubConfig
|
||||
): string => {
|
||||
let unit;
|
||||
if (
|
||||
!stateObj ||
|
||||
stateObj.state === UNAVAILABLE ||
|
||||
stateObj.state === UNKNOWN ||
|
||||
(!config.attribute && stateObj.attributes.device_class === "duration")
|
||||
stateObj &&
|
||||
!isUnavailableState(stateObj.state) &&
|
||||
(config.attribute || stateObj.attributes.device_class !== "duration")
|
||||
) {
|
||||
return "";
|
||||
// check for an explicitly defined unit in config
|
||||
unit = config.unit;
|
||||
|
||||
if (!unit) {
|
||||
if (!config.attribute) {
|
||||
// use entity's unit_of_measurement
|
||||
const stateParts = hass.formatEntityStateToParts(stateObj);
|
||||
unit = stateParts.find((part) => part.type === "unit")?.value;
|
||||
} else {
|
||||
// use attribute's unit if available
|
||||
const attrParts = hass.formatEntityAttributeValueToParts(
|
||||
stateObj,
|
||||
config.attribute
|
||||
);
|
||||
unit = attrParts.find((part) => part.type === "unit")?.value;
|
||||
}
|
||||
}
|
||||
|
||||
return unit ?? "";
|
||||
}
|
||||
|
||||
// check for an explicitly defined unit in config
|
||||
if (config.unit) {
|
||||
return config.unit;
|
||||
}
|
||||
|
||||
// otherwise derive from the entity's state or attribute
|
||||
const parts = config.attribute
|
||||
? hass.formatEntityAttributeValueToParts(stateObj, config.attribute)
|
||||
: hass.formatEntityStateToParts(stateObj);
|
||||
|
||||
return parts.find((part) => part.type === "unit")?.value ?? "";
|
||||
return "";
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity/entity";
|
||||
import { UNAVAILABLE_STATES } from "../../data/entity/entity";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { stringCompare } from "../string/compare";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
@@ -253,7 +253,7 @@ export const getStatesDomain = (
|
||||
|
||||
if (!attribute) {
|
||||
// All entities can have unavailable states
|
||||
result.push(UNAVAILABLE, UNKNOWN);
|
||||
result.push(...UNAVAILABLE_STATES);
|
||||
}
|
||||
|
||||
if (!attribute && domain in FIXED_DOMAIN_STATES) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity/entity";
|
||||
import { isUnavailableState, UNAVAILABLE } from "../../data/entity/entity";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { computeStateDomain } from "./compute_state_domain";
|
||||
|
||||
@@ -8,18 +8,12 @@ export const computeGroupEntitiesState = (states: HassEntity[]): string => {
|
||||
return UNAVAILABLE;
|
||||
}
|
||||
|
||||
const allUnavailable = states.every(
|
||||
(stateObj) => stateObj.state === UNAVAILABLE
|
||||
const validState = states.some(
|
||||
(stateObj) => !isUnavailableState(stateObj.state)
|
||||
);
|
||||
if (allUnavailable) {
|
||||
return UNAVAILABLE;
|
||||
}
|
||||
|
||||
const hasValidState = states.some(
|
||||
(stateObj) => stateObj.state !== UNAVAILABLE && stateObj.state !== UNKNOWN
|
||||
);
|
||||
if (!hasValidState) {
|
||||
return UNKNOWN;
|
||||
if (!validState) {
|
||||
return UNAVAILABLE;
|
||||
}
|
||||
|
||||
// Use the first state to determine the domain
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { OFF, UNAVAILABLE, UNKNOWN } from "../../data/entity/entity";
|
||||
import { isUnavailableState, OFF, UNAVAILABLE } from "../../data/entity/entity";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
|
||||
export function stateActive(stateObj: HassEntity, state?: string): boolean {
|
||||
@@ -19,7 +19,7 @@ export function stateActive(stateObj: HassEntity, state?: string): boolean {
|
||||
return compareState !== UNAVAILABLE;
|
||||
}
|
||||
|
||||
if (compareState === UNAVAILABLE || compareState === UNKNOWN) {
|
||||
if (isUnavailableState(compareState)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* @summary Truncates a string to `maxLength`, appending `ellipsis` only when it actually shortens the result.
|
||||
* @param text The input string.
|
||||
* @param maxLength Maximum length of the prefix kept before the ellipsis.
|
||||
* @param ellipsis Suffix appended when truncation occurs.
|
||||
* @returns `text` unchanged when its length is `<= maxLength + ellipsis.length`, otherwise `text.substring(0, maxLength) + ellipsis`.
|
||||
*/
|
||||
export const truncateWithEllipsis = (
|
||||
text: string,
|
||||
maxLength: number,
|
||||
ellipsis = "..."
|
||||
): string => {
|
||||
if (text.length <= maxLength + ellipsis.length) {
|
||||
return text;
|
||||
}
|
||||
return `${text.substring(0, maxLength)}${ellipsis}`;
|
||||
};
|
||||
@@ -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";
|
||||
|
||||
@@ -8,10 +9,11 @@ export type LiveTestState = "pass" | "fail" | "invalid" | "unknown";
|
||||
*
|
||||
* @summary
|
||||
* Small status indicator dot used in automation/condition rows to surface the
|
||||
* live evaluation result.
|
||||
* live evaluation result. Renders an optional tooltip with details on hover.
|
||||
*
|
||||
* @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,6 +31,9 @@ export class HaAutomationRowLiveTest extends LitElement {
|
||||
tabindex="0"
|
||||
aria-label=${this.label}
|
||||
></div>
|
||||
${this.message
|
||||
? html`<ha-tooltip for="indicator">${this.message}</ha-tooltip>`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -49,15 +56,31 @@ export class HaAutomationRowLiveTest extends LitElement {
|
||||
background-color: var(--ha-color-fill-success-loud-resting);
|
||||
border-color: var(--ha-color-fill-success-loud-resting);
|
||||
}
|
||||
:host([state="pass"]) #indicator:hover {
|
||||
background-color: var(--ha-color-fill-success-loud-hover);
|
||||
border-color: var(--ha-color-fill-success-loud-hover);
|
||||
}
|
||||
:host([state="fail"]) #indicator {
|
||||
border-color: var(--ha-color-fill-warning-loud-resting);
|
||||
}
|
||||
:host([state="fail"]) #indicator:hover {
|
||||
background-color: var(--ha-color-fill-warning-loud-hover);
|
||||
border-color: var(--ha-color-fill-warning-loud-hover);
|
||||
}
|
||||
:host([state="invalid"]) #indicator {
|
||||
border-color: var(--ha-color-fill-danger-loud-resting);
|
||||
}
|
||||
:host([state="invalid"]) #indicator:hover {
|
||||
background-color: var(--ha-color-fill-danger-loud-hover);
|
||||
border-color: var(--ha-color-fill-danger-loud-hover);
|
||||
}
|
||||
:host([state="unknown"]) #indicator {
|
||||
border-color: var(--ha-color-fill-neutral-loud-resting);
|
||||
}
|
||||
:host([state="unknown"]) #indicator:hover {
|
||||
background-color: var(--ha-color-fill-neutral-loud-hover);
|
||||
border-color: var(--ha-color-fill-neutral-loud-hover);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,9 +128,7 @@ export class HaAutomationRow extends LitElement {
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
padding-left: var(--ha-space-3);
|
||||
padding-inline-start: var(--ha-space-3);
|
||||
padding-inline-end: initial;
|
||||
padding: 0 0 0 var(--ha-space-3);
|
||||
min-height: 48px;
|
||||
align-items: flex-start;
|
||||
cursor: pointer;
|
||||
@@ -146,8 +144,6 @@ export class HaAutomationRow extends LitElement {
|
||||
transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
color: var(--ha-color-on-neutral-quiet);
|
||||
margin-left: calc(var(--ha-space-2) * -1);
|
||||
margin-inline-start: calc(var(--ha-space-2) * -1);
|
||||
margin-inline-end: initial;
|
||||
}
|
||||
:host([building-block]) .leading-icon-wrapper {
|
||||
background-color: var(--ha-color-fill-neutral-loud-resting);
|
||||
@@ -191,6 +187,7 @@ export class HaAutomationRow extends LitElement {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
margin: 0 var(--ha-space-3);
|
||||
}
|
||||
::slotted([slot="header"]) {
|
||||
overflow-wrap: anywhere;
|
||||
|
||||
@@ -116,7 +116,7 @@ export class HaProgressButton extends LitElement {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
:host([appearance="brand"]) ha-svg-icon {
|
||||
ha-svg-icon {
|
||||
color: var(--white-color);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { consume } from "@lit/context";
|
||||
import type { HassEntities } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { fullEntitiesContext } from "../../data/context";
|
||||
import type { DeviceAutomation } from "../../data/device/device_automation";
|
||||
import {
|
||||
@@ -14,7 +12,7 @@ import {
|
||||
sortDeviceAutomations,
|
||||
} from "../../data/device/device_automation";
|
||||
import type { EntityRegistryEntry } from "../../data/entity/entity_registry";
|
||||
import type { CallWS, HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../ha-generic-picker";
|
||||
import type { PickerValueRenderer } from "../ha-picker-field";
|
||||
|
||||
@@ -48,14 +46,13 @@ export abstract class HaDeviceAutomationPicker<
|
||||
}
|
||||
|
||||
private _localizeDeviceAutomation: (
|
||||
localize: LocalizeFunc,
|
||||
states: HassEntities,
|
||||
hass: HomeAssistant,
|
||||
entityRegistry: EntityRegistryEntry[],
|
||||
automation: T
|
||||
) => string;
|
||||
|
||||
private _fetchDeviceAutomations: (
|
||||
callWS: CallWS,
|
||||
hass: HomeAssistant,
|
||||
deviceId: string
|
||||
) => Promise<T[]>;
|
||||
|
||||
@@ -130,8 +127,7 @@ export abstract class HaDeviceAutomationPicker<
|
||||
|
||||
const automationListItems = automations.map((automation, idx) => {
|
||||
const primary = this._localizeDeviceAutomation(
|
||||
this.hass.localize,
|
||||
this.hass.states,
|
||||
this.hass,
|
||||
this._entityReg,
|
||||
automation
|
||||
);
|
||||
@@ -166,12 +162,7 @@ export abstract class HaDeviceAutomationPicker<
|
||||
);
|
||||
|
||||
const text = automation
|
||||
? this._localizeDeviceAutomation(
|
||||
this.hass.localize,
|
||||
this.hass.states,
|
||||
this._entityReg,
|
||||
automation
|
||||
)
|
||||
? this._localizeDeviceAutomation(this.hass, this._entityReg, automation)
|
||||
: value === NO_AUTOMATION_KEY
|
||||
? this.NO_AUTOMATION_TEXT
|
||||
: value;
|
||||
@@ -181,9 +172,9 @@ export abstract class HaDeviceAutomationPicker<
|
||||
|
||||
private async _updateDeviceInfo() {
|
||||
this._automations = this.deviceId
|
||||
? (
|
||||
await this._fetchDeviceAutomations(this.hass.callWS, this.deviceId)
|
||||
).sort(sortDeviceAutomations)
|
||||
? (await this._fetchDeviceAutomations(this.hass, this.deviceId)).sort(
|
||||
sortDeviceAutomations
|
||||
)
|
||||
: // No device, clear the list of automations
|
||||
[];
|
||||
|
||||
|
||||
@@ -6,7 +6,11 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { STATES_OFF } from "../../common/const";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity/entity";
|
||||
import {
|
||||
UNAVAILABLE,
|
||||
UNKNOWN,
|
||||
isUnavailableState,
|
||||
} from "../../data/entity/entity";
|
||||
import { forwardHaptic } from "../../data/haptics";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-formfield";
|
||||
@@ -16,8 +20,7 @@ import "../ha-switch";
|
||||
const isOn = (stateObj?: HassEntity) =>
|
||||
stateObj !== undefined &&
|
||||
!STATES_OFF.includes(stateObj.state) &&
|
||||
stateObj.state !== UNAVAILABLE &&
|
||||
stateObj.state !== UNKNOWN;
|
||||
!isUnavailableState(stateObj.state);
|
||||
|
||||
/**
|
||||
* @element ha-entity-toggle
|
||||
|
||||
@@ -9,7 +9,7 @@ import secondsToDuration from "../../common/datetime/seconds_to_duration";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { FIXED_DOMAIN_STATES } from "../../common/entity/get_states";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity/entity";
|
||||
import { isUnavailableState, UNAVAILABLE } from "../../data/entity/entity";
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
|
||||
import { timerTimeRemaining } from "../../data/timer";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
@@ -170,8 +170,7 @@ export class HaStateLabelBadge extends LitElement {
|
||||
}
|
||||
// eslint-disable-next-line: disable=no-fallthrough
|
||||
default:
|
||||
return entityState.state === UNAVAILABLE ||
|
||||
entityState.state === UNKNOWN
|
||||
return isUnavailableState(entityState.state)
|
||||
? "—"
|
||||
: this.hass!.formatEntityStateToParts(entityState).find(
|
||||
(part) => part.type === "value"
|
||||
@@ -210,7 +209,7 @@ export class HaStateLabelBadge extends LitElement {
|
||||
_timerTimeRemaining = 0
|
||||
) {
|
||||
// For unavailable states or certain domains, use a special translation that is truncated to fit within the badge label
|
||||
if (entityState.state === UNAVAILABLE || entityState.state === UNKNOWN) {
|
||||
if (isUnavailableState(entityState.state)) {
|
||||
return this.hass!.localize(`state_badge.default.${entityState.state}`);
|
||||
}
|
||||
const domainStateKey = getTruncatedKey(domain, entityState.state);
|
||||
|
||||
@@ -29,7 +29,7 @@ export interface AreaControlPickerItem extends PickerComboBoxItem {
|
||||
deviceClass?: string;
|
||||
}
|
||||
|
||||
const AREA_CONTROL_DOMAINS = [
|
||||
const AREA_CONTROL_DOMAINS: readonly AreaControlDomain[] = [
|
||||
"light",
|
||||
"fan",
|
||||
"switch",
|
||||
@@ -43,7 +43,7 @@ const AREA_CONTROL_DOMAINS = [
|
||||
"cover-door",
|
||||
"cover-window",
|
||||
"cover-damper",
|
||||
] as const satisfies readonly AreaControlDomain[];
|
||||
] as const;
|
||||
|
||||
@customElement("ha-area-controls-picker")
|
||||
export class HaAreaControlsPicker extends LitElement {
|
||||
@@ -130,7 +130,7 @@ export class HaAreaControlsPicker extends LitElement {
|
||||
(excludeValues !== undefined && excludeValues.includes(id));
|
||||
|
||||
const controlEntities = getAreaControlEntities(
|
||||
AREA_CONTROL_DOMAINS,
|
||||
AREA_CONTROL_DOMAINS as unknown as AreaControlDomain[],
|
||||
areaId,
|
||||
excludeEntities,
|
||||
this.hass
|
||||
|
||||
@@ -3,7 +3,7 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { ClimateEntity } from "../data/climate";
|
||||
import { CLIMATE_PRESET_NONE } from "../data/climate";
|
||||
import { OFF, UNAVAILABLE, UNKNOWN } from "../data/entity/entity";
|
||||
import { isUnavailableState, OFF } from "../data/entity/entity";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
@customElement("ha-climate-state")
|
||||
@@ -14,11 +14,9 @@ class HaClimateState extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const currentStatus = this._computeCurrentStatus();
|
||||
const noValue =
|
||||
this.stateObj.state === UNAVAILABLE || this.stateObj.state === UNKNOWN;
|
||||
|
||||
return html`<div class="target">
|
||||
${!noValue
|
||||
${!isUnavailableState(this.stateObj.state)
|
||||
? html`<span class="state-label">
|
||||
${this._localizeState()}
|
||||
${this.stateObj.attributes.preset_mode &&
|
||||
@@ -34,7 +32,7 @@ class HaClimateState extends LitElement {
|
||||
: this._localizeState()}
|
||||
</div>
|
||||
|
||||
${currentStatus && !noValue
|
||||
${currentStatus && !isUnavailableState(this.stateObj.state)
|
||||
? html`
|
||||
<div class="current">
|
||||
${this.hass.localize("ui.card.climate.currently")}:
|
||||
@@ -121,10 +119,7 @@ class HaClimateState extends LitElement {
|
||||
}
|
||||
|
||||
private _localizeState(): string {
|
||||
if (
|
||||
this.stateObj.state === UNAVAILABLE ||
|
||||
this.stateObj.state === UNKNOWN
|
||||
) {
|
||||
if (isUnavailableState(this.stateObj.state)) {
|
||||
return this.hass.localize(`state.default.${this.stateObj.state}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -294,7 +294,6 @@ export class HaDrawer extends LitElement {
|
||||
border-inline-end: 1px solid var(--divider-color, rgba(0, 0, 0, 0.12));
|
||||
box-sizing: border-box;
|
||||
transition: width var(--ha-animation-duration-normal) ease;
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
.app-content {
|
||||
|
||||
@@ -6,18 +6,11 @@ import type { HaIconButton } from "./ha-icon-button";
|
||||
|
||||
/**
|
||||
* Event type for the ha-dropdown component when an item is selected.
|
||||
* @param TValue - The type of the selected item's `value`.
|
||||
* @param TData - The type of the selected item's `data` when set on `ha-dropdown-item`.
|
||||
* @param T - The type of the value of the selected item.
|
||||
*/
|
||||
export type HaDropdownSelectEvent<TValue = string, TData = undefined> = [
|
||||
TData,
|
||||
] extends [undefined]
|
||||
? CustomEvent<{
|
||||
item: Omit<HaDropdownItem, "value"> & { value: TValue };
|
||||
}>
|
||||
: CustomEvent<{
|
||||
item: Omit<HaDropdownItem, "value"> & { value: TValue; data: TData };
|
||||
}>;
|
||||
export type HaDropdownSelectEvent<T = string> = CustomEvent<{
|
||||
item: Omit<HaDropdownItem, "value"> & { value: T };
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Home Assistant dropdown component
|
||||
|
||||
@@ -109,8 +109,6 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
@property({ attribute: "custom-value-label" })
|
||||
public customValueLabel?: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "no-sort" }) public noSort = false;
|
||||
|
||||
@query(".container") private _containerElement?: HTMLDivElement;
|
||||
|
||||
@query("ha-picker-combo-box") private _comboBox?: HaPickerComboBox;
|
||||
@@ -273,7 +271,6 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
.selectedSection=${this.selectedSection}
|
||||
.searchKeys=${this.searchKeys}
|
||||
.customValueLabel=${this.customValueLabel}
|
||||
.noSort=${this.noSort}
|
||||
></ha-picker-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { OFF, UNAVAILABLE, UNKNOWN } from "../data/entity/entity";
|
||||
import { isUnavailableState, OFF } from "../data/entity/entity";
|
||||
import type { HumidifierEntity } from "../data/humidifier";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
@@ -13,11 +13,9 @@ class HaHumidifierState extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const currentStatus = this._computeCurrentStatus();
|
||||
const noValue =
|
||||
this.stateObj.state === UNAVAILABLE || this.stateObj.state === UNKNOWN;
|
||||
|
||||
return html`<div class="target">
|
||||
${!noValue
|
||||
${!isUnavailableState(this.stateObj.state)
|
||||
? html`<span class="state-label">
|
||||
${this._localizeState()}
|
||||
${this.stateObj.attributes.mode
|
||||
@@ -32,7 +30,7 @@ class HaHumidifierState extends LitElement {
|
||||
: this._localizeState()}
|
||||
</div>
|
||||
|
||||
${currentStatus && !noValue
|
||||
${currentStatus && !isUnavailableState(this.stateObj.state)
|
||||
? html`<div class="current">
|
||||
${this.hass.localize("ui.card.humidifier.currently")}:
|
||||
<div class="unit">${currentStatus}</div>
|
||||
@@ -71,10 +69,7 @@ class HaHumidifierState extends LitElement {
|
||||
}
|
||||
|
||||
private _localizeState(): string {
|
||||
if (
|
||||
this.stateObj.state === UNAVAILABLE ||
|
||||
this.stateObj.state === UNKNOWN
|
||||
) {
|
||||
if (isUnavailableState(this.stateObj.state)) {
|
||||
return this.hass.localize(`state.default.${this.stateObj.state}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -167,8 +167,6 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public clearable = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "no-sort" }) public noSort = false;
|
||||
|
||||
@query("lit-virtualizer") public virtualizerElement?: LitVirtualizer;
|
||||
|
||||
@query("ha-input-search") private _searchFieldElement?: HaInputSearch;
|
||||
@@ -344,7 +342,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
private _getItems = () => {
|
||||
let items = [...(this.getItems(this._search, this._selectedSection) || [])];
|
||||
|
||||
if (!this.sections?.length && !this.noSort) {
|
||||
if (!this.sections?.length) {
|
||||
items = items.sort((entityA, entityB) => {
|
||||
const sortLabelA =
|
||||
typeof entityA === "string" ? entityA : entityA.sorting_label;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent, type HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { ColorTempSelector } from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-labeled-slider";
|
||||
@@ -94,10 +94,10 @@ export class HaColorTempSelector extends LitElement {
|
||||
}
|
||||
);
|
||||
|
||||
private _valueChanged(ev: HASSDomEvent<HASSDomEvents["value-changed"]>) {
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: Number(ev.detail.value),
|
||||
value: Number((ev.detail as any).value),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +199,6 @@ export class HaSelectSelector extends LitElement {
|
||||
: nothing}
|
||||
|
||||
<ha-generic-picker
|
||||
no-sort
|
||||
.hass=${this.hass}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
@@ -216,7 +215,6 @@ export class HaSelectSelector extends LitElement {
|
||||
if (this.selector.select?.custom_value) {
|
||||
return html`
|
||||
<ha-generic-picker
|
||||
no-sort
|
||||
.hass=${this.hass}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
|
||||
@@ -837,7 +837,7 @@ export class HaServiceControl extends LitElement {
|
||||
if (targetDevices.length) {
|
||||
targetDevices = targetDevices.filter((device) =>
|
||||
deviceMeetsTargetSelector(
|
||||
this.hass.states,
|
||||
this.hass,
|
||||
Object.values(this.hass.entities),
|
||||
this.hass.devices[device],
|
||||
targetSelector
|
||||
|
||||
@@ -30,7 +30,6 @@ export class HaSettingsRow extends LitElement {
|
||||
<slot name="prefix"></slot>
|
||||
<div
|
||||
class="body"
|
||||
part="heading"
|
||||
?two-line=${!this.threeLine && hasDescription}
|
||||
?three-line=${this.threeLine}
|
||||
>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import "@home-assistant/webawesome/dist/components/textarea/textarea";
|
||||
import type WaTextarea from "@home-assistant/webawesome/dist/components/textarea/textarea";
|
||||
import { HasSlotController } from "@home-assistant/webawesome/dist/internal/slot";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import { WaInputMixin, waInputStyles } from "./input/wa-input-mixin";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
|
||||
/**
|
||||
* Home Assistant textarea component
|
||||
@@ -85,20 +84,6 @@ export class HaTextArea extends WaInputMixin(LitElement) {
|
||||
this.removeEventListener("keydown", stopPropagation);
|
||||
}
|
||||
|
||||
protected override async firstUpdated(
|
||||
changedProperties: PropertyValues<this>
|
||||
): Promise<void> {
|
||||
super.firstUpdated(changedProperties);
|
||||
if (this.autofocus) {
|
||||
await this._textarea?.updateComplete;
|
||||
this._textarea?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public override focus(options?: FocusOptions): void {
|
||||
this._textarea?.focus(options);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const hasLabelSlot = this.label
|
||||
? false
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import "./ha-generic-picker";
|
||||
import type { PickerComboBoxItem } from "./ha-picker-combo-box";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { HaSelectOption, HaSelectSelectEvent } from "./ha-select";
|
||||
import "./ha-select";
|
||||
|
||||
const DEFAULT_THEME = "default";
|
||||
|
||||
const SEARCH_KEYS = [{ name: "primary", weight: 1 }];
|
||||
|
||||
@customElement("ha-theme-picker")
|
||||
export class HaThemePicker extends LitElement {
|
||||
@property() public value?: string;
|
||||
@@ -29,74 +25,52 @@ export class HaThemePicker extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property({ attribute: "no-theme-label" }) public noThemeLabel?: string;
|
||||
|
||||
private _getThemeOptions = memoizeOne(
|
||||
(
|
||||
themes: Record<string, unknown>,
|
||||
locale: string,
|
||||
includeDefault: boolean
|
||||
): PickerComboBoxItem[] => {
|
||||
const items: PickerComboBoxItem[] = [];
|
||||
|
||||
if (includeDefault) {
|
||||
items.push({ id: DEFAULT_THEME, primary: "Home Assistant" });
|
||||
}
|
||||
|
||||
const themeNames = Object.keys(themes).sort((a, b) =>
|
||||
caseInsensitiveStringCompare(a, b, locale)
|
||||
);
|
||||
for (const theme of themeNames) {
|
||||
items.push({ id: theme, primary: theme });
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
);
|
||||
|
||||
private _getItems = () =>
|
||||
this._getThemeOptions(
|
||||
this.hass?.themes.themes || {},
|
||||
this.hass?.locale.language || "en",
|
||||
this.includeDefault
|
||||
);
|
||||
|
||||
private _valueRenderer = (value: string): TemplateResult =>
|
||||
html`<span slot="headline"
|
||||
>${this._getItems().find((i) => i.id === value)?.primary ?? value}</span
|
||||
>`;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const options: HaSelectOption[] = Object.keys(
|
||||
this.hass?.themes.themes || {}
|
||||
).map((theme) => ({
|
||||
value: theme,
|
||||
}));
|
||||
|
||||
if (this.includeDefault) {
|
||||
options.unshift({
|
||||
value: DEFAULT_THEME,
|
||||
label: "Home Assistant",
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.required) {
|
||||
options.unshift({
|
||||
value: "remove",
|
||||
label: this.hass!.localize("ui.components.theme-picker.no_theme"),
|
||||
});
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-generic-picker
|
||||
.label=${this.label ??
|
||||
this.hass?.localize("ui.components.theme-picker.theme") ??
|
||||
"Theme"}
|
||||
.placeholder=${this.noThemeLabel ??
|
||||
this.hass?.localize("ui.components.theme-picker.no_theme")}
|
||||
.helper=${this.helper}
|
||||
<ha-select
|
||||
.label=${this.label ||
|
||||
this.hass!.localize("ui.components.theme-picker.theme")}
|
||||
.value=${this.value}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.getItems=${this._getItems}
|
||||
.searchKeys=${SEARCH_KEYS}
|
||||
.disabled=${this.disabled}
|
||||
.helper=${this.helper}
|
||||
.required=${this.required}
|
||||
@value-changed=${this._changed}
|
||||
popover-placement="bottom"
|
||||
></ha-generic-picker>
|
||||
.disabled=${this.disabled}
|
||||
@selected=${this._changed}
|
||||
.options=${options}
|
||||
></ha-select>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-generic-picker {
|
||||
ha-select {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
private _changed(ev: ValueChangedEvent<string | undefined>): void {
|
||||
ev.stopPropagation();
|
||||
this.value = ev.detail.value;
|
||||
private _changed(ev: HaSelectSelectEvent): void {
|
||||
if (!this.hass || ev.detail.value === "") {
|
||||
return;
|
||||
}
|
||||
this.value = ev.detail.value === "remove" ? undefined : ev.detail.value;
|
||||
fireEvent(this, "value-changed", { value: this.value });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { until } from "lit/directives/until";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { slugify } from "../../common/string/slugify";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { UNAVAILABLE } from "../../data/entity/entity";
|
||||
import { isUnavailableState } from "../../data/entity/entity";
|
||||
import type {
|
||||
MediaPickedEvent,
|
||||
MediaPlayerBrowseAction,
|
||||
@@ -290,7 +290,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
} else if (
|
||||
err.code === "entity_not_found" &&
|
||||
this.entityId &&
|
||||
this.hass.states[this.entityId]?.state === UNAVAILABLE
|
||||
isUnavailableState(this.hass.states[this.entityId]?.state)
|
||||
) {
|
||||
this._setError({
|
||||
message: this.hass.localize(
|
||||
|
||||
@@ -454,7 +454,7 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
}
|
||||
try {
|
||||
const entries = await extractFromTarget(
|
||||
this.hass.callWS,
|
||||
this.hass,
|
||||
{
|
||||
[`${this.type}_id`]: [this.itemId],
|
||||
},
|
||||
|
||||
@@ -112,7 +112,6 @@ export class HaTileContainer extends LitElement {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.vertical ::slotted([slot="info"]) {
|
||||
width: 100%;
|
||||
|
||||
@@ -62,7 +62,7 @@ export const AREA_CONTROLS_BUTTONS: Record<
|
||||
};
|
||||
|
||||
export const getAreaControlEntities = (
|
||||
controls: readonly AreaControlDomain[],
|
||||
controls: AreaControlDomain[],
|
||||
areaId: string,
|
||||
excludeEntities: string[] | undefined,
|
||||
hass: HomeAssistant
|
||||
|
||||
+2
-11
@@ -1,4 +1,3 @@
|
||||
import { createContext } from "@lit/context";
|
||||
import type {
|
||||
Connection,
|
||||
HassEntityAttributeBase,
|
||||
@@ -96,7 +95,6 @@ export interface TriggerList {
|
||||
|
||||
export interface BaseTrigger {
|
||||
alias?: string;
|
||||
comment?: string;
|
||||
/** @deprecated Use `trigger` instead */
|
||||
platform?: string;
|
||||
trigger: string;
|
||||
@@ -242,7 +240,6 @@ export type Trigger = LegacyTrigger | TriggerList | PlatformTrigger;
|
||||
interface BaseCondition {
|
||||
condition: string;
|
||||
alias?: string;
|
||||
comment?: string;
|
||||
enabled?: boolean;
|
||||
options?: Record<string, unknown>;
|
||||
}
|
||||
@@ -491,12 +488,12 @@ export const migrateAutomationTrigger = (
|
||||
|
||||
export const flattenTriggers = (
|
||||
triggers: undefined | Trigger | Trigger[]
|
||||
): Exclude<Trigger, TriggerList>[] => {
|
||||
): Trigger[] => {
|
||||
if (!triggers) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const flatTriggers: Exclude<Trigger, TriggerList>[] = [];
|
||||
const flatTriggers: Trigger[] = [];
|
||||
|
||||
ensureArray(triggers).forEach((t) => {
|
||||
if ("triggers" in t) {
|
||||
@@ -610,7 +607,6 @@ export interface AutomationClipboard {
|
||||
export interface BaseSidebarConfig {
|
||||
delete: () => void;
|
||||
close: (focus?: boolean) => void;
|
||||
editComment: () => void;
|
||||
}
|
||||
|
||||
export interface TriggerSidebarConfig extends BaseSidebarConfig {
|
||||
@@ -672,7 +668,6 @@ export interface OptionSidebarConfig extends BaseSidebarConfig {
|
||||
rename: () => void;
|
||||
duplicate: () => void;
|
||||
defaultOption?: boolean;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface ScriptFieldSidebarConfig extends BaseSidebarConfig {
|
||||
@@ -698,7 +693,3 @@ export interface ShowAutomationEditorParams {
|
||||
data?: Partial<AutomationConfig>;
|
||||
expanded?: boolean;
|
||||
}
|
||||
|
||||
export const automationConfigContext = createContext<
|
||||
AutomationConfig | undefined
|
||||
>("automationConfig");
|
||||
|
||||
@@ -27,7 +27,6 @@ import type {
|
||||
LegacyTrigger,
|
||||
Trigger,
|
||||
} from "./automation";
|
||||
import { flattenTriggers } from "./automation";
|
||||
import { getConditionDomain, getConditionObjectId } from "./condition";
|
||||
import type {
|
||||
DeviceCondition,
|
||||
@@ -108,41 +107,6 @@ const formatNumericLimitValue = (
|
||||
: value;
|
||||
};
|
||||
|
||||
export interface TriggerInfo {
|
||||
id: string;
|
||||
label: string;
|
||||
triggerType: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export const getTriggerInfos = (
|
||||
triggers: Trigger[] | undefined,
|
||||
hass: HomeAssistant,
|
||||
entityRegistry: EntityRegistryEntry[]
|
||||
): TriggerInfo[] => {
|
||||
if (!triggers) {
|
||||
return [];
|
||||
}
|
||||
const map = new Map<string, TriggerInfo>();
|
||||
for (const t of flattenTriggers(triggers)) {
|
||||
if (isTriggerList(t) || !t.id) {
|
||||
continue;
|
||||
}
|
||||
const existing = map.get(t.id);
|
||||
if (existing) {
|
||||
existing.count++;
|
||||
} else {
|
||||
map.set(t.id, {
|
||||
id: t.id,
|
||||
label: describeTrigger(t, hass, entityRegistry),
|
||||
triggerType: t.trigger,
|
||||
count: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
return Array.from(map.values());
|
||||
};
|
||||
|
||||
export const describeTrigger = (
|
||||
trigger: Trigger,
|
||||
hass: HomeAssistant,
|
||||
@@ -854,8 +818,7 @@ const describeLegacyTrigger = (
|
||||
if (trigger.trigger === "device" && trigger.device_id) {
|
||||
const config = trigger as DeviceTrigger;
|
||||
const localized = localizeDeviceAutomationTrigger(
|
||||
hass.localize,
|
||||
hass.states,
|
||||
hass,
|
||||
entityRegistry,
|
||||
config
|
||||
);
|
||||
@@ -1373,8 +1336,7 @@ const describeLegacyCondition = (
|
||||
if (condition.condition === "device" && condition.device_id) {
|
||||
const config = condition as DeviceCondition;
|
||||
const localized = localizeDeviceAutomationCondition(
|
||||
hass.localize,
|
||||
hass.states,
|
||||
hass,
|
||||
entityRegistry,
|
||||
config
|
||||
);
|
||||
|
||||
+2
-11
@@ -17,7 +17,6 @@ export interface BluetoothDeviceData extends DataTableRowData {
|
||||
source: string;
|
||||
time: number;
|
||||
tx_power: number;
|
||||
raw: string | null;
|
||||
}
|
||||
|
||||
export interface BluetoothConnectionData extends DataTableRowData {
|
||||
@@ -59,21 +58,13 @@ export interface BluetoothAllocationsData {
|
||||
allocated: string[];
|
||||
}
|
||||
|
||||
export type BluetoothScannerMode = "active" | "passive";
|
||||
|
||||
export type BluetoothScannerRequestedMode = BluetoothScannerMode | "auto";
|
||||
|
||||
export interface BluetoothScannerState {
|
||||
source: string;
|
||||
adapter: string;
|
||||
current_mode: BluetoothScannerMode | null;
|
||||
requested_mode: BluetoothScannerRequestedMode | null;
|
||||
current_mode: "active" | "passive" | null;
|
||||
requested_mode: "active" | "passive" | null;
|
||||
}
|
||||
|
||||
export const isScannerStateMismatch = (state: BluetoothScannerState): boolean =>
|
||||
state.requested_mode !== "auto" &&
|
||||
state.current_mode !== state.requested_mode;
|
||||
|
||||
export const subscribeBluetoothScannersDetailsUpdates = (
|
||||
conn: Connection,
|
||||
store: Store<BluetoothScannersDetails>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { getColorByIndex } from "../common/color/colors";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { UNAVAILABLE } from "./entity/entity";
|
||||
import { isUnavailableState } from "./entity/entity";
|
||||
import type { EntityRegistryEntry } from "./entity/entity_registry";
|
||||
|
||||
export interface Calendar {
|
||||
@@ -120,7 +120,7 @@ export const getCalendars = (
|
||||
.filter(
|
||||
(eid) =>
|
||||
computeDomain(eid) === "calendar" &&
|
||||
hass.states[eid].state !== UNAVAILABLE &&
|
||||
!isUnavailableState(hass.states[eid].state) &&
|
||||
hass.entities[eid]?.hidden !== true
|
||||
)
|
||||
.sort()
|
||||
|
||||
@@ -5,10 +5,7 @@ export interface DataTableFilter {
|
||||
|
||||
export type DataTableFilters = Record<string, DataTableFilter>;
|
||||
|
||||
export type DataTableFiltersValue =
|
||||
| string[]
|
||||
| Record<"key" | string, string[]>
|
||||
| undefined;
|
||||
export type DataTableFiltersValue = string[] | { key: string[] } | undefined;
|
||||
|
||||
export type DataTableFiltersValues = Record<string, DataTableFiltersValue>;
|
||||
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import type { HassEntities } from "home-assistant-js-websocket";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import type { HaFormSchema } from "../../components/ha-form/types";
|
||||
import type { CallWS } from "../../types";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { BaseTrigger } from "../automation";
|
||||
import { migrateAutomationTrigger } from "../automation";
|
||||
import type { EntityRegistryEntry } from "../entity/entity_registry";
|
||||
import {
|
||||
computeEntityRegistryName,
|
||||
entityRegistryByEntityId,
|
||||
entityRegistryById,
|
||||
} from "../entity/entity_registry";
|
||||
|
||||
export interface DeviceAutomation {
|
||||
alias?: string;
|
||||
comment?: string;
|
||||
device_id: string;
|
||||
domain: string;
|
||||
entity_id?: string;
|
||||
@@ -41,47 +39,49 @@ export interface DeviceCapabilities {
|
||||
extra_fields: HaFormSchema[];
|
||||
}
|
||||
|
||||
export const fetchDeviceActions = (callWS: CallWS, deviceId: string) =>
|
||||
callWS<DeviceAction[]>({
|
||||
export const fetchDeviceActions = (hass: HomeAssistant, deviceId: string) =>
|
||||
hass.callWS<DeviceAction[]>({
|
||||
type: "device_automation/action/list",
|
||||
device_id: deviceId,
|
||||
});
|
||||
|
||||
export const fetchDeviceConditions = (callWS: CallWS, deviceId: string) =>
|
||||
callWS<DeviceCondition[]>({
|
||||
export const fetchDeviceConditions = (hass: HomeAssistant, deviceId: string) =>
|
||||
hass.callWS<DeviceCondition[]>({
|
||||
type: "device_automation/condition/list",
|
||||
device_id: deviceId,
|
||||
});
|
||||
|
||||
export const fetchDeviceTriggers = (callWS: CallWS, deviceId: string) =>
|
||||
callWS<DeviceTrigger[]>({
|
||||
type: "device_automation/trigger/list",
|
||||
device_id: deviceId,
|
||||
}).then((triggers) => migrateAutomationTrigger(triggers) as DeviceTrigger[]);
|
||||
export const fetchDeviceTriggers = (hass: HomeAssistant, deviceId: string) =>
|
||||
hass
|
||||
.callWS<DeviceTrigger[]>({
|
||||
type: "device_automation/trigger/list",
|
||||
device_id: deviceId,
|
||||
})
|
||||
.then((triggers) => migrateAutomationTrigger(triggers) as DeviceTrigger[]);
|
||||
|
||||
export const fetchDeviceActionCapabilities = (
|
||||
callWS: CallWS,
|
||||
hass: HomeAssistant,
|
||||
action: DeviceAction
|
||||
) =>
|
||||
callWS<DeviceCapabilities>({
|
||||
hass.callWS<DeviceCapabilities>({
|
||||
type: "device_automation/action/capabilities",
|
||||
action,
|
||||
});
|
||||
|
||||
export const fetchDeviceConditionCapabilities = (
|
||||
callWS: CallWS,
|
||||
hass: HomeAssistant,
|
||||
condition: DeviceCondition
|
||||
) =>
|
||||
callWS<DeviceCapabilities>({
|
||||
hass.callWS<DeviceCapabilities>({
|
||||
type: "device_automation/condition/capabilities",
|
||||
condition,
|
||||
});
|
||||
|
||||
export const fetchDeviceTriggerCapabilities = (
|
||||
callWS: CallWS,
|
||||
hass: HomeAssistant,
|
||||
trigger: DeviceTrigger
|
||||
) =>
|
||||
callWS<DeviceCapabilities>({
|
||||
hass.callWS<DeviceCapabilities>({
|
||||
type: "device_automation/trigger/capabilities",
|
||||
trigger,
|
||||
});
|
||||
@@ -184,16 +184,19 @@ const compareEntityIdWithEntityRegId = (
|
||||
};
|
||||
|
||||
const getEntityName = (
|
||||
localize: LocalizeFunc,
|
||||
states: HassEntities,
|
||||
hass: HomeAssistant,
|
||||
entityRegistry: EntityRegistryEntry[],
|
||||
entityId: string | undefined
|
||||
): string => {
|
||||
if (!entityId) {
|
||||
return `<${localize("ui.panel.config.automation.editor.unknown_entity")}>`;
|
||||
return (
|
||||
"<" +
|
||||
hass.localize("ui.panel.config.automation.editor.unknown_entity") +
|
||||
">"
|
||||
);
|
||||
}
|
||||
if (entityId.includes(".")) {
|
||||
const state = states[entityId];
|
||||
const state = hass.states[entityId];
|
||||
if (state) {
|
||||
return computeStateName(state);
|
||||
}
|
||||
@@ -201,35 +204,26 @@ const getEntityName = (
|
||||
}
|
||||
const entityReg = entityRegistryById(entityRegistry)[entityId];
|
||||
if (entityReg) {
|
||||
if (entityReg.name) {
|
||||
return entityReg.name;
|
||||
}
|
||||
const state = states[entityReg.entity_id];
|
||||
if (state) {
|
||||
return computeStateName(state);
|
||||
}
|
||||
return entityReg.original_name ?? entityId;
|
||||
return computeEntityRegistryName(hass, entityReg) || entityId;
|
||||
}
|
||||
return `<${localize("ui.panel.config.automation.editor.unknown_entity")}>`;
|
||||
return (
|
||||
"<" +
|
||||
hass.localize("ui.panel.config.automation.editor.unknown_entity") +
|
||||
">"
|
||||
);
|
||||
};
|
||||
|
||||
export const localizeDeviceAutomationAction = (
|
||||
localize: LocalizeFunc,
|
||||
states: HassEntities,
|
||||
hass: HomeAssistant,
|
||||
entityRegistry: EntityRegistryEntry[],
|
||||
action: DeviceAction
|
||||
): string =>
|
||||
localize(
|
||||
hass.localize(
|
||||
`component.${action.domain}.device_automation.action_type.${action.type}`,
|
||||
{
|
||||
entity_name: getEntityName(
|
||||
localize,
|
||||
states,
|
||||
entityRegistry,
|
||||
action.entity_id
|
||||
),
|
||||
entity_name: getEntityName(hass, entityRegistry, action.entity_id),
|
||||
subtype: action.subtype
|
||||
? localize(
|
||||
? hass.localize(
|
||||
`component.${action.domain}.device_automation.action_subtype.${action.subtype}`
|
||||
) || action.subtype
|
||||
: "",
|
||||
@@ -237,22 +231,16 @@ export const localizeDeviceAutomationAction = (
|
||||
) || (action.subtype ? `"${action.subtype}" ${action.type}` : action.type!);
|
||||
|
||||
export const localizeDeviceAutomationCondition = (
|
||||
localize: LocalizeFunc,
|
||||
states: HassEntities,
|
||||
hass: HomeAssistant,
|
||||
entityRegistry: EntityRegistryEntry[],
|
||||
condition: DeviceCondition
|
||||
): string =>
|
||||
localize(
|
||||
hass.localize(
|
||||
`component.${condition.domain}.device_automation.condition_type.${condition.type}`,
|
||||
{
|
||||
entity_name: getEntityName(
|
||||
localize,
|
||||
states,
|
||||
entityRegistry,
|
||||
condition.entity_id
|
||||
),
|
||||
entity_name: getEntityName(hass, entityRegistry, condition.entity_id),
|
||||
subtype: condition.subtype
|
||||
? localize(
|
||||
? hass.localize(
|
||||
`component.${condition.domain}.device_automation.condition_subtype.${condition.subtype}`
|
||||
) || condition.subtype
|
||||
: "",
|
||||
@@ -263,22 +251,16 @@ export const localizeDeviceAutomationCondition = (
|
||||
: condition.type!);
|
||||
|
||||
export const localizeDeviceAutomationTrigger = (
|
||||
localize: LocalizeFunc,
|
||||
states: HassEntities,
|
||||
hass: HomeAssistant,
|
||||
entityRegistry: EntityRegistryEntry[],
|
||||
trigger: DeviceTrigger
|
||||
): string =>
|
||||
localize(
|
||||
hass.localize(
|
||||
`component.${trigger.domain}.device_automation.trigger_type.${trigger.type}`,
|
||||
{
|
||||
entity_name: getEntityName(
|
||||
localize,
|
||||
states,
|
||||
entityRegistry,
|
||||
trigger.entity_id
|
||||
),
|
||||
entity_name: getEntityName(hass, entityRegistry, trigger.entity_id),
|
||||
subtype: trigger.subtype
|
||||
? localize(
|
||||
? hass.localize(
|
||||
`component.${trigger.domain}.device_automation.trigger_subtype.${trigger.subtype}`
|
||||
) || trigger.subtype
|
||||
: "",
|
||||
@@ -287,18 +269,18 @@ export const localizeDeviceAutomationTrigger = (
|
||||
(trigger.subtype ? `"${trigger.subtype}" ${trigger.type}` : trigger.type!);
|
||||
|
||||
export const localizeExtraFieldsComputeLabelCallback =
|
||||
(localize: LocalizeFunc, deviceAutomation: DeviceAutomation) =>
|
||||
(hass: HomeAssistant, deviceAutomation: DeviceAutomation) =>
|
||||
// Returns a callback for ha-form to calculate labels per schema object
|
||||
(schema): string =>
|
||||
localize(
|
||||
hass.localize(
|
||||
`component.${deviceAutomation.domain}.device_automation.extra_fields.${schema.name}`
|
||||
) || schema.name;
|
||||
|
||||
export const localizeExtraFieldsComputeHelperCallback =
|
||||
(localize: LocalizeFunc, deviceAutomation: DeviceAutomation) =>
|
||||
(hass: HomeAssistant, deviceAutomation: DeviceAutomation) =>
|
||||
// Returns a callback for ha-form to calculate helper texts per schema object
|
||||
(schema): string | undefined =>
|
||||
localize(
|
||||
hass.localize(
|
||||
`component.${deviceAutomation.domain}.device_automation.extra_fields_descriptions.${schema.name}`
|
||||
);
|
||||
|
||||
|
||||
@@ -2,23 +2,16 @@ import { computeAreaName } from "../../common/entity/compute_area_name";
|
||||
import { computeDeviceNameDisplay } from "../../common/entity/compute_device_name";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { getDeviceArea } from "../../common/entity/context/get_device_context";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../../components/device/ha-device-picker";
|
||||
import type { PickerComboBoxItem } from "../../components/ha-picker-combo-box";
|
||||
import type { FuseWeightedKey } from "../../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { ConfigEntry } from "../config_entries";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../entity/entity";
|
||||
import type {
|
||||
EntityRegistryDisplayEntry,
|
||||
EntityRegistryEntry,
|
||||
} from "../entity/entity_registry";
|
||||
import { domainToName } from "../integration";
|
||||
import {
|
||||
getDeviceEntityDisplayLookup,
|
||||
type DeviceEntityDisplayLookup,
|
||||
type DeviceRegistryEntry,
|
||||
} from "./device_registry";
|
||||
|
||||
export interface DevicePickerItem extends PickerComboBoxItem {
|
||||
@@ -26,46 +19,6 @@ export interface DevicePickerItem extends PickerComboBoxItem {
|
||||
domain_name?: string;
|
||||
}
|
||||
|
||||
export interface DeviceAreaLabel {
|
||||
areaName?: string;
|
||||
viaDeviceName?: string;
|
||||
viaDeviceAreaName?: string;
|
||||
}
|
||||
|
||||
export const computeDeviceAreaLabel = (
|
||||
device: DeviceRegistryEntry,
|
||||
areas: HomeAssistant["areas"],
|
||||
devices: HomeAssistant["devices"],
|
||||
states: HomeAssistant["states"],
|
||||
localize: LocalizeFunc,
|
||||
language: HomeAssistant["language"],
|
||||
translationMetadata: HomeAssistant["translationMetadata"],
|
||||
viaDeviceEntities?: EntityRegistryEntry[] | EntityRegistryDisplayEntry[]
|
||||
): DeviceAreaLabel => {
|
||||
const area = getDeviceArea(device, areas);
|
||||
|
||||
const viaDevice = device.via_device_id
|
||||
? devices[device.via_device_id]
|
||||
: undefined;
|
||||
const viaDeviceName = viaDevice
|
||||
? computeDeviceNameDisplay(viaDevice, localize, states, viaDeviceEntities)
|
||||
: undefined;
|
||||
const viaDeviceArea = viaDevice ? getDeviceArea(viaDevice, areas) : undefined;
|
||||
const viaDeviceAreaName = viaDeviceArea
|
||||
? computeAreaName(viaDeviceArea)
|
||||
: undefined;
|
||||
|
||||
const isRTL = computeRTL(language, translationMetadata.translations);
|
||||
|
||||
const areaName = area
|
||||
? computeAreaName(area)
|
||||
: viaDeviceAreaName
|
||||
? `${viaDeviceAreaName}${isRTL ? " ◂ " : " ▸ "}${viaDeviceName}`
|
||||
: viaDeviceName || undefined;
|
||||
|
||||
return { areaName, viaDeviceName, viaDeviceAreaName };
|
||||
};
|
||||
|
||||
export const deviceComboBoxKeys: FuseWeightedKey[] = [
|
||||
{
|
||||
name: "search_labels.deviceName",
|
||||
@@ -83,14 +36,6 @@ export const deviceComboBoxKeys: FuseWeightedKey[] = [
|
||||
name: "search_labels.domain",
|
||||
weight: 4,
|
||||
},
|
||||
{
|
||||
name: "search_labels.viaDeviceName",
|
||||
weight: 3,
|
||||
},
|
||||
{
|
||||
name: "search_labels.viaDeviceArea",
|
||||
weight: 3,
|
||||
},
|
||||
];
|
||||
|
||||
export const getDevices = (
|
||||
@@ -204,19 +149,9 @@ export const getDevices = (
|
||||
deviceEntityLookup[device.id]
|
||||
);
|
||||
|
||||
const { areaName, viaDeviceName, viaDeviceAreaName } =
|
||||
computeDeviceAreaLabel(
|
||||
device,
|
||||
hass.areas,
|
||||
hass.devices,
|
||||
hass.states,
|
||||
hass.localize,
|
||||
hass.language,
|
||||
hass.translationMetadata,
|
||||
device.via_device_id
|
||||
? deviceEntityLookup[device.via_device_id]
|
||||
: undefined
|
||||
);
|
||||
const area = getDeviceArea(device, hass.areas);
|
||||
|
||||
const areaName = area ? computeAreaName(area) : undefined;
|
||||
|
||||
const configEntry = device.primary_config_entry
|
||||
? configEntryLookup?.[device.primary_config_entry]
|
||||
@@ -239,8 +174,6 @@ export const getDevices = (
|
||||
areaName: areaName || null,
|
||||
domain: domain || null,
|
||||
domainName: domainName || null,
|
||||
viaDeviceName: viaDeviceName || null,
|
||||
viaDeviceArea: viaDeviceAreaName || null,
|
||||
},
|
||||
sorting_label: [primary, areaName, domainName].filter(Boolean).join("_"),
|
||||
};
|
||||
|
||||
@@ -148,7 +148,6 @@ export interface GridSourceTypeEnergyPreference {
|
||||
power_config?: PowerConfig;
|
||||
|
||||
cost_adjustment_day: number;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface SolarSourceTypeEnergyPreference {
|
||||
@@ -157,7 +156,6 @@ export interface SolarSourceTypeEnergyPreference {
|
||||
stat_energy_from: string;
|
||||
stat_rate?: string;
|
||||
config_entry_solar_forecast: string[] | null;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface BatterySourceTypeEnergyPreference {
|
||||
@@ -167,7 +165,6 @@ export interface BatterySourceTypeEnergyPreference {
|
||||
stat_rate?: string; // always available if power_config is set
|
||||
power_config?: PowerConfig;
|
||||
stat_soc?: string;
|
||||
name?: string;
|
||||
}
|
||||
export interface GasSourceTypeEnergyPreference {
|
||||
type: "gas";
|
||||
|
||||
@@ -6,8 +6,10 @@ export const UNKNOWN = "unknown";
|
||||
export const ON = "on";
|
||||
export const OFF = "off";
|
||||
|
||||
export const UNAVAILABLE_STATES = [UNAVAILABLE, UNKNOWN] as const;
|
||||
export const OFF_STATES = [UNAVAILABLE, UNKNOWN, OFF] as const;
|
||||
|
||||
export const isUnavailableState = arrayLiteralIncludes(UNAVAILABLE_STATES);
|
||||
export const isOffState = arrayLiteralIncludes(OFF_STATES);
|
||||
|
||||
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
||||
|
||||
@@ -161,10 +161,6 @@ export interface VacuumEntityOptions {
|
||||
last_seen_segments?: Segment[];
|
||||
}
|
||||
|
||||
export interface DeviceTrackerEntityOptions {
|
||||
associated_zone?: string | null;
|
||||
}
|
||||
|
||||
export interface EntityRegistryOptions {
|
||||
number?: NumberEntityOptions;
|
||||
sensor?: SensorEntityOptions;
|
||||
@@ -176,7 +172,6 @@ export interface EntityRegistryOptions {
|
||||
cover?: CoverEntityOptions;
|
||||
valve?: ValveEntityOptions;
|
||||
vacuum?: VacuumEntityOptions;
|
||||
device_tracker?: DeviceTrackerEntityOptions;
|
||||
switch_as_x?: SwitchAsXEntityOptions;
|
||||
conversation?: Record<string, unknown>;
|
||||
"cloud.alexa"?: Record<string, unknown>;
|
||||
@@ -202,8 +197,7 @@ export interface EntityRegistryEntryUpdateParams {
|
||||
| LightEntityOptions
|
||||
| CoverEntityOptions
|
||||
| ValveEntityOptions
|
||||
| VacuumEntityOptions
|
||||
| DeviceTrackerEntityOptions;
|
||||
| VacuumEntityOptions;
|
||||
aliases?: (string | null)[];
|
||||
labels?: string[];
|
||||
categories?: Record<string, string | null>;
|
||||
|
||||
+199
-97
@@ -1,15 +1,11 @@
|
||||
import { atLeastVersion } from "../../common/config/version";
|
||||
import type { HaFormSchema } from "../../components/ha-form/types";
|
||||
import type {
|
||||
CallWS,
|
||||
HomeAssistant,
|
||||
HomeAssistantApi,
|
||||
TranslationDict,
|
||||
} from "../../types";
|
||||
import type { HomeAssistant, TranslationDict } from "../../types";
|
||||
import { supervisorApiCall } from "../supervisor/common";
|
||||
import type { StoreAddonDetails } from "../supervisor/store";
|
||||
import type { Supervisor, SupervisorArch } from "../supervisor/supervisor";
|
||||
import type { HassioResponse } from "./common";
|
||||
import { extractApiErrorMessage } from "./common";
|
||||
import { extractApiErrorMessage, hassioApiResultExtractor } from "./common";
|
||||
|
||||
export type AddonCapability = Exclude<
|
||||
keyof TranslationDict["ui"]["panel"]["config"]["apps"]["dashboard"]["capability"],
|
||||
@@ -147,38 +143,57 @@ export interface HassioAddonSetOptionParams {
|
||||
}
|
||||
|
||||
export const reloadHassioAddons = async (hass: HomeAssistant) => {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/addons/reload",
|
||||
method: "post",
|
||||
});
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/addons/reload",
|
||||
method: "post",
|
||||
});
|
||||
return;
|
||||
}
|
||||
await hass.callApi<HassioResponse<void>>("POST", `hassio/addons/reload`);
|
||||
};
|
||||
|
||||
export const fetchHassioAddonsInfo = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<HassioAddonsInfo> => {
|
||||
return hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/addons",
|
||||
method: "get",
|
||||
});
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: "/addons",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioAddonsInfo>>("GET", `hassio/addons`)
|
||||
);
|
||||
};
|
||||
|
||||
export const fetchHassioAddonInfo = async (
|
||||
callWS: CallWS,
|
||||
hass: HomeAssistant,
|
||||
slug: string
|
||||
): Promise<HassioAddonDetails> => {
|
||||
return callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/info`,
|
||||
method: "get",
|
||||
});
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/info`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioAddonDetails>>(
|
||||
"GET",
|
||||
`hassio/addons/${slug}/info`
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export const fetchHassioAddonChangelog = async (
|
||||
api: HomeAssistantApi,
|
||||
hass: HomeAssistant,
|
||||
slug: string
|
||||
) => api.callApi<string>("GET", `hassio/addons/${slug}/changelog`);
|
||||
) => hass.callApi<string>("GET", `hassio/addons/${slug}/changelog`);
|
||||
|
||||
export const fetchHassioAddonLogs = async (hass: HomeAssistant, slug: string) =>
|
||||
hass.callApi<string>("GET", `hassio/addons/${slug}/logs`);
|
||||
@@ -189,77 +204,119 @@ export const fetchHassioAddonDocumentation = async (
|
||||
) => hass.callApi<string>("GET", `hassio/addons/${slug}/documentation`);
|
||||
|
||||
export const setHassioAddonOption = async (
|
||||
callWS: CallWS,
|
||||
hass: HomeAssistant,
|
||||
slug: string,
|
||||
data: HassioAddonSetOptionParams
|
||||
) => {
|
||||
const response = await callWS<HassioResponse<any>>({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/options`,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
const response = await hass.callWS<HassioResponse<any>>({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/options`,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
|
||||
if (response.result === "error") {
|
||||
throw Error(extractApiErrorMessage(response));
|
||||
if (response.result === "error") {
|
||||
throw Error(extractApiErrorMessage(response));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
return response;
|
||||
|
||||
return hass.callApi<HassioResponse<any>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}/options`,
|
||||
data
|
||||
);
|
||||
};
|
||||
|
||||
export const validateHassioAddonOption = async (
|
||||
callWS: CallWS,
|
||||
hass: HomeAssistant,
|
||||
slug: string,
|
||||
data?: any
|
||||
): Promise<{ message: string; valid: boolean }> => {
|
||||
return callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/options/validate`,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/options/validate`,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
await hass.callApi<HassioResponse<{ message: string; valid: boolean }>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}/options/validate`
|
||||
)
|
||||
).data;
|
||||
};
|
||||
|
||||
export const startHassioAddon = async (callWS: CallWS, slug: string) => {
|
||||
return callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/start`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
export const startHassioAddon = async (hass: HomeAssistant, slug: string) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/start`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
}
|
||||
|
||||
return hass.callApi<string>("POST", `hassio/addons/${slug}/start`);
|
||||
};
|
||||
|
||||
export const stopHassioAddon = async (callWS: CallWS, slug: string) => {
|
||||
return callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/stop`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
export const stopHassioAddon = async (hass: HomeAssistant, slug: string) => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/stop`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
}
|
||||
|
||||
return hass.callApi<string>("POST", `hassio/addons/${slug}/stop`);
|
||||
};
|
||||
|
||||
export const setHassioAddonSecurity = async (
|
||||
callWS: CallWS,
|
||||
hass: HomeAssistant,
|
||||
slug: string,
|
||||
data: HassioAddonSetSecurityParams
|
||||
) => {
|
||||
await callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/security`,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/security`,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}/security`,
|
||||
data
|
||||
);
|
||||
};
|
||||
|
||||
export const installHassioAddon = async (
|
||||
callWS: CallWS,
|
||||
hass: HomeAssistant,
|
||||
slug: string
|
||||
): Promise<void> => {
|
||||
await callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/install`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/install`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}/install`
|
||||
);
|
||||
};
|
||||
|
||||
export const updateHassioAddon = async (
|
||||
@@ -267,37 +324,74 @@ export const updateHassioAddon = async (
|
||||
slug: string,
|
||||
backup: boolean
|
||||
): Promise<void> => {
|
||||
await hass.callWS({
|
||||
type: "hassio/update/addon",
|
||||
addon: slug,
|
||||
backup: backup,
|
||||
});
|
||||
if (atLeastVersion(hass.config.version, 2025, 2, 0)) {
|
||||
await hass.callWS({
|
||||
type: "hassio/update/addon",
|
||||
addon: slug,
|
||||
backup: backup,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/store/addons/${slug}/update`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
data: { backup },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}/update`,
|
||||
{ backup }
|
||||
);
|
||||
};
|
||||
|
||||
export const restartHassioAddon = async (
|
||||
callWS: CallWS,
|
||||
hass: HomeAssistant,
|
||||
slug: string
|
||||
): Promise<void> => {
|
||||
await callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/restart`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/restart`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}/restart`
|
||||
);
|
||||
};
|
||||
|
||||
export const uninstallHassioAddon = async (
|
||||
callWS: CallWS,
|
||||
hass: HomeAssistant,
|
||||
slug: string,
|
||||
removeData: boolean
|
||||
): Promise<void> => {
|
||||
await callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/uninstall`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
data: { remove_config: removeData },
|
||||
});
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
await hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/uninstall`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
data: { remove_config: removeData },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}/uninstall`,
|
||||
{ remove_config: removeData }
|
||||
);
|
||||
};
|
||||
|
||||
export const fetchAddonInfo = (
|
||||
@@ -313,13 +407,21 @@ export const fetchAddonInfo = (
|
||||
);
|
||||
|
||||
export const rebuildLocalAddon = async (
|
||||
callWS: CallWS,
|
||||
hass: HomeAssistant,
|
||||
slug: string
|
||||
): Promise<void> => {
|
||||
return callWS<undefined>({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/rebuild`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return hass.callWS<undefined>({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/rebuild`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
}
|
||||
return (
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}rebuild`
|
||||
)
|
||||
).data;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { CallWS } from "../../types";
|
||||
import { atLeastVersion } from "../../common/config/version";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
export interface HassioResponse<T> {
|
||||
data: T;
|
||||
@@ -45,12 +46,21 @@ export const ignoreSupervisorError = (error): boolean => {
|
||||
};
|
||||
|
||||
export const fetchHassioStats = async (
|
||||
callWS: CallWS,
|
||||
hass: HomeAssistant,
|
||||
container: string
|
||||
): Promise<HassioStats> => {
|
||||
return callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/${container}/stats`,
|
||||
method: "get",
|
||||
});
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return hass.callWS({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/${container}/stats`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioStats>>(
|
||||
"GET",
|
||||
`hassio/${container}/stats`
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
mdiPlaylistMusic,
|
||||
mdiPlayPause,
|
||||
mdiPodcast,
|
||||
mdiPowerStandby,
|
||||
mdiPower,
|
||||
mdiPowerOff,
|
||||
mdiPowerOn,
|
||||
mdiRepeat,
|
||||
@@ -295,7 +295,7 @@ export const computeMediaControls = (
|
||||
return supportsFeature(stateObj, MediaPlayerEntityFeature.TURN_ON)
|
||||
? [
|
||||
{
|
||||
icon: mdiPowerStandby,
|
||||
icon: mdiPower,
|
||||
action: "turn_on",
|
||||
},
|
||||
]
|
||||
@@ -316,7 +316,7 @@ export const computeMediaControls = (
|
||||
|
||||
if (supportsFeature(stateObj, MediaPlayerEntityFeature.TURN_OFF)) {
|
||||
buttons.push({
|
||||
icon: assumedState ? mdiPowerOff : mdiPowerStandby,
|
||||
icon: assumedState ? mdiPowerOff : mdiPower,
|
||||
action: "turn_off",
|
||||
});
|
||||
}
|
||||
|
||||
+1
-6
@@ -7,18 +7,13 @@ export interface GenericPreview {
|
||||
state: string;
|
||||
attributes: Record<string, any>;
|
||||
error?: string;
|
||||
domain?: string;
|
||||
}
|
||||
|
||||
export const subscribePreviewGeneric = (
|
||||
hass: HomeAssistant,
|
||||
domain: string,
|
||||
flow_id: string,
|
||||
flow_type:
|
||||
| "config_flow"
|
||||
| "options_flow"
|
||||
| "config_subentries_flow"
|
||||
| "repair_flow",
|
||||
flow_type: "config_flow" | "options_flow" | "config_subentries_flow",
|
||||
user_input: Record<string, any>,
|
||||
callback: (preview: GenericPreview) => void
|
||||
): Promise<UnsubscribeFunc> =>
|
||||
|
||||
@@ -36,7 +36,6 @@ export const isMaxMode = arrayLiteralIncludes(MODES_MAX);
|
||||
|
||||
export const baseActionStruct = object({
|
||||
alias: optional(string()),
|
||||
comment: optional(string()),
|
||||
continue_on_error: optional(boolean()),
|
||||
enabled: optional(boolean()),
|
||||
});
|
||||
@@ -106,7 +105,6 @@ export interface Field {
|
||||
|
||||
interface BaseAction {
|
||||
alias?: string;
|
||||
comment?: string;
|
||||
continue_on_error?: boolean;
|
||||
enabled?: boolean;
|
||||
}
|
||||
@@ -197,7 +195,6 @@ export interface ForEachRepeat extends BaseRepeat {
|
||||
|
||||
export interface Option {
|
||||
alias?: string;
|
||||
comment?: string;
|
||||
conditions: string | Condition[];
|
||||
sequence: Action | Action[];
|
||||
}
|
||||
|
||||
@@ -335,8 +335,7 @@ const tryDescribeAction = <T extends ActionType>(
|
||||
);
|
||||
}
|
||||
const localized = localizeDeviceAutomationAction(
|
||||
hass.localize,
|
||||
hass.states,
|
||||
hass,
|
||||
entityRegistry,
|
||||
config
|
||||
);
|
||||
|
||||
@@ -641,7 +641,7 @@ export const expandLabelTarget = (
|
||||
if (
|
||||
device.labels.includes(labelId) &&
|
||||
deviceMeetsTargetSelector(
|
||||
hass.states,
|
||||
hass,
|
||||
Object.values(entities),
|
||||
device,
|
||||
targetSelector,
|
||||
@@ -708,7 +708,7 @@ export const expandAreaTarget = (
|
||||
if (
|
||||
device.area_id === areaId &&
|
||||
deviceMeetsTargetSelector(
|
||||
hass.states,
|
||||
hass,
|
||||
Object.values(entities),
|
||||
device,
|
||||
targetSelector,
|
||||
@@ -768,7 +768,7 @@ export const areaMeetsTargetSelector = (
|
||||
if (
|
||||
device.area_id === areaId &&
|
||||
deviceMeetsTargetSelector(
|
||||
hass.states,
|
||||
hass,
|
||||
Object.values(entities),
|
||||
device,
|
||||
targetSelector,
|
||||
@@ -798,7 +798,7 @@ export const areaMeetsTargetSelector = (
|
||||
};
|
||||
|
||||
export const deviceMeetsTargetSelector = (
|
||||
states: HomeAssistant["states"],
|
||||
hass: HomeAssistant,
|
||||
entityRegistry: EntityRegistryDisplayEntry[] | EntityRegistryEntry[],
|
||||
device: DeviceRegistryEntry,
|
||||
targetSelector: TargetSelector,
|
||||
@@ -822,7 +822,7 @@ export const deviceMeetsTargetSelector = (
|
||||
(reg) => reg.device_id === device.id
|
||||
);
|
||||
return entities.some((entity) => {
|
||||
const entityState = states[entity.entity_id];
|
||||
const entityState = hass.states[entity.entity_id];
|
||||
return entityMeetsTargetSelector(
|
||||
entityState,
|
||||
targetSelector,
|
||||
|
||||
+3
-3
@@ -3,7 +3,7 @@ import { ensureArray } from "../common/array/ensure-array";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../components/device/ha-device-picker";
|
||||
import type { PickerComboBoxItem } from "../components/ha-picker-combo-box";
|
||||
import type { CallWS, HomeAssistant } from "../types";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { AreaRegistryEntry } from "./area/area_registry";
|
||||
import type { FloorComboBoxItem } from "./area_floor_picker";
|
||||
import type { DevicePickerItem } from "./device/device_picker";
|
||||
@@ -47,12 +47,12 @@ export interface ExtractFromTargetResultReferenced {
|
||||
}
|
||||
|
||||
export const extractFromTarget = async (
|
||||
callWS: CallWS,
|
||||
hass: HomeAssistant,
|
||||
target: HassServiceTarget,
|
||||
expandGroup = false,
|
||||
primaryEntitiesOnly = true
|
||||
) =>
|
||||
callWS<ExtractFromTargetResult>({
|
||||
hass.callWS<ExtractFromTargetResult>({
|
||||
type: "extract_from_target",
|
||||
target,
|
||||
expand_group: expandGroup,
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import type { HomeAssistant, ServiceCallResponse } from "../types";
|
||||
import { UNAVAILABLE } from "./entity/entity";
|
||||
import { isUnavailableState } from "./entity/entity";
|
||||
|
||||
export interface TodoList {
|
||||
entity_id: string;
|
||||
@@ -49,7 +49,7 @@ export const getTodoLists = (
|
||||
.filter(
|
||||
(entityId) =>
|
||||
computeDomain(entityId) === "todo" &&
|
||||
hass.states[entityId].state !== UNAVAILABLE &&
|
||||
!isUnavailableState(hass.states[entityId].state) &&
|
||||
(includeHidden || hass.entities[entityId]?.hidden !== true)
|
||||
)
|
||||
.map((entityId) => ({
|
||||
|
||||
@@ -8,7 +8,6 @@ import type {
|
||||
Trigger,
|
||||
TriggerList,
|
||||
} from "./automation";
|
||||
import { flattenTriggers } from "./automation";
|
||||
import type { Selector, TargetSelector } from "./selector";
|
||||
|
||||
export const TRIGGER_COLLECTIONS: AutomationElementGroupCollection[] = [
|
||||
@@ -57,49 +56,6 @@ export const TRIGGER_COLLECTIONS: AutomationElementGroupCollection[] = [
|
||||
export const isTriggerList = (trigger: Trigger): trigger is TriggerList =>
|
||||
"triggers" in trigger;
|
||||
|
||||
export const getTriggerIds = (triggers: Trigger[]): string[] =>
|
||||
flattenTriggers(triggers)
|
||||
.map((trigger) => trigger.id)
|
||||
.filter((id): id is string => !!id);
|
||||
|
||||
export const getNextNumericTriggerId = (triggers: Trigger[]): string => {
|
||||
let max = 0;
|
||||
for (const id of getTriggerIds(triggers)) {
|
||||
const num = Number(id);
|
||||
if (Number.isInteger(num) && num > max) {
|
||||
max = num;
|
||||
}
|
||||
}
|
||||
return String(max + 1);
|
||||
};
|
||||
|
||||
const computeUniqueId = (id: string, existing: Set<string>): string => {
|
||||
if (!existing.has(id)) {
|
||||
return id;
|
||||
}
|
||||
|
||||
// Split into a base and a trailing integer suffix so we can bump the
|
||||
// suffix on collision (e.g. "foo2" -> "foo3"); if there's no trailing
|
||||
// digit we start at 2 ("foo" -> "foo2").
|
||||
const match = id.match(/^(.*?)(\d+)$/);
|
||||
let base: string;
|
||||
let num: number;
|
||||
if (match) {
|
||||
base = match[1];
|
||||
num = Number(match[2]) + 1;
|
||||
} else {
|
||||
base = id;
|
||||
num = 2;
|
||||
}
|
||||
while (existing.has(`${base}${num}`)) {
|
||||
num++;
|
||||
}
|
||||
return `${base}${num}`;
|
||||
};
|
||||
|
||||
export const getUniqueTriggerId = (id: string, triggers: Trigger[]): string =>
|
||||
computeUniqueId(id, new Set(getTriggerIds(triggers)));
|
||||
|
||||
export interface TriggerDescription {
|
||||
target?: TargetSelector["target"];
|
||||
fields: Record<
|
||||
|
||||
+2
-9
@@ -29,13 +29,6 @@ import type {
|
||||
import type { SVGTemplateResult, TemplateResult } from "lit";
|
||||
import { css, html, svg } from "lit";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import {
|
||||
UNIT_HPA,
|
||||
UNIT_IN,
|
||||
UNIT_INHG,
|
||||
UNIT_KM,
|
||||
UNIT_MM,
|
||||
} from "../common/const";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import { round } from "../common/number/round";
|
||||
import "../components/ha-svg-icon";
|
||||
@@ -252,12 +245,12 @@ export const getWeatherUnit = (
|
||||
case "precipitation":
|
||||
return (
|
||||
stateObj.attributes.precipitation_unit ||
|
||||
(lengthUnit === UNIT_KM ? UNIT_MM : UNIT_IN)
|
||||
(lengthUnit === "km" ? "mm" : "in")
|
||||
);
|
||||
case "pressure":
|
||||
return (
|
||||
stateObj.attributes.pressure_unit ||
|
||||
(lengthUnit === UNIT_KM ? UNIT_HPA : UNIT_INHG)
|
||||
(lengthUnit === "km" ? "hPa" : "inHg")
|
||||
);
|
||||
case "apparent_temperature":
|
||||
case "dew_point":
|
||||
|
||||
@@ -24,7 +24,6 @@ interface TemplatePreviewState {
|
||||
state: string;
|
||||
attributes: Record<string, any>;
|
||||
listeners: TemplateListeners;
|
||||
domain?: string;
|
||||
}
|
||||
|
||||
interface TemplatePreviewError {
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export type ZwaveCredentialType =
|
||||
| "pin_code"
|
||||
| "password"
|
||||
| "rfid_code"
|
||||
| "ble"
|
||||
| "nfc"
|
||||
| "uwb"
|
||||
| "eye_biometric"
|
||||
| "face_biometric"
|
||||
| "finger_biometric"
|
||||
| "hand_biometric"
|
||||
| "unspecified_biometric"
|
||||
| "desfire";
|
||||
|
||||
export const ENTERABLE_ZWAVE_CREDENTIAL_TYPES: readonly ZwaveCredentialType[] =
|
||||
["pin_code", "password"];
|
||||
|
||||
// UI surfaces only general + disposable to stay aligned with Matter lock UX.
|
||||
// Other types (programming, duress, non_access, remote_only, expiring) are
|
||||
// defined in translations for display in existing-user rows, but are not
|
||||
// selectable here.
|
||||
export const SIMPLE_USER_TYPES: readonly string[] = ["general", "disposable"];
|
||||
|
||||
// Fallback bounds when a lock advertises an enterable type without
|
||||
// per-type min/max — values mirror Z-Wave spec defaults.
|
||||
export const DEFAULT_CREDENTIAL_MIN_LENGTH = 4;
|
||||
export const DEFAULT_CREDENTIAL_MAX_LENGTH = 10;
|
||||
|
||||
export type CredentialErrorCode =
|
||||
| "required"
|
||||
| "length"
|
||||
| "pin_digits_only"
|
||||
| "";
|
||||
|
||||
export const enterableCredentialTypes = (
|
||||
capabilities: ZwaveCredentialCapabilities
|
||||
): ZwaveCredentialType[] => {
|
||||
if (!capabilities.supported_credential_types) {
|
||||
return [];
|
||||
}
|
||||
return ENTERABLE_ZWAVE_CREDENTIAL_TYPES.filter(
|
||||
(type) => type in capabilities.supported_credential_types
|
||||
);
|
||||
};
|
||||
|
||||
export const compatibleUserTypes = (
|
||||
capabilities: ZwaveCredentialCapabilities
|
||||
): string[] => {
|
||||
const supported = capabilities.supported_user_types ?? [];
|
||||
return SIMPLE_USER_TYPES.filter((t) => supported.includes(t));
|
||||
};
|
||||
|
||||
export const canAddZwaveUser = (
|
||||
capabilities: ZwaveCredentialCapabilities
|
||||
): boolean =>
|
||||
enterableCredentialTypes(capabilities).length > 0 &&
|
||||
compatibleUserTypes(capabilities).length > 0;
|
||||
|
||||
export const getCredentialError = (
|
||||
data: string,
|
||||
type: ZwaveCredentialType | "",
|
||||
capability: ZwaveCredentialTypeCapability | undefined
|
||||
): CredentialErrorCode => {
|
||||
if (!data) {
|
||||
return "required";
|
||||
}
|
||||
const minLength = capability?.min_length ?? DEFAULT_CREDENTIAL_MIN_LENGTH;
|
||||
const maxLength = capability?.max_length ?? DEFAULT_CREDENTIAL_MAX_LENGTH;
|
||||
if (data.length < minLength || data.length > maxLength) {
|
||||
return "length";
|
||||
}
|
||||
if (type === "pin_code" && !/^\d+$/.test(data)) {
|
||||
return "pin_digits_only";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
export interface ZwaveCredentialTypeCapability {
|
||||
num_slots: number;
|
||||
min_length: number;
|
||||
max_length: number;
|
||||
supports_learn: boolean;
|
||||
}
|
||||
|
||||
export interface ZwaveCredentialCapabilities {
|
||||
supports_user_management: boolean;
|
||||
max_users: number;
|
||||
supported_user_types: string[];
|
||||
max_user_name_length: number;
|
||||
supported_credential_rules: string[];
|
||||
supported_credential_types: Partial<
|
||||
Record<ZwaveCredentialType, ZwaveCredentialTypeCapability>
|
||||
>;
|
||||
}
|
||||
|
||||
export interface ZwaveCredential {
|
||||
type: ZwaveCredentialType;
|
||||
slot: number;
|
||||
}
|
||||
|
||||
export interface ZwaveUser {
|
||||
user_id: number;
|
||||
user_name: string | null;
|
||||
active: boolean;
|
||||
user_type: string;
|
||||
credential_rule: string | null;
|
||||
credentials: ZwaveCredential[];
|
||||
}
|
||||
|
||||
export interface ZwaveUsersResponse {
|
||||
max_users: number;
|
||||
users: ZwaveUser[];
|
||||
}
|
||||
|
||||
export interface SetZwaveUserParams {
|
||||
user_id?: number;
|
||||
user_name?: string | null;
|
||||
user_type?: string;
|
||||
credential_rule?: string;
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
export interface SetZwaveUserResult {
|
||||
user_id: number;
|
||||
}
|
||||
|
||||
export interface SetZwaveCredentialParams {
|
||||
user_id: number;
|
||||
credential_type: ZwaveCredentialType;
|
||||
credential_data: string;
|
||||
credential_slot?: number;
|
||||
}
|
||||
|
||||
export interface SetZwaveCredentialResult {
|
||||
credential_slot: number;
|
||||
user_id: number;
|
||||
}
|
||||
|
||||
export interface DeleteZwaveCredentialParams {
|
||||
user_id: number;
|
||||
credential_type: ZwaveCredentialType;
|
||||
credential_slot: number;
|
||||
}
|
||||
|
||||
// The Z-Wave services key their response by entity_id to support multi-target
|
||||
// calls. The frontend only ever calls them with a single lock entity, so we
|
||||
// expect exactly that key. Anything else (no response, mismatched key) is a
|
||||
// backend contract violation — surface it as a localized error rather than
|
||||
// letting `cannot read property of undefined` bubble up.
|
||||
const unwrapEntityResponse = <T>(
|
||||
hass: HomeAssistant,
|
||||
response: Record<string, T> | undefined,
|
||||
entity_id: string
|
||||
): T => {
|
||||
const value = response?.[entity_id];
|
||||
if (value === undefined) {
|
||||
throw new Error(
|
||||
hass.localize(
|
||||
"ui.panel.config.zwave_js.credentials.errors.empty_response"
|
||||
)
|
||||
);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const callCredentialService = async <T>(
|
||||
hass: HomeAssistant,
|
||||
service: string,
|
||||
entity_id: string,
|
||||
params: Record<string, unknown> = {}
|
||||
): Promise<T> => {
|
||||
// notifyOnError=false — callers surface errors in-dialog instead.
|
||||
const result = await hass.callService<Record<string, T>>(
|
||||
"zwave_js",
|
||||
service,
|
||||
params,
|
||||
{ entity_id },
|
||||
false,
|
||||
true
|
||||
);
|
||||
return unwrapEntityResponse(hass, result.response, entity_id);
|
||||
};
|
||||
|
||||
export const getZwaveCredentialCapabilities = (
|
||||
hass: HomeAssistant,
|
||||
entity_id: string
|
||||
): Promise<ZwaveCredentialCapabilities> =>
|
||||
callCredentialService<ZwaveCredentialCapabilities>(
|
||||
hass,
|
||||
"get_credential_capabilities",
|
||||
entity_id
|
||||
);
|
||||
|
||||
export const getZwaveUsers = (
|
||||
hass: HomeAssistant,
|
||||
entity_id: string
|
||||
): Promise<ZwaveUsersResponse> =>
|
||||
callCredentialService<ZwaveUsersResponse>(hass, "get_users", entity_id);
|
||||
|
||||
export const setZwaveUser = async (
|
||||
hass: HomeAssistant,
|
||||
entity_id: string,
|
||||
params: SetZwaveUserParams
|
||||
): Promise<SetZwaveUserResult> => {
|
||||
// notifyOnError=false — caller surfaces errors in-dialog instead.
|
||||
const result = await hass.callService<Record<string, SetZwaveUserResult>>(
|
||||
"zwave_js",
|
||||
"set_user",
|
||||
params,
|
||||
{ entity_id },
|
||||
false,
|
||||
true
|
||||
);
|
||||
return unwrapEntityResponse(hass, result.response, entity_id);
|
||||
};
|
||||
|
||||
export const deleteZwaveUser = (
|
||||
hass: HomeAssistant,
|
||||
entity_id: string,
|
||||
user_id: number
|
||||
) =>
|
||||
hass.callService(
|
||||
"zwave_js",
|
||||
"delete_user",
|
||||
{ user_id },
|
||||
{ entity_id },
|
||||
false
|
||||
);
|
||||
|
||||
export const deleteZwaveAllUsers = (hass: HomeAssistant, entity_id: string) =>
|
||||
hass.callService("zwave_js", "delete_all_users", {}, { entity_id }, false);
|
||||
|
||||
export const setZwaveCredential = async (
|
||||
hass: HomeAssistant,
|
||||
entity_id: string,
|
||||
params: SetZwaveCredentialParams
|
||||
): Promise<SetZwaveCredentialResult> => {
|
||||
// notifyOnError=false — caller surfaces errors in-dialog instead.
|
||||
const result = await hass.callService<
|
||||
Record<string, SetZwaveCredentialResult>
|
||||
>("zwave_js", "set_credential", params, { entity_id }, false, true);
|
||||
return unwrapEntityResponse(hass, result.response, entity_id);
|
||||
};
|
||||
|
||||
export const deleteZwaveCredential = (
|
||||
hass: HomeAssistant,
|
||||
entity_id: string,
|
||||
params: DeleteZwaveCredentialParams
|
||||
) =>
|
||||
hass.callService(
|
||||
"zwave_js",
|
||||
"delete_credential",
|
||||
params,
|
||||
{ entity_id },
|
||||
false
|
||||
);
|
||||
@@ -18,7 +18,7 @@ import "../../../components/ha-slider";
|
||||
import "../../../components/ha-time-input";
|
||||
import "../../../components/input/ha-input";
|
||||
import { isTiltOnly } from "../../../data/cover";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity/entity";
|
||||
import { isUnavailableState, UNAVAILABLE } from "../../../data/entity/entity";
|
||||
import type { ImageEntity } from "../../../data/image";
|
||||
import { computeImageUrl } from "../../../data/image";
|
||||
import "../../../panels/lovelace/components/hui-timestamp-display";
|
||||
@@ -108,13 +108,14 @@ class EntityPreviewRow extends LitElement {
|
||||
|
||||
private _renderEntityState(stateObj: HassEntity): TemplateResult | string {
|
||||
const domain = stateObj.entity_id.split(".", 1)[0];
|
||||
const disabled = stateObj.state === UNAVAILABLE;
|
||||
const noValue =
|
||||
stateObj.state === UNAVAILABLE || stateObj.state === UNKNOWN;
|
||||
|
||||
if (domain === "button") {
|
||||
return html`
|
||||
<ha-button appearance="plain" size="small" .disabled=${disabled}>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
.disabled=${isUnavailableState(stateObj.state)}
|
||||
>
|
||||
${this.hass.localize("ui.card.button.press")}
|
||||
</ha-button>
|
||||
`;
|
||||
@@ -150,15 +151,19 @@ class EntityPreviewRow extends LitElement {
|
||||
return html`
|
||||
<ha-date-input
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${disabled}
|
||||
.value=${noValue ? undefined : stateObj.state}
|
||||
.disabled=${isUnavailableState(stateObj.state)}
|
||||
.value=${isUnavailableState(stateObj.state)
|
||||
? undefined
|
||||
: stateObj.state}
|
||||
>
|
||||
</ha-date-input>
|
||||
`;
|
||||
}
|
||||
|
||||
if (domain === "datetime") {
|
||||
const dateObj = noValue ? undefined : new Date(stateObj.state);
|
||||
const dateObj = isUnavailableState(stateObj.state)
|
||||
? undefined
|
||||
: new Date(stateObj.state);
|
||||
const time = dateObj ? format(dateObj, "HH:mm:ss") : undefined;
|
||||
const date = dateObj ? format(dateObj, "yyyy-MM-dd") : undefined;
|
||||
return html`
|
||||
@@ -167,12 +172,12 @@ class EntityPreviewRow extends LitElement {
|
||||
.label=${computeStateName(stateObj)}
|
||||
.locale=${this.hass.locale}
|
||||
.value=${date}
|
||||
.disabled=${disabled}
|
||||
.disabled=${isUnavailableState(stateObj.state)}
|
||||
>
|
||||
</ha-date-input>
|
||||
<ha-time-input
|
||||
.value=${time}
|
||||
.disabled=${disabled}
|
||||
.disabled=${isUnavailableState(stateObj.state)}
|
||||
.locale=${this.hass.locale}
|
||||
></ha-time-input>
|
||||
</div>
|
||||
@@ -182,7 +187,7 @@ class EntityPreviewRow extends LitElement {
|
||||
if (domain === "event") {
|
||||
return html`
|
||||
<div class="when">
|
||||
${noValue
|
||||
${isUnavailableState(stateObj.state)
|
||||
? this.hass.formatEntityState(stateObj)
|
||||
: html`<hui-timestamp-display
|
||||
.hass=${this.hass}
|
||||
@@ -191,7 +196,7 @@ class EntityPreviewRow extends LitElement {
|
||||
></hui-timestamp-display>`}
|
||||
</div>
|
||||
<div class="what">
|
||||
${noValue
|
||||
${isUnavailableState(stateObj.state)
|
||||
? nothing
|
||||
: this.hass.formatEntityAttributeValue(stateObj, "event_type")}
|
||||
</div>
|
||||
@@ -201,7 +206,9 @@ class EntityPreviewRow extends LitElement {
|
||||
const toggleDomains = ["fan", "light", "remote", "siren", "switch"];
|
||||
if (toggleDomains.includes(domain)) {
|
||||
const showToggle =
|
||||
stateObj.state === "on" || stateObj.state === "off" || noValue;
|
||||
stateObj.state === "on" ||
|
||||
stateObj.state === "off" ||
|
||||
isUnavailableState(stateObj.state);
|
||||
return html`
|
||||
${showToggle
|
||||
? html`
|
||||
@@ -234,7 +241,7 @@ class EntityPreviewRow extends LitElement {
|
||||
if (domain === "lock") {
|
||||
return html`
|
||||
<ha-button
|
||||
.disabled=${disabled}
|
||||
.disabled=${isUnavailableState(stateObj.state)}
|
||||
class="text-content"
|
||||
appearance="plain"
|
||||
size="small"
|
||||
@@ -259,7 +266,7 @@ class EntityPreviewRow extends LitElement {
|
||||
<div class="numberflex">
|
||||
<ha-slider
|
||||
labeled
|
||||
.disabled=${disabled}
|
||||
.disabled=${stateObj.state === UNAVAILABLE}
|
||||
.step=${Number(stateObj.attributes.step)}
|
||||
.min=${Number(stateObj.attributes.min)}
|
||||
.max=${Number(stateObj.attributes.max)}
|
||||
@@ -273,7 +280,7 @@ class EntityPreviewRow extends LitElement {
|
||||
: html`<div class="numberflex numberstate">
|
||||
<ha-input
|
||||
auto-validate
|
||||
.disabled=${disabled}
|
||||
.disabled=${stateObj.state === UNAVAILABLE}
|
||||
pattern="[0-9]+([\\.][0-9]+)?"
|
||||
.step=${Number(stateObj.attributes.step)}
|
||||
.min=${Number(stateObj.attributes.min)}
|
||||
@@ -296,7 +303,7 @@ class EntityPreviewRow extends LitElement {
|
||||
<ha-select
|
||||
.label=${computeStateName(stateObj)}
|
||||
.value=${stateObj.state}
|
||||
.disabled=${disabled}
|
||||
.disabled=${stateObj.state === UNAVAILABLE}
|
||||
.options=${stateObj.attributes.options?.map((option) => ({
|
||||
value: option,
|
||||
label: this.hass!.formatEntityState(stateObj, option),
|
||||
@@ -310,7 +317,7 @@ class EntityPreviewRow extends LitElement {
|
||||
const showSensor =
|
||||
SENSOR_TIMESTAMP_DEVICE_CLASSES.includes(
|
||||
stateObj.attributes.device_class
|
||||
) && !noValue;
|
||||
) && !isUnavailableState(stateObj.state);
|
||||
return html`
|
||||
${showSensor
|
||||
? html`
|
||||
@@ -332,7 +339,7 @@ class EntityPreviewRow extends LitElement {
|
||||
return html`
|
||||
<ha-input
|
||||
.label=${computeStateName(stateObj)}
|
||||
.disabled=${disabled}
|
||||
.disabled=${isUnavailableState(stateObj.state)}
|
||||
.value=${stateObj.state}
|
||||
.minlength=${stateObj.attributes.min}
|
||||
.maxlength=${stateObj.attributes.max}
|
||||
@@ -347,9 +354,11 @@ class EntityPreviewRow extends LitElement {
|
||||
if (domain === "time") {
|
||||
return html`
|
||||
<ha-time-input
|
||||
.value=${noValue ? undefined : stateObj.state}
|
||||
.value=${isUnavailableState(stateObj.state)
|
||||
? undefined
|
||||
: stateObj.state}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${disabled}
|
||||
.disabled=${isUnavailableState(stateObj.state)}
|
||||
></ha-time-input>
|
||||
`;
|
||||
}
|
||||
@@ -357,7 +366,7 @@ class EntityPreviewRow extends LitElement {
|
||||
if (domain === "weather") {
|
||||
return html`
|
||||
<div>
|
||||
${noValue ||
|
||||
${isUnavailableState(stateObj.state) ||
|
||||
stateObj.attributes.temperature === undefined ||
|
||||
stateObj.attributes.temperature === null
|
||||
? this.hass.formatEntityState(stateObj)
|
||||
|
||||
@@ -65,7 +65,7 @@ export class FlowPreviewGeneric extends LitElement {
|
||||
}
|
||||
const now = new Date().toISOString();
|
||||
this._preview = {
|
||||
entity_id: `${preview.domain ?? this.stepId}.___flow_preview___`,
|
||||
entity_id: `${this.stepId}.___flow_preview___`,
|
||||
last_changed: now,
|
||||
last_updated: now,
|
||||
context: { id: "", parent_id: null, user_id: null },
|
||||
@@ -85,8 +85,7 @@ export class FlowPreviewGeneric extends LitElement {
|
||||
if (
|
||||
this.flowType !== "config_flow" &&
|
||||
this.flowType !== "options_flow" &&
|
||||
this.flowType !== "config_subentries_flow" &&
|
||||
this.flowType !== "repair_flow"
|
||||
this.flowType !== "config_subentries_flow"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ class FlowPreviewTemplate extends LitElement {
|
||||
this._listeners = preview.listeners;
|
||||
const now = new Date().toISOString();
|
||||
this._preview = {
|
||||
entity_id: `${preview.domain ?? this.stepId}.___flow_preview___`,
|
||||
entity_id: `${this.stepId}.___flow_preview___`,
|
||||
last_changed: now,
|
||||
last_updated: now,
|
||||
context: { id: "", parent_id: null, user_id: null },
|
||||
|
||||
@@ -9,8 +9,6 @@ import "../../components/ha-dialog";
|
||||
import "../../components/ha-dialog-footer";
|
||||
import "../../components/ha-dialog-header";
|
||||
import "../../components/ha-svg-icon";
|
||||
import "../../components/ha-textarea";
|
||||
import type { HaTextArea } from "../../components/ha-textarea";
|
||||
import "../../components/input/ha-input";
|
||||
import type { HaInput } from "../../components/input/ha-input";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
@@ -30,7 +28,7 @@ class DialogBox extends LitElement {
|
||||
|
||||
@state() private _validInput = true;
|
||||
|
||||
@query("ha-input, ha-textarea") private _textField?: HaInput | HaTextArea;
|
||||
@query("ha-input") private _textField?: HaInput;
|
||||
|
||||
private _closePromise?: Promise<void>;
|
||||
|
||||
@@ -111,7 +109,7 @@ class DialogBox extends LitElement {
|
||||
</ha-dialog-header>
|
||||
<div id="dialog-box-description">
|
||||
${this._params.text ? html` <p>${this._params.text}</p> ` : ""}
|
||||
${this._params.prompt && !this._params.multiline
|
||||
${this._params.prompt
|
||||
? html`
|
||||
<ha-input
|
||||
autofocus
|
||||
@@ -133,19 +131,7 @@ class DialogBox extends LitElement {
|
||||
: nothing}
|
||||
</ha-input>
|
||||
`
|
||||
: this._params.prompt && this._params.multiline
|
||||
? html`
|
||||
<ha-textarea
|
||||
resize="auto"
|
||||
autofocus
|
||||
.value=${this._params.defaultValue}
|
||||
.placeholder=${this._params.placeholder}
|
||||
.label=${this._params.inputLabel}
|
||||
.disabled=${this._loading}
|
||||
@input=${this._validateInput}
|
||||
></ha-textarea>
|
||||
`
|
||||
: nothing}
|
||||
: nothing}
|
||||
</div>
|
||||
<ha-dialog-footer slot="footer">
|
||||
${confirmPrompt
|
||||
|
||||
@@ -33,7 +33,6 @@ export interface PromptDialogParams extends BaseDialogBoxParams {
|
||||
inputMin?: number | string;
|
||||
inputMax?: number | string;
|
||||
action?: (value?: string) => Promise<void>;
|
||||
multiline?: boolean;
|
||||
}
|
||||
|
||||
export interface DialogBoxParams
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
import { navigate } from "../../common/navigate";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { createSearchParam } from "../../common/url/search-params";
|
||||
import type { SingleHassServiceTarget } from "../../data/target";
|
||||
import {
|
||||
ADD_AUTOMATION_ELEMENT_AREA_TARGET_PARAM,
|
||||
ADD_AUTOMATION_ELEMENT_DEVICE_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 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 name of the action */
|
||||
name: string;
|
||||
/** Optional translated description of the action */
|
||||
description?: string;
|
||||
/** MDI icon name (e.g., "mdi:car") */
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export interface DefaultEntityAddToAction extends BaseEntityAddToAction {
|
||||
/** Type of action handled in the frontend */
|
||||
type: "default";
|
||||
/** Stable key used to resolve the action handler */
|
||||
key: AddToActionKey;
|
||||
}
|
||||
|
||||
export interface ExternalEntityAddToAction extends BaseEntityAddToAction {
|
||||
/** Type of action. External is handled by external apps instead of in the frontend */
|
||||
type: "external";
|
||||
/** Opaque payload for external action handling */
|
||||
payload?: string;
|
||||
}
|
||||
|
||||
export type EntityAddToAction =
|
||||
| DefaultEntityAddToAction
|
||||
| ExternalEntityAddToAction;
|
||||
|
||||
export type EntityAddToActions = EntityAddToAction[];
|
||||
|
||||
interface ActionDefinition {
|
||||
translation_key: AddToActionKey;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_ACTION_DEFS: ActionDefinition[] = [
|
||||
{
|
||||
translation_key: "automation_trigger",
|
||||
icon: "mdi:robot-outline",
|
||||
},
|
||||
{
|
||||
translation_key: "automation_condition",
|
||||
icon: "mdi:playlist-check",
|
||||
},
|
||||
{
|
||||
translation_key: "automation_action",
|
||||
icon: "mdi:play-circle-outline",
|
||||
},
|
||||
{
|
||||
translation_key: "script_action",
|
||||
icon: "mdi:script-text-outline",
|
||||
},
|
||||
];
|
||||
|
||||
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,
|
||||
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,
|
||||
})
|
||||
);
|
||||
|
||||
/** Handler for adding a target to an automation/script. */
|
||||
export function addToActionHandler(
|
||||
key: AddToActionKey,
|
||||
target: SingleHassServiceTarget
|
||||
): Promise<boolean> {
|
||||
const searchParams: Record<string, string> = {};
|
||||
|
||||
if (target.entity_id) {
|
||||
searchParams[ADD_AUTOMATION_ELEMENT_ENTITY_TARGET_PARAM] = target.entity_id;
|
||||
} else if (target.device_id) {
|
||||
searchParams[ADD_AUTOMATION_ELEMENT_DEVICE_TARGET_PARAM] = target.device_id;
|
||||
} else if (target.area_id) {
|
||||
searchParams[ADD_AUTOMATION_ELEMENT_AREA_TARGET_PARAM] = target.area_id;
|
||||
}
|
||||
|
||||
const params = (addElement: string) =>
|
||||
`?${createSearchParam({
|
||||
[ADD_AUTOMATION_ELEMENT_QUERY_PARAM]: addElement,
|
||||
...searchParams,
|
||||
})}`;
|
||||
|
||||
switch (key) {
|
||||
case "automation_trigger":
|
||||
return navigate(`/config/automation/edit/new${params("trigger")}`);
|
||||
case "automation_condition":
|
||||
return navigate(`/config/automation/edit/new${params("condition")}`);
|
||||
case "automation_action":
|
||||
return navigate(`/config/automation/edit/new${params("action")}`);
|
||||
case "script_action":
|
||||
return navigate(`/config/script/edit/new${params("action")}`);
|
||||
default:
|
||||
return Promise.reject(new Error(`Unknown action key ${key}`));
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../components/ha-absolute-time";
|
||||
import "../../../components/ha-relative-time";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity/entity";
|
||||
import { isUnavailableState } from "../../../data/entity/entity";
|
||||
import type { LightEntity } from "../../../data/light";
|
||||
import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../../../data/sensor";
|
||||
import "../../../panels/lovelace/components/hui-timestamp-display";
|
||||
@@ -24,8 +24,7 @@ export class HaMoreInfoStateHeader extends LitElement {
|
||||
private _localizeState(): TemplateResult | string {
|
||||
if (
|
||||
this.stateObj.attributes.device_class === SENSOR_DEVICE_CLASS_TIMESTAMP &&
|
||||
this.stateObj.state !== UNAVAILABLE &&
|
||||
this.stateObj.state !== UNKNOWN
|
||||
!isUnavailableState(this.stateObj.state)
|
||||
) {
|
||||
return html`
|
||||
<hui-timestamp-display
|
||||
|
||||
@@ -4,7 +4,7 @@ import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-relative-time";
|
||||
import { triggerAutomationActions } from "../../../data/automation";
|
||||
import { UNAVAILABLE } from "../../../data/entity/entity";
|
||||
import { isUnavailableState } from "../../../data/entity/entity";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
@customElement("more-info-automation")
|
||||
@@ -34,7 +34,7 @@ class MoreInfoAutomation extends LitElement {
|
||||
appearance="plain"
|
||||
size="small"
|
||||
@click=${this._runActions}
|
||||
.disabled=${this.stateObj!.state === UNAVAILABLE}
|
||||
.disabled=${isUnavailableState(this.stateObj!.state)}
|
||||
>
|
||||
${this.hass.localize("ui.card.automation.trigger")}
|
||||
</ha-button>
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-button";
|
||||
import { UNAVAILABLE } from "../../../data/entity/entity";
|
||||
import { isUnavailableState } from "../../../data/entity/entity";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
@customElement("more-info-counter")
|
||||
@@ -16,7 +16,7 @@ class MoreInfoCounter extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const disabled = this.stateObj.state === UNAVAILABLE;
|
||||
const disabled = isUnavailableState(this.stateObj.state);
|
||||
|
||||
return html`
|
||||
<div class="actions">
|
||||
|
||||
@@ -4,7 +4,7 @@ import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-date-input";
|
||||
import "../../../components/ha-time-input";
|
||||
import { setDateValue } from "../../../data/date";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity/entity";
|
||||
import { isUnavailableState, UNAVAILABLE } from "../../../data/entity/entity";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../../types";
|
||||
|
||||
@customElement("more-info-date")
|
||||
@@ -21,9 +21,10 @@ class MoreInfoDate extends LitElement {
|
||||
return html`
|
||||
<ha-date-input
|
||||
.locale=${this.hass.locale}
|
||||
.value=${this.stateObj.state === UNKNOWN
|
||||
.value=${isUnavailableState(this.stateObj.state)
|
||||
? undefined
|
||||
: this.stateObj.state}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
@value-changed=${this._dateChanged}
|
||||
>
|
||||
</ha-date-input>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-date-input";
|
||||
import "../../../components/ha-time-input";
|
||||
import { setDateTimeValue } from "../../../data/datetime";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity/entity";
|
||||
import { isUnavailableState, UNAVAILABLE } from "../../../data/entity/entity";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../../types";
|
||||
|
||||
@customElement("more-info-datetime")
|
||||
@@ -19,22 +19,23 @@ class MoreInfoDatetime extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const dateObj =
|
||||
this.stateObj.state === UNKNOWN
|
||||
? undefined
|
||||
: new Date(this.stateObj.state);
|
||||
const dateObj = isUnavailableState(this.stateObj.state)
|
||||
? undefined
|
||||
: new Date(this.stateObj.state);
|
||||
const time = dateObj ? format(dateObj, "HH:mm:ss") : undefined;
|
||||
const date = dateObj ? format(dateObj, "yyyy-MM-dd") : undefined;
|
||||
|
||||
return html`<ha-date-input
|
||||
.locale=${this.hass.locale}
|
||||
.value=${date}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
@value-changed=${this._dateChanged}
|
||||
>
|
||||
</ha-date-input>
|
||||
<ha-time-input
|
||||
.value=${time}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
@value-changed=${this._timeChanged}
|
||||
@click=${this._stopEventPropagation}
|
||||
></ha-time-input>`;
|
||||
|
||||
@@ -200,13 +200,12 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
protected _renderSourceControl() {
|
||||
if (
|
||||
!this.stateObj ||
|
||||
!supportsFeature(this.stateObj, MediaPlayerEntityFeature.SELECT_SOURCE)
|
||||
!supportsFeature(this.stateObj, MediaPlayerEntityFeature.SELECT_SOURCE) ||
|
||||
!this.stateObj.attributes.source_list?.length
|
||||
) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const sourceList = this.stateObj.attributes.source_list || [];
|
||||
|
||||
return html`<ha-tooltip for="source-button">
|
||||
${this.hass.localize(`ui.card.media_player.source`)}
|
||||
</ha-tooltip>
|
||||
@@ -218,7 +217,7 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
.path=${mdiLoginVariant}
|
||||
>
|
||||
</ha-icon-button>
|
||||
${sourceList.map(
|
||||
${this.stateObj.attributes.source_list!.map(
|
||||
(source) =>
|
||||
html`<ha-dropdown-item
|
||||
.value=${source}
|
||||
|
||||
@@ -11,7 +11,7 @@ import "../../../components/ha-control-button-group";
|
||||
import "../../../components/ha-markdown";
|
||||
import "../../../components/ha-relative-time";
|
||||
import "../../../components/ha-service-control";
|
||||
import { UNAVAILABLE } from "../../../data/entity/entity";
|
||||
import { isUnavailableState } from "../../../data/entity/entity";
|
||||
import type { ExtEntityRegistryEntry } from "../../../data/entity/entity_registry";
|
||||
import type { ScriptEntity } from "../../../data/script";
|
||||
import {
|
||||
@@ -141,7 +141,7 @@ class MoreInfoScript extends LitElement {
|
||||
<ha-control-button
|
||||
class="run-button"
|
||||
@click=${this._runScript}
|
||||
.disabled=${stateObj.state === UNAVAILABLE || !this._canRun()}
|
||||
.disabled=${isUnavailableState(stateObj.state) || !this._canRun()}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlay}></ha-svg-icon>
|
||||
${this.hass!.localize("ui.card.script.run")}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-date-input";
|
||||
import "../../../components/ha-time-input";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity/entity";
|
||||
import { isUnavailableState, UNAVAILABLE } from "../../../data/entity/entity";
|
||||
import { setTimeValue } from "../../../data/time";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../../types";
|
||||
|
||||
@@ -20,10 +20,11 @@ class MoreInfoTime extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-time-input
|
||||
.value=${this.stateObj.state === UNKNOWN
|
||||
.value=${isUnavailableState(this.stateObj.state)
|
||||
? undefined
|
||||
: this.stateObj.state}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
@value-changed=${this._timeChanged}
|
||||
@click=${this._stopEventPropagation}
|
||||
></ha-time-input>
|
||||
|
||||
@@ -15,7 +15,7 @@ import "../../../components/item/ha-row-item";
|
||||
import "../../../components/progress/ha-progress-bar";
|
||||
import type { BackupConfig } from "../../../data/backup";
|
||||
import { fetchBackupConfig } from "../../../data/backup";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity/entity";
|
||||
import { isUnavailableState } from "../../../data/entity/entity";
|
||||
import type { EntitySources } from "../../../data/entity/entity_sources";
|
||||
import { fetchEntitySourcesWithCache } from "../../../data/entity/entity_sources";
|
||||
import { getSupervisorUpdateConfig } from "../../../data/supervisor/update";
|
||||
@@ -176,8 +176,7 @@ class MoreInfoUpdate extends LitElement {
|
||||
if (
|
||||
!this.hass ||
|
||||
!this.stateObj ||
|
||||
this.stateObj.state === UNAVAILABLE ||
|
||||
this.stateObj.state === UNKNOWN
|
||||
isUnavailableState(this.stateObj.state)
|
||||
) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
@@ -5,18 +5,14 @@ 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 type {
|
||||
ExternalEntityAddToAction,
|
||||
ExternalEntityAddToActions,
|
||||
} from "../../external_app/external_messaging";
|
||||
import { showToast } from "../../util/toast";
|
||||
|
||||
import type { HASSDomCurrentTargetEvent } from "../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import {
|
||||
type EntityAddToAction,
|
||||
type EntityAddToActions,
|
||||
addToActionHandler,
|
||||
getDefaultAddToActions,
|
||||
} from "./add-to";
|
||||
|
||||
@customElement("ha-more-info-add-to")
|
||||
export class HaMoreInfoAddTo extends LitElement {
|
||||
@@ -24,112 +20,54 @@ export class HaMoreInfoAddTo extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public entityId!: string;
|
||||
|
||||
@state() private _defaultActions: EntityAddToActions = [];
|
||||
|
||||
@state() private _externalActions: EntityAddToActions = [];
|
||||
@state() private _externalActions?: ExternalEntityAddToActions = {
|
||||
actions: [],
|
||||
};
|
||||
|
||||
@state() private _loading = true;
|
||||
|
||||
private async _loadActions() {
|
||||
this._defaultActions = getDefaultAddToActions(
|
||||
this.hass.states,
|
||||
this.hass.localize,
|
||||
this.hass.formatEntityName,
|
||||
this.entityId
|
||||
);
|
||||
this._externalActions = [];
|
||||
|
||||
private async _loadExternalActions() {
|
||||
if (this.hass.auth.external?.config.hasEntityAddTo) {
|
||||
try {
|
||||
const response =
|
||||
await this.hass.auth.external?.sendMessage<"entity/add_to/get_actions">(
|
||||
{
|
||||
type: "entity/add_to/get_actions",
|
||||
payload: { entity_id: this.entityId },
|
||||
}
|
||||
);
|
||||
if (response?.actions) {
|
||||
this._externalActions = response.actions.map((action) => ({
|
||||
type: "external",
|
||||
enabled: action.enabled,
|
||||
name: action.name,
|
||||
description: action.details,
|
||||
icon: action.mdi_icon,
|
||||
payload: action.app_payload,
|
||||
}));
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("Failed to fetch add to actions", err);
|
||||
}
|
||||
this._externalActions =
|
||||
await this.hass.auth.external?.sendMessage<"entity/add_to/get_actions">(
|
||||
{
|
||||
type: "entity/add_to/get_actions",
|
||||
payload: { entity_id: this.entityId },
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async _actionSelected(
|
||||
ev: HASSDomCurrentTargetEvent<
|
||||
HaListItemButton & {
|
||||
action: EntityAddToAction;
|
||||
}
|
||||
>
|
||||
) {
|
||||
const action = ev.currentTarget.action;
|
||||
private async _actionSelected(ev: CustomEvent) {
|
||||
const action = (ev.currentTarget as any)
|
||||
.action as ExternalEntityAddToAction;
|
||||
if (!action.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.type === "external") {
|
||||
try {
|
||||
if (!action.payload) {
|
||||
throw new Error("Missing external action payload");
|
||||
}
|
||||
this.hass.auth.external!.fireMessage({
|
||||
type: "entity/add_to",
|
||||
payload: {
|
||||
entity_id: this.entityId,
|
||||
app_payload: action.payload,
|
||||
},
|
||||
});
|
||||
fireEvent(this, "add-to-action-selected");
|
||||
} catch (err: unknown) {
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.dialogs.more_info_control.add_to.action_failed",
|
||||
{
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
}
|
||||
),
|
||||
});
|
||||
}
|
||||
return;
|
||||
try {
|
||||
await this.hass.auth.external!.fireMessage({
|
||||
type: "entity/add_to",
|
||||
payload: {
|
||||
entity_id: this.entityId,
|
||||
app_payload: action.app_payload,
|
||||
},
|
||||
});
|
||||
fireEvent(this, "add-to-action-selected");
|
||||
} catch (err: any) {
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.dialogs.more_info_control.add_to.action_failed",
|
||||
{
|
||||
error: err.message || err,
|
||||
}
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (action.type !== "default") {
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
await this._loadExternalActions();
|
||||
this._loading = false;
|
||||
}
|
||||
|
||||
@@ -142,7 +80,7 @@ export class HaMoreInfoAddTo extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
if (!this._defaultActions.length && !this._externalActions.length) {
|
||||
if (!this._externalActions?.actions.length) {
|
||||
return html`
|
||||
<ha-alert alert-type="info">
|
||||
${this.hass.localize(
|
||||
@@ -154,27 +92,30 @@ export class HaMoreInfoAddTo extends LitElement {
|
||||
|
||||
return html`
|
||||
<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>
|
||||
${this._externalActions?.actions.map(
|
||||
(action) => html`
|
||||
<ha-list-item-button
|
||||
.disabled=${!action.enabled}
|
||||
.action=${action}
|
||||
@click=${this._actionSelected}
|
||||
>
|
||||
<ha-icon slot="start" .icon=${action.mdi_icon}></ha-icon>
|
||||
<span slot="headline">${action.name}</span>
|
||||
${action.details
|
||||
? html`<span slot="supporting-text">${action.details}</span>`
|
||||
: nothing}
|
||||
</ha-list-item-button>
|
||||
`
|
||||
: nothing}
|
||||
)}
|
||||
</ha-list-base>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
padding: var(--ha-space-3) 0 var(--ha-space-4);
|
||||
padding: var(--ha-space-2) var(--ha-space-6) var(--ha-space-6)
|
||||
var(--ha-space-6);
|
||||
}
|
||||
|
||||
.loading {
|
||||
@@ -184,15 +125,6 @@ export class HaMoreInfoAddTo extends LitElement {
|
||||
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;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiBackupRestore,
|
||||
mdiChartBoxOutline,
|
||||
@@ -18,13 +17,13 @@ import {
|
||||
} from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
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 { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||
import type { HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
import { computeAreaName } from "../../common/entity/compute_area_name";
|
||||
@@ -58,12 +57,10 @@ import {
|
||||
getExtendedEntityRegistryEntry,
|
||||
updateEntityRegistryEntry,
|
||||
} from "../../data/entity/entity_registry";
|
||||
import { subscribeLabFeature } from "../../data/labs";
|
||||
import type { ItemType } from "../../data/search";
|
||||
import { SearchableDomains } from "../../data/search";
|
||||
import { getSensorNumericDeviceClasses } from "../../data/sensor";
|
||||
import { ScrollableFadeMixin } from "../../mixins/scrollable-fade-mixin";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import {
|
||||
haStyleDialog,
|
||||
haStyleDialogFixedTop,
|
||||
@@ -73,12 +70,12 @@ import "../../state-summary/state-card-content";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
||||
import {
|
||||
computeShowHistoryComponent,
|
||||
computeShowLogBookComponent,
|
||||
DOMAINS_WITH_MORE_INFO,
|
||||
EDITABLE_DOMAINS_WITH_ID,
|
||||
EDITABLE_DOMAINS_WITH_UNIQUE_ID,
|
||||
type MoreInfoView,
|
||||
computeShowHistoryComponent,
|
||||
computeShowLogBookComponent,
|
||||
} from "./const";
|
||||
import "./controls/more-info-default";
|
||||
import type { FavoritesDialogContext } from "./favorites";
|
||||
@@ -120,9 +117,7 @@ declare global {
|
||||
const DEFAULT_VIEW: MoreInfoView = "info";
|
||||
|
||||
@customElement("ha-more-info-dialog")
|
||||
export class MoreInfoDialog extends SubscribeMixin(
|
||||
ScrollableFadeMixin(LitElement)
|
||||
) {
|
||||
export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public large = false;
|
||||
@@ -161,8 +156,6 @@ export class MoreInfoDialog extends SubscribeMixin(
|
||||
|
||||
@state() private _sensorNumericDeviceClasses?: string[] = [];
|
||||
|
||||
@state() private _newTriggersAndConditions = false;
|
||||
|
||||
protected scrollFadeThreshold = 24;
|
||||
|
||||
protected get scrollableElement(): HTMLElement | null {
|
||||
@@ -261,24 +254,7 @@ export class MoreInfoDialog extends SubscribeMixin(
|
||||
}
|
||||
|
||||
private _shouldShowAddEntityTo(): boolean {
|
||||
// When new_triggers_conditions labs feature is promoted, this whole check can be removed.
|
||||
return (
|
||||
this._newTriggersAndConditions ||
|
||||
!!this.hass.auth.external?.config.hasEntityAddTo
|
||||
);
|
||||
}
|
||||
|
||||
protected hassSubscribe() {
|
||||
return [
|
||||
subscribeLabFeature(
|
||||
this.hass.connection,
|
||||
"automation",
|
||||
"new_triggers_conditions",
|
||||
(feature) => {
|
||||
this._newTriggersAndConditions = feature.enabled;
|
||||
}
|
||||
),
|
||||
];
|
||||
return !!this.hass.auth.external?.config.hasEntityAddTo;
|
||||
}
|
||||
|
||||
private _getDeviceId(): string | null {
|
||||
@@ -704,21 +680,6 @@ export class MoreInfoDialog extends SubscribeMixin(
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
${this._shouldShowAddEntityTo()
|
||||
? html`
|
||||
<ha-dropdown-item value="add_to">
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiPlusBoxMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.add_to.title"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
`
|
||||
: nothing}
|
||||
${supportsFavorites
|
||||
? html`
|
||||
<ha-dropdown-item value="toggle_edit">
|
||||
@@ -808,6 +769,19 @@ export class MoreInfoDialog extends SubscribeMixin(
|
||||
"ui.dialogs.more_info_control.details"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
${this._shouldShowAddEntityTo()
|
||||
? html`
|
||||
<ha-dropdown-item value="add_to">
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiPlusBoxMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.add_entity_to"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
`
|
||||
: nothing}
|
||||
</ha-dropdown>
|
||||
`
|
||||
: !__DEMO__ && this._shouldShowAddEntityTo()
|
||||
@@ -815,7 +789,7 @@ export class MoreInfoDialog extends SubscribeMixin(
|
||||
<ha-icon-button
|
||||
slot="headerActionItems"
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.add_to.title"
|
||||
"ui.dialogs.more_info_control.add_entity_to"
|
||||
)}
|
||||
.path=${mdiPlusBoxMultipleOutline}
|
||||
@click=${this._goToAddEntityTo}
|
||||
|
||||
@@ -343,9 +343,9 @@ export class HaVoiceAssistantSetupDialog extends LitElement {
|
||||
this._step = this._previousSteps.pop()!;
|
||||
}
|
||||
|
||||
private async _goToNextStep(ev?: CustomEvent) {
|
||||
private _goToNextStep(ev?: CustomEvent) {
|
||||
if (ev?.detail?.updateConfig) {
|
||||
await this._fetchAssistConfiguration();
|
||||
this._fetchAssistConfiguration();
|
||||
}
|
||||
if (ev?.detail?.nextStep) {
|
||||
this._nextStep = ev.detail.nextStep;
|
||||
|
||||
@@ -199,13 +199,13 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
|
||||
this._detailState = this.hass.localize(
|
||||
`ui.panel.config.voice_assistants.satellite_wizard.local.state.installing_${this._ttsProviderName}`
|
||||
);
|
||||
await installHassioAddon(this.hass.callWS, this._ttsAddonName);
|
||||
await installHassioAddon(this.hass, this._ttsAddonName);
|
||||
}
|
||||
if (!ttsAddon || ttsAddon.state !== "started") {
|
||||
this._detailState = this.hass.localize(
|
||||
`ui.panel.config.voice_assistants.satellite_wizard.local.state.starting_${this._ttsProviderName}`
|
||||
);
|
||||
await startHassioAddon(this.hass.callWS, this._ttsAddonName);
|
||||
await startHassioAddon(this.hass, this._ttsAddonName);
|
||||
}
|
||||
this._detailState = this.hass.localize(
|
||||
`ui.panel.config.voice_assistants.satellite_wizard.local.state.setup_${this._ttsProviderName}`
|
||||
@@ -217,13 +217,13 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
|
||||
this._detailState = this.hass.localize(
|
||||
`ui.panel.config.voice_assistants.satellite_wizard.local.state.installing_${this._sttProviderName}`
|
||||
);
|
||||
await installHassioAddon(this.hass.callWS, this._sttAddonName);
|
||||
await installHassioAddon(this.hass, this._sttAddonName);
|
||||
}
|
||||
if (!sttAddon || sttAddon.state !== "started") {
|
||||
this._detailState = this.hass.localize(
|
||||
`ui.panel.config.voice_assistants.satellite_wizard.local.state.starting_${this._sttProviderName}`
|
||||
);
|
||||
await startHassioAddon(this.hass.callWS, this._sttAddonName);
|
||||
await startHassioAddon(this.hass, this._sttAddonName);
|
||||
}
|
||||
this._detailState = this.hass.localize(
|
||||
`ui.panel.config.voice_assistants.satellite_wizard.local.state.setup_${this._sttProviderName}`
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import {
|
||||
HAS_RESOLVED_IANA_TIME_ZONE,
|
||||
LOCAL_TIME_ZONE,
|
||||
} from "../common/datetime/resolve-time-zone";
|
||||
import { LOCAL_TIME_ZONE } from "../common/datetime/resolve-time-zone";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import "../components/ha-alert";
|
||||
import "../components/ha-button";
|
||||
import { COUNTRIES } from "../components/ha-country-picker";
|
||||
import "../components/ha-form/ha-form";
|
||||
import type { HaForm } from "../components/ha-form/ha-form";
|
||||
import type { HaFormSchema } from "../components/ha-form/types";
|
||||
import "../components/ha-spinner";
|
||||
import type { ConfigUpdateValues } from "../data/core";
|
||||
import { saveCoreConfig } from "../data/core";
|
||||
import { countryCurrency } from "../data/currency";
|
||||
import { onboardCoreConfigStep } from "../data/onboarding";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import { getLocalLanguage } from "../util/common-translation";
|
||||
import "./onboarding-location";
|
||||
|
||||
@@ -35,9 +28,7 @@ class OnboardingCoreConfig extends LitElement {
|
||||
|
||||
private _elevation = "0";
|
||||
|
||||
@state() private _timeZone: ConfigUpdateValues["time_zone"] = LOCAL_TIME_ZONE;
|
||||
|
||||
@state() private _timeZoneDetected = HAS_RESOLVED_IANA_TIME_ZONE;
|
||||
private _timeZone: ConfigUpdateValues["time_zone"] = LOCAL_TIME_ZONE;
|
||||
|
||||
private _language: ConfigUpdateValues["language"] = getLocalLanguage();
|
||||
|
||||
@@ -51,29 +42,7 @@ class OnboardingCoreConfig extends LitElement {
|
||||
|
||||
@state() private _skipCore = false;
|
||||
|
||||
@query("ha-form") private _form?: HaForm;
|
||||
|
||||
private _schema = memoizeOne((includeTimeZone: boolean): HaFormSchema[] => [
|
||||
{
|
||||
name: "country",
|
||||
required: true,
|
||||
selector: { country: null },
|
||||
},
|
||||
...(includeTimeZone
|
||||
? ([
|
||||
{
|
||||
name: "time_zone",
|
||||
required: true,
|
||||
selector: { timezone: null },
|
||||
},
|
||||
] satisfies HaFormSchema[])
|
||||
: []),
|
||||
]);
|
||||
|
||||
private _computeLabel = (schema: HaFormSchema) =>
|
||||
this.hass.localize(
|
||||
`ui.panel.config.core.section.core.core_config.${schema.name}` as any
|
||||
);
|
||||
@query("ha-country-picker") private _countryPicker?: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._location) {
|
||||
@@ -99,17 +68,17 @@ class OnboardingCoreConfig extends LitElement {
|
||||
)}
|
||||
</p>
|
||||
|
||||
<ha-form
|
||||
<ha-country-picker
|
||||
class="flex"
|
||||
.hass=${this.hass}
|
||||
.data=${{
|
||||
country: this._country ?? "",
|
||||
time_zone: this._timeZone,
|
||||
}}
|
||||
.schema=${this._schema(!this._timeZoneDetected)}
|
||||
.computeLabel=${this._computeLabel}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.core.section.core.core_config.country"
|
||||
) || "Country"}
|
||||
required
|
||||
.disabled=${this._working}
|
||||
@value-changed=${this._handleFormChanged}
|
||||
></ha-form>
|
||||
.value=${this._countryValue}
|
||||
@value-changed=${this._handleCountryChanged}
|
||||
></ha-country-picker>
|
||||
|
||||
<div class="footer">
|
||||
<ha-button @click=${this._save} .disabled=${this._working}>
|
||||
@@ -130,12 +99,12 @@ class OnboardingCoreConfig extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _handleFormChanged(ev: CustomEvent) {
|
||||
const value = ev.detail.value as { country?: string; time_zone?: string };
|
||||
this._country = value.country || undefined;
|
||||
if (value.time_zone) {
|
||||
this._timeZone = value.time_zone;
|
||||
}
|
||||
private get _countryValue() {
|
||||
return this._country || "";
|
||||
}
|
||||
|
||||
private _handleCountryChanged(ev: ValueChangedEvent<string>) {
|
||||
this._country = ev.detail.value;
|
||||
}
|
||||
|
||||
private async _locationChanged(ev) {
|
||||
@@ -154,12 +123,11 @@ class OnboardingCoreConfig extends LitElement {
|
||||
}
|
||||
if (ev.detail.value.timezone) {
|
||||
this._timeZone = ev.detail.value.timezone;
|
||||
this._timeZoneDetected = true;
|
||||
}
|
||||
if (ev.detail.value.unit_system) {
|
||||
this._unitSystem = ev.detail.value.unit_system;
|
||||
}
|
||||
if (this._country && this._timeZoneDetected) {
|
||||
if (this._country) {
|
||||
this._skipCore = true;
|
||||
this._save(ev);
|
||||
return;
|
||||
@@ -177,11 +145,11 @@ class OnboardingCoreConfig extends LitElement {
|
||||
|
||||
fireEvent(this, "onboarding-progress", { increase: 0.5 });
|
||||
await this.updateComplete;
|
||||
setTimeout(() => this._form?.focus(), 100);
|
||||
setTimeout(() => this._countryPicker!.focus(), 100);
|
||||
}
|
||||
|
||||
private async _save(ev) {
|
||||
if (!this._location || !this._country || !this._timeZone) {
|
||||
if (!this._location || !this._country) {
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
@@ -198,7 +166,7 @@ class OnboardingCoreConfig extends LitElement {
|
||||
this._unitSystem || ["US", "MM", "LR"].includes(this._country)
|
||||
? "us_customary"
|
||||
: "metric",
|
||||
time_zone: this._timeZone,
|
||||
time_zone: this._timeZone || "UTC",
|
||||
currency: this._currency || countryCurrency[this._country] || "EUR",
|
||||
country: this._country,
|
||||
language: this._language,
|
||||
|
||||
@@ -6,7 +6,6 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { createRef, ref } from "lit/directives/ref";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { IFRAME_SANDBOX } from "../../util/iframe";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import { computeRouteTail } from "../../common/url/route";
|
||||
import { nextRender } from "../../common/util/render-status";
|
||||
@@ -137,8 +136,6 @@ class HaPanelApp extends LitElement {
|
||||
})}
|
||||
title=${this._addon.name}
|
||||
src=${this._addon.ingress_url!}
|
||||
.sandbox=${IFRAME_SANDBOX}
|
||||
allow="microphone; camera; clipboard-write"
|
||||
@load=${this._checkLoaded}
|
||||
${ref(this._iframeRef)}
|
||||
>
|
||||
@@ -216,7 +213,7 @@ class HaPanelApp extends LitElement {
|
||||
let addon: HassioAddonDetails;
|
||||
|
||||
try {
|
||||
addon = await fetchHassioAddonInfo(this.hass.callWS, addonSlug);
|
||||
addon = await fetchHassioAddonInfo(this.hass, addonSlug);
|
||||
} catch (err: any) {
|
||||
await this._showErrorAndNavigateHome(
|
||||
addonSlug,
|
||||
@@ -256,7 +253,7 @@ class HaPanelApp extends LitElement {
|
||||
);
|
||||
// Set auto-retry window for after starting the app
|
||||
this._autoRetryUntil = Date.now() + START_WAIT_TIME;
|
||||
await startHassioAddon(this.hass.callWS, addonSlug);
|
||||
await startHassioAddon(this.hass, addonSlug);
|
||||
this._fetchData(addonSlug);
|
||||
return;
|
||||
} catch (_err) {
|
||||
|
||||
@@ -190,20 +190,16 @@ class PanelCalendar extends SubscribeMixin(LitElement) {
|
||||
.label=${this.hass.localize("ui.common.refresh")}
|
||||
@click=${this._handleRefresh}
|
||||
></ha-icon-button>
|
||||
${showPane
|
||||
? html`<ha-list slot="pane" multi>${calendarItems}</ha-list>${this
|
||||
.hass.user?.is_admin
|
||||
? html`<ha-list-item
|
||||
graphic="icon"
|
||||
slot="pane-footer"
|
||||
@click=${this._addCalendar}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="graphic"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.components.calendar.create_calendar"
|
||||
)}
|
||||
</ha-list-item>`
|
||||
: nothing}`
|
||||
${showPane && this.hass.user?.is_admin
|
||||
? html`<ha-list slot="pane" multi}>${calendarItems}</ha-list>
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
slot="pane-footer"
|
||||
@click=${this._addCalendar}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="graphic"></ha-svg-icon>
|
||||
${this.hass.localize("ui.components.calendar.create_calendar")}
|
||||
</ha-list-item>`
|
||||
: nothing}
|
||||
<ha-full-calendar
|
||||
add-fab
|
||||
|
||||
@@ -3,7 +3,7 @@ import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import "../../../../../components/ha-bar";
|
||||
import "../../../../../components/item/ha-row-item";
|
||||
import "../../../../../components/ha-settings-row";
|
||||
import { roundWithOneDecimal } from "../../../../../util/calculate";
|
||||
|
||||
@customElement("supervisor-app-metric")
|
||||
@@ -16,9 +16,9 @@ class SupervisorAppMetric extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const roundedValue = roundWithOneDecimal(this.value);
|
||||
return html`<ha-row-item empty>
|
||||
<span slot="headline"> ${this.description} </span>
|
||||
<div slot="supporting-text" .title=${this.tooltip ?? ""}>
|
||||
return html`<ha-settings-row empty>
|
||||
<span slot="heading"> ${this.description} </span>
|
||||
<div slot="description" .title=${this.tooltip ?? ""}>
|
||||
<span class="value"> ${roundedValue} % </span>
|
||||
<ha-bar
|
||||
class=${classMap({
|
||||
@@ -28,14 +28,16 @@ class SupervisorAppMetric extends LitElement {
|
||||
.value=${this.value}
|
||||
></ha-bar>
|
||||
</div>
|
||||
</ha-row-item>`;
|
||||
</ha-settings-row>`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-row-item {
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
height: 54px;
|
||||
width: 100%;
|
||||
}
|
||||
ha-row-item > div[slot="supporting-text"] {
|
||||
ha-settings-row > div[slot="description"] {
|
||||
white-space: normal;
|
||||
color: var(--secondary-text-color);
|
||||
display: flex;
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
import {
|
||||
css,
|
||||
type CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
type PropertyValues,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { atLeastVersion } from "../../../../../common/config/version";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/buttons/ha-progress-button";
|
||||
import "../../../../../components/ha-alert";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-faded";
|
||||
import "../../../../../components/ha-markdown";
|
||||
import "../../../../../components/ha-spinner";
|
||||
import "../../../../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../../../../components/ha-switch";
|
||||
import "../../../../../components/item/ha-row-item";
|
||||
import type { HassioAddonDetails } from "../../../../../data/hassio/addon";
|
||||
import {
|
||||
fetchHassioAddonChangelog,
|
||||
updateHassioAddon,
|
||||
} from "../../../../../data/hassio/addon";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
ignoreSupervisorError,
|
||||
} from "../../../../../data/hassio/common";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { extractChangelog } from "../util/supervisor-app";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"update-complete": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("supervisor-app-update-available-card")
|
||||
class SupervisorAppUpdateAvailableCard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@state() private _changelogContent?: string;
|
||||
|
||||
@state() private _updating = false;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
protected render() {
|
||||
if (!this.addon) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const createBackupTexts = this._computeCreateBackupTexts();
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.apps.dashboard.update_available.update_name",
|
||||
{
|
||||
name: this.addon.name,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
${this.addon.version === this.addon.version_latest
|
||||
? html`<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.apps.dashboard.update_available.no_update",
|
||||
{
|
||||
name: this.addon.name,
|
||||
}
|
||||
)}
|
||||
</p>`
|
||||
: !this._updating
|
||||
? html`
|
||||
${this._changelogContent
|
||||
? html`
|
||||
<ha-faded>
|
||||
<ha-markdown .content=${this._changelogContent}>
|
||||
</ha-markdown>
|
||||
</ha-faded>
|
||||
`
|
||||
: nothing}
|
||||
<div class="versions">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.apps.dashboard.update_available.description",
|
||||
{
|
||||
name: this.addon.name,
|
||||
version: this.addon.version,
|
||||
newest_version: this.addon.version_latest,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
${createBackupTexts
|
||||
? html`
|
||||
<hr />
|
||||
<ha-row-item>
|
||||
<span slot="headline">
|
||||
${createBackupTexts.title}
|
||||
</span>
|
||||
|
||||
${createBackupTexts.description
|
||||
? html`
|
||||
<span slot="supporting-text">
|
||||
${createBackupTexts.description}
|
||||
</span>
|
||||
`
|
||||
: nothing}
|
||||
<ha-switch slot="end" id="create-backup"></ha-switch>
|
||||
</ha-row-item>
|
||||
`
|
||||
: nothing}
|
||||
`
|
||||
: html`<ha-spinner
|
||||
aria-label="Updating"
|
||||
size="large"
|
||||
></ha-spinner>
|
||||
<p class="progress-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.apps.dashboard.update_available.updating",
|
||||
{
|
||||
name: this.addon.name,
|
||||
version: this.addon.version_latest,
|
||||
}
|
||||
)}
|
||||
</p>`}
|
||||
</div>
|
||||
${this.addon.version !== this.addon.version_latest && !this._updating
|
||||
? html`
|
||||
<div class="card-actions">
|
||||
<span></span>
|
||||
<ha-progress-button @click=${this._update}>
|
||||
${this.hass.localize("ui.common.update")}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues<this>) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._loadAddonData();
|
||||
}
|
||||
|
||||
private _computeCreateBackupTexts():
|
||||
| { title: string; description?: string }
|
||||
| undefined {
|
||||
if (atLeastVersion(this.hass.config.version, 2025, 2, 0)) {
|
||||
const version = this.addon.version;
|
||||
return {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.apps.dashboard.update_available.create_backup.app"
|
||||
),
|
||||
description: this.hass.localize(
|
||||
"ui.panel.config.apps.dashboard.update_available.create_backup.app_description",
|
||||
{ version: version }
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.apps.dashboard.update_available.create_backup.generic"
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
get _shouldCreateBackup(): boolean {
|
||||
const createBackupSwitch = this.shadowRoot?.getElementById(
|
||||
"create-backup"
|
||||
) as HaSwitch;
|
||||
if (createBackupSwitch) {
|
||||
return createBackupSwitch.checked;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async _loadAddonData() {
|
||||
if (this.addon.changelog) {
|
||||
try {
|
||||
const content = await fetchHassioAddonChangelog(
|
||||
this.hass,
|
||||
this.addon.slug
|
||||
);
|
||||
this._changelogContent = extractChangelog(
|
||||
this.addon as HassioAddonDetails,
|
||||
content
|
||||
);
|
||||
} catch (err) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _update() {
|
||||
this._error = undefined;
|
||||
this._updating = true;
|
||||
|
||||
try {
|
||||
await updateHassioAddon(
|
||||
this.hass,
|
||||
this.addon.slug,
|
||||
this._shouldCreateBackup
|
||||
);
|
||||
} catch (err: any) {
|
||||
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
this._updating = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
fireEvent(this, "update-complete");
|
||||
this._updating = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
ha-card {
|
||||
margin: auto;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
ha-spinner {
|
||||
display: block;
|
||||
margin: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
ha-markdown {
|
||||
padding-bottom: var(--ha-space-2);
|
||||
}
|
||||
|
||||
hr {
|
||||
border-color: var(--divider-color);
|
||||
border-bottom: none;
|
||||
margin: var(--ha-space-4) 0 0 0;
|
||||
}
|
||||
|
||||
ha-row-item {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
margin-bottom: calc(-1 * var(--ha-space-4));
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"supervisor-app-update-available-card": SupervisorAppUpdateAvailableCard;
|
||||
}
|
||||
}
|
||||
@@ -183,7 +183,7 @@ class SupervisorAppAudio extends LitElement {
|
||||
this._selectedOutput === "default" ? null : this._selectedOutput,
|
||||
};
|
||||
try {
|
||||
await setHassioAddonOption(this.hass.callWS, this.addon.slug, data);
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
if (this.addon?.state === "started") {
|
||||
await suggestSupervisorAppRestart(this, this.hass, this.addon);
|
||||
}
|
||||
|
||||
@@ -449,7 +449,7 @@ class SupervisorAppConfig extends LitElement {
|
||||
options: null,
|
||||
};
|
||||
try {
|
||||
await setHassioAddonOption(this.hass.callWS, this.addon.slug, data);
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
this._configHasChanged = false;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
@@ -488,14 +488,14 @@ class SupervisorAppConfig extends LitElement {
|
||||
|
||||
try {
|
||||
const validation = await validateHassioAddonOption(
|
||||
this.hass.callWS,
|
||||
this.hass,
|
||||
this.addon.slug,
|
||||
options
|
||||
);
|
||||
if (!validation.valid) {
|
||||
throw Error(validation.message);
|
||||
}
|
||||
await setHassioAddonOption(this.hass.callWS, this.addon.slug, {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, {
|
||||
options,
|
||||
});
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/buttons/ha-progress-button";
|
||||
import "../../../../../components/ha-alert";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-formfield";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
import type { HaFormSchema } from "../../../../../components/ha-form/types";
|
||||
import "../../../../../components/ha-formfield";
|
||||
import type {
|
||||
HassioAddonDetails,
|
||||
HassioAddonSetOptionParams,
|
||||
@@ -17,8 +17,8 @@ import { setHassioAddonOption } from "../../../../../data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../../../data/hassio/common";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { supervisorAppsStyle } from "../../resources/supervisor-apps-style";
|
||||
import { suggestSupervisorAppRestart } from "../dialogs/suggestSupervisorAppRestart";
|
||||
import { supervisorAppsStyle } from "../../resources/supervisor-apps-style";
|
||||
|
||||
@customElement("supervisor-app-network")
|
||||
class SupervisorAppNetwork extends LitElement {
|
||||
@@ -160,7 +160,7 @@ class SupervisorAppNetwork extends LitElement {
|
||||
};
|
||||
|
||||
try {
|
||||
await setHassioAddonOption(this.hass.callWS, this.addon.slug, data);
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
this._configHasChanged = false;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
@@ -205,7 +205,7 @@ class SupervisorAppNetwork extends LitElement {
|
||||
};
|
||||
|
||||
try {
|
||||
await setHassioAddonOption(this.hass.callWS, this.addon.slug, data);
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
this._configHasChanged = false;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
|
||||
@@ -28,7 +28,7 @@ export const suggestSupervisorAppRestart = async (
|
||||
});
|
||||
if (confirmed) {
|
||||
try {
|
||||
await restartHassioAddon(hass.callWS, addon.slug);
|
||||
await restartHassioAddon(hass, addon.slug);
|
||||
} catch (err: any) {
|
||||
showAlertDialog(element, {
|
||||
title: hass.localize(
|
||||
|
||||
@@ -46,8 +46,8 @@ class SupervisorAppInfoDashboard extends LitElement {
|
||||
css`
|
||||
.content {
|
||||
margin: auto;
|
||||
padding: var(--ha-space-4);
|
||||
max-width: 1200px;
|
||||
padding: var(--ha-space-2);
|
||||
max-width: 1024px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,16 @@
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-alert";
|
||||
import "../../../../../components/ha-button";
|
||||
import { internationalizationContext } from "../../../../../data/context";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
|
||||
@customElement("supervisor-app-system-managed")
|
||||
class SupervisorAppSystemManaged extends LitElement {
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private i18n!: ContextType<typeof internationalizationContext>;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, attribute: "hide-button" }) public hideButton =
|
||||
false;
|
||||
@@ -22,18 +19,18 @@ class SupervisorAppSystemManaged extends LitElement {
|
||||
return html`
|
||||
<ha-alert
|
||||
alert-type="warning"
|
||||
.title=${this.i18n.localize(
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.apps.dashboard.system_managed.title"
|
||||
)}
|
||||
.narrow=${this.narrow}
|
||||
>
|
||||
${this.i18n.localize(
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.apps.dashboard.system_managed.description"
|
||||
)}
|
||||
${!this.hideButton
|
||||
? html`
|
||||
<ha-button slot="action" @click=${this._takeControl}>
|
||||
${this.i18n.localize(
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.apps.dashboard.system_managed.take_control"
|
||||
)}
|
||||
</ha-button>
|
||||
|
||||
@@ -161,7 +161,7 @@ class HaConfigAppDashboard extends LitElement {
|
||||
}
|
||||
|
||||
try {
|
||||
this._addon = await fetchHassioAddonInfo(this.hass.callWS, slug);
|
||||
this._addon = await fetchHassioAddonInfo(this.hass, slug);
|
||||
} catch (err: any) {
|
||||
if (repositoryUrl) {
|
||||
try {
|
||||
@@ -210,7 +210,7 @@ class HaConfigAppDashboard extends LitElement {
|
||||
}
|
||||
|
||||
await addStoreRepository(this.hass, repositoryUrl);
|
||||
this._addon = await fetchHassioAddonInfo(this.hass.callWS, slug);
|
||||
this._addon = await fetchHassioAddonInfo(this.hass, slug);
|
||||
}
|
||||
|
||||
private async _apiCalled(ev): Promise<void> {
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import {
|
||||
mdiPalette,
|
||||
mdiPlayCircleOutline,
|
||||
mdiPlaylistCheck,
|
||||
mdiRobotOutline,
|
||||
mdiScriptTextOutline,
|
||||
} from "@mdi/js";
|
||||
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 {
|
||||
addToActionHandler,
|
||||
type AddToActionKey,
|
||||
} from "../../../dialogs/more-info/add-to";
|
||||
import { haStyle, haStyleDialog } from "../../../resources/styles";
|
||||
import type { AreaAddToDialogParams } from "./show-dialog-area-add-to";
|
||||
|
||||
@customElement("dialog-area-add-to")
|
||||
class DialogAreaAddTo extends LitElement {
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n!: ContextType<typeof internationalizationContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: areasContext, subscribe: true })
|
||||
private _areas!: ContextType<typeof areasContext>;
|
||||
|
||||
@state() private _params?: AreaAddToDialogParams;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
public showDialog(params: AreaAddToDialogParams): void {
|
||||
this._params = params;
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-adaptive-dialog
|
||||
.open=${this._open}
|
||||
header-title=${this._i18n.localize(
|
||||
"ui.dialogs.more_info_control.add_to.title"
|
||||
)}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
${this._renderOptions()}
|
||||
</ha-adaptive-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderOptions() {
|
||||
if (!this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const area = this._areas[this._params.areaId];
|
||||
const areaName = computeAreaName(area) || this._params.areaId;
|
||||
|
||||
return html`
|
||||
<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 _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 key = (ev.currentTarget as HTMLElement).dataset
|
||||
.type as AddToActionKey;
|
||||
|
||||
this.closeDialog();
|
||||
addToActionHandler(key, { area_id: this._params.areaId });
|
||||
}
|
||||
|
||||
private _handleCreateScene() {
|
||||
if (!this._params) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entities: SceneEntities = {};
|
||||
for (const entityId of this._params.entityIds) {
|
||||
entities[entityId] = "";
|
||||
}
|
||||
|
||||
this.closeDialog();
|
||||
showSceneEditor({ entities }, this._params.areaId);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
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);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-area-add-to": DialogAreaAddTo;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,6 @@
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiDelete,
|
||||
mdiDevices,
|
||||
mdiDotsVertical,
|
||||
mdiImagePlus,
|
||||
mdiPalette,
|
||||
mdiPencil,
|
||||
mdiPlus,
|
||||
mdiRobot,
|
||||
mdiScriptText,
|
||||
mdiShape,
|
||||
mdiTools,
|
||||
} from "@mdi/js";
|
||||
import type {
|
||||
HassEntity,
|
||||
UnsubscribeFunc,
|
||||
} from "home-assistant-js-websocket/dist/types";
|
||||
import { mdiDelete, mdiDotsVertical, mdiImagePlus, mdiPencil } from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket/dist/types";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@@ -25,7 +10,7 @@ import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { computeDeviceNameDisplay } from "../../../common/entity/compute_device_name";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { goBack, navigate } from "../../../common/navigate";
|
||||
import { goBack } from "../../../common/navigate";
|
||||
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import { groupBy } from "../../../common/util/group-by";
|
||||
@@ -33,13 +18,11 @@ import { afterNextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-dropdown";
|
||||
import type { HASSDomCurrentTargetEvent } from "../../../common/dom/fire_event";
|
||||
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-list";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-tooltip";
|
||||
import type { AreaRegistryEntry } from "../../../data/area/area_registry";
|
||||
import {
|
||||
@@ -55,7 +38,6 @@ import {
|
||||
computeEntityRegistryName,
|
||||
sortEntityRegistryByName,
|
||||
} from "../../../data/entity/entity_registry";
|
||||
import { subscribeLabFeature } from "../../../data/labs";
|
||||
import type { SceneEntity } from "../../../data/scene";
|
||||
import type { ScriptEntity } from "../../../data/script";
|
||||
import type { RelatedResult } from "../../../data/search";
|
||||
@@ -64,15 +46,9 @@ import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box
|
||||
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { isHelperDomain } from "../helpers/const";
|
||||
import "../../logbook/ha-logbook";
|
||||
import {
|
||||
loadAreaAddToDialog,
|
||||
showAreaAddToDialog,
|
||||
} from "./show-dialog-area-add-to";
|
||||
import {
|
||||
loadAreaRegistryDetailDialog,
|
||||
showAreaRegistryDetailDialog,
|
||||
@@ -83,60 +59,8 @@ declare interface NameAndEntity<EntityType extends HassEntity> {
|
||||
entity: EntityType;
|
||||
}
|
||||
|
||||
type AreaQuickLinkKey =
|
||||
| "devices"
|
||||
| "entities"
|
||||
| "helpers"
|
||||
| "automations"
|
||||
| "scenes"
|
||||
| "scripts";
|
||||
|
||||
const NAVIGATION_ACTIONS: {
|
||||
value: string;
|
||||
path: string;
|
||||
icon: string;
|
||||
countKey: AreaQuickLinkKey;
|
||||
}[] = [
|
||||
{
|
||||
value: "navigate-devices",
|
||||
path: "/config/devices/dashboard",
|
||||
icon: mdiDevices,
|
||||
countKey: "devices",
|
||||
},
|
||||
{
|
||||
value: "navigate-entities",
|
||||
path: "/config/entities",
|
||||
icon: mdiShape,
|
||||
countKey: "entities",
|
||||
},
|
||||
{
|
||||
value: "navigate-helpers",
|
||||
path: "/config/helpers",
|
||||
icon: mdiTools,
|
||||
countKey: "helpers",
|
||||
},
|
||||
{
|
||||
value: "navigate-automations",
|
||||
path: "/config/automation/dashboard",
|
||||
icon: mdiRobot,
|
||||
countKey: "automations",
|
||||
},
|
||||
{
|
||||
value: "navigate-scenes",
|
||||
path: "/config/scene/dashboard",
|
||||
icon: mdiPalette,
|
||||
countKey: "scenes",
|
||||
},
|
||||
{
|
||||
value: "navigate-scripts",
|
||||
path: "/config/script/dashboard",
|
||||
icon: mdiScriptText,
|
||||
countKey: "scripts",
|
||||
},
|
||||
] as const;
|
||||
|
||||
@customElement("ha-config-area-page")
|
||||
class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
class HaConfigAreaPage extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public areaId!: string;
|
||||
@@ -153,8 +77,6 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _related?: RelatedResult;
|
||||
|
||||
@state() private _newTriggersConditions = false;
|
||||
|
||||
private _logbookTime = { recent: 86400 };
|
||||
|
||||
private _memberships = memoizeOne(
|
||||
@@ -206,35 +128,9 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
.concat(memberships.indirectEntities.map((entry) => entry.entity_id))
|
||||
);
|
||||
|
||||
private _getQuickLinkCounts = memoizeOne(
|
||||
(
|
||||
memberships: {
|
||||
devices: DeviceRegistryEntry[];
|
||||
entities: EntityRegistryEntry[];
|
||||
indirectEntities: EntityRegistryEntry[];
|
||||
},
|
||||
related?: RelatedResult
|
||||
) => {
|
||||
const allEntityIds = this._allEntities(memberships);
|
||||
const entityIds = related?.entity ?? allEntityIds;
|
||||
|
||||
return {
|
||||
devices: related?.device?.length ?? memberships.devices.length,
|
||||
entities: entityIds.length,
|
||||
helpers: entityIds.filter((entityId) =>
|
||||
isHelperDomain(computeDomain(entityId))
|
||||
).length,
|
||||
automations: related?.automation?.length ?? 0,
|
||||
scenes: related?.scene?.length ?? 0,
|
||||
scripts: related?.script?.length ?? 0,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues<this>) {
|
||||
super.firstUpdated(changedProps);
|
||||
loadAreaRegistryDetailDialog();
|
||||
loadAreaAddToDialog();
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues<this>) {
|
||||
@@ -244,23 +140,6 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
// When new_triggers_conditions labs feature is promoted, this whole method can be removed.
|
||||
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
|
||||
if (!isComponentLoaded(this.hass!.config, "automation")) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
subscribeLabFeature(
|
||||
this.hass!.connection,
|
||||
"automation",
|
||||
"new_triggers_conditions",
|
||||
(feature) => {
|
||||
this._newTriggersConditions = feature.enabled;
|
||||
}
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.hass.areas || !this.hass.devices || !this.hass.entities) {
|
||||
return nothing;
|
||||
@@ -283,10 +162,6 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
this._entityReg
|
||||
);
|
||||
const { devices, entities } = memberships;
|
||||
const quickLinkCounts = this._getQuickLinkCounts(
|
||||
memberships,
|
||||
this._related
|
||||
);
|
||||
|
||||
// Pre-compute the entity and device names, so we can sort by them
|
||||
if (devices) {
|
||||
@@ -345,13 +220,6 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
));
|
||||
}
|
||||
|
||||
const nonAutomatedEntities = entities.filter(
|
||||
(entity) =>
|
||||
!["scene", "script", "automation"].includes(
|
||||
computeDomain(entity.entity_id)
|
||||
)
|
||||
);
|
||||
|
||||
return html`
|
||||
<hass-subpage
|
||||
.hass=${this.hass}
|
||||
@@ -370,21 +238,6 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
${NAVIGATION_ACTIONS.map(
|
||||
(action) => html`
|
||||
<ha-dropdown-item value=${action.value}>
|
||||
<ha-svg-icon slot="icon" .path=${action.icon}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.areas.quick_links.${action.countKey}`,
|
||||
{ count: quickLinkCounts[action.countKey] }
|
||||
)}
|
||||
<ha-icon-next slot="details"></ha-icon-next>
|
||||
</ha-dropdown-item>
|
||||
`
|
||||
)}
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<ha-dropdown-item value="edit" .data=${area}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPencil}> </ha-svg-icon>
|
||||
${this.hass.localize("ui.panel.config.areas.edit_settings")}
|
||||
@@ -411,41 +264,15 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
class="img-edit-btn"
|
||||
></ha-icon-button>
|
||||
</div>`
|
||||
: nothing}
|
||||
${area.picture && !this._newTriggersConditions
|
||||
? nothing
|
||||
: html`<div class="action-buttons">
|
||||
${area.picture
|
||||
? nothing
|
||||
: html`<ha-button
|
||||
appearance="filled"
|
||||
.entry=${area}
|
||||
@click=${this._showSettings}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${mdiImagePlus}
|
||||
slot="start"
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.add_picture"
|
||||
)}
|
||||
</ha-button>`}
|
||||
${this._newTriggersConditions
|
||||
? html`<ha-button
|
||||
appearance="filled"
|
||||
variant="brand"
|
||||
@click=${this._showAddToDialog}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiPlus}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.add_to.title"
|
||||
)}
|
||||
</ha-button>`
|
||||
: nothing}
|
||||
</div>`}
|
||||
: html`<ha-button
|
||||
appearance="filled"
|
||||
size="small"
|
||||
.entry=${area}
|
||||
@click=${this._showSettings}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiImagePlus} slot="start"></ha-svg-icon>
|
||||
${this.hass.localize("ui.panel.config.areas.add_picture")}
|
||||
</ha-button>`}
|
||||
<ha-card
|
||||
outlined
|
||||
.header=${this.hass.localize("ui.panel.config.devices.caption")}
|
||||
@@ -476,19 +303,23 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
"ui.panel.config.areas.editor.linked_entities_caption"
|
||||
)}
|
||||
>
|
||||
${nonAutomatedEntities.length
|
||||
${entities.length
|
||||
? html`<ha-list>
|
||||
${nonAutomatedEntities.map(
|
||||
(entity) => html`
|
||||
<ha-list-item
|
||||
@click=${this._openEntity}
|
||||
.entity=${entity}
|
||||
hasMeta
|
||||
>
|
||||
<span>${entity.name}</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`
|
||||
${entities.map((entity) =>
|
||||
["scene", "script", "automation"].includes(
|
||||
computeDomain(entity.entity_id)
|
||||
)
|
||||
? ""
|
||||
: html`
|
||||
<ha-list-item
|
||||
@click=${this._openEntity}
|
||||
.entity=${entity}
|
||||
hasMeta
|
||||
>
|
||||
<span>${entity.name}</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`
|
||||
)}</ha-list
|
||||
>`
|
||||
: html`
|
||||
@@ -778,39 +609,9 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
this._related = await findRelated(this.hass, "area", this.areaId);
|
||||
}
|
||||
|
||||
private _showAddToDialog() {
|
||||
const area = this.hass.areas[this.areaId];
|
||||
if (!area) {
|
||||
return;
|
||||
}
|
||||
showAreaAddToDialog(this, {
|
||||
areaId: area.area_id,
|
||||
entityIds: this._areaEntityIds,
|
||||
});
|
||||
}
|
||||
|
||||
private get _areaEntityIds(): string[] {
|
||||
const memberships = this._memberships(
|
||||
this.areaId,
|
||||
Object.values(this.hass.devices),
|
||||
this._entityReg
|
||||
);
|
||||
|
||||
return this._allEntities(memberships);
|
||||
}
|
||||
|
||||
private _handleMenuAction(
|
||||
ev: HaDropdownSelectEvent<string, AreaRegistryEntry>
|
||||
) {
|
||||
private _handleMenuAction(ev: HaDropdownSelectEvent) {
|
||||
const action = ev.detail?.item?.value;
|
||||
const entry = ev.detail?.item?.data;
|
||||
|
||||
const navAction = NAVIGATION_ACTIONS.find((a) => a.value === action);
|
||||
if (navAction) {
|
||||
navigate(`${navAction.path}?historyBack=1&area=${this.areaId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const entry = (ev.detail?.item as any)?.data as AreaRegistryEntry;
|
||||
switch (action) {
|
||||
case "edit":
|
||||
this._openDialog(entry);
|
||||
@@ -821,19 +622,15 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private _showSettings(
|
||||
ev: HASSDomCurrentTargetEvent<
|
||||
HTMLButtonElement & { entry: AreaRegistryEntry }
|
||||
>
|
||||
) {
|
||||
this._openDialog(ev.currentTarget.entry);
|
||||
private _showSettings(ev: MouseEvent) {
|
||||
const entry: AreaRegistryEntry = (ev.currentTarget! as any).entry;
|
||||
this._openDialog(entry);
|
||||
}
|
||||
|
||||
private _openEntity(
|
||||
ev: HASSDomCurrentTargetEvent<HTMLElement & { entity: EntityRegistryEntry }>
|
||||
) {
|
||||
private _openEntity(ev) {
|
||||
const entry: EntityRegistryEntry = (ev.currentTarget as any).entity;
|
||||
showMoreInfoDialog(this, {
|
||||
entityId: ev.currentTarget.entity.entity_id,
|
||||
entityId: entry.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -875,14 +672,6 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: var(--ha-space-2);
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: var(
|
||||
--ha-card-border-radius,
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
||||
export interface AreaAddToDialogParams {
|
||||
areaId: string;
|
||||
entityIds: string[];
|
||||
}
|
||||
|
||||
export const loadAreaAddToDialog = () => import("./dialog-area-add-to");
|
||||
|
||||
export const showAreaAddToDialog = (
|
||||
element: HTMLElement,
|
||||
params: AreaAddToDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-area-add-to",
|
||||
dialogImport: loadAreaAddToDialog,
|
||||
dialogParams: params,
|
||||
});
|
||||
};
|
||||
@@ -107,7 +107,6 @@ export default class HaAutomationActionEditor extends LitElement {
|
||||
ev.stopPropagation();
|
||||
const value = {
|
||||
...(this.action.alias ? { alias: this.action.alias } : {}),
|
||||
...(this.action.comment ? { comment: this.action.comment } : {}),
|
||||
...ev.detail.value,
|
||||
};
|
||||
fireEvent(this, "value-changed", { value });
|
||||
|
||||
@@ -7,8 +7,6 @@ import {
|
||||
mdiArrowUp,
|
||||
mdiCheckboxBlankOutline,
|
||||
mdiCheckboxOutline,
|
||||
mdiCommentEditOutline,
|
||||
mdiCommentTextOutline,
|
||||
mdiContentCopy,
|
||||
mdiContentCut,
|
||||
mdiContentPaste,
|
||||
@@ -36,7 +34,6 @@ import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
import { computeObjectId } from "../../../../common/entity/compute_object_id";
|
||||
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
||||
import { truncateWithEllipsis } from "../../../../common/string/truncate-with-ellipsis";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||
import "../../../../components/automation/ha-automation-row";
|
||||
@@ -297,11 +294,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
?.target
|
||||
: undefined;
|
||||
|
||||
const commentTooltipText = truncateWithEllipsis(
|
||||
this.action.comment?.trim() || "",
|
||||
250
|
||||
);
|
||||
|
||||
return html`
|
||||
${type === "service" && "action" in this.action && this.action.action
|
||||
? html`
|
||||
@@ -337,21 +329,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
serviceTargetSpec
|
||||
)
|
||||
: nothing}
|
||||
${commentTooltipText
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
id="comment-icon"
|
||||
.path=${mdiCommentTextOutline}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.comment.label"
|
||||
)}
|
||||
class="comment-indicator"
|
||||
></ha-svg-icon
|
||||
><ha-tooltip for="comment-icon"
|
||||
><p>${commentTooltipText}</p></ha-tooltip
|
||||
>
|
||||
`
|
||||
: nothing}
|
||||
${type !== "condition" &&
|
||||
(this.action as NonConditionAction).continue_on_error === true
|
||||
? html`<ha-svg-icon
|
||||
@@ -407,14 +384,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
)
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item value="edit_comment">
|
||||
<ha-svg-icon slot="icon" .path=${mdiCommentEditOutline}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
`ui.panel.config.automation.editor.comment.${this.action.comment ? "edit" : "add"}`
|
||||
)
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
<wa-divider></wa-divider>
|
||||
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
|
||||
<ha-svg-icon
|
||||
@@ -941,38 +910,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
}
|
||||
};
|
||||
|
||||
private _editCommentAction = async (): Promise<void> => {
|
||||
const comment = await showPromptDialog(this, {
|
||||
title: this.hass.localize(
|
||||
`ui.panel.config.automation.editor.comment.${this.action.comment ? "edit" : "add"}`
|
||||
),
|
||||
inputLabel: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.comment.label"
|
||||
),
|
||||
inputType: "string",
|
||||
defaultValue: this.action.comment,
|
||||
confirmText: this.hass.localize("ui.common.submit"),
|
||||
multiline: true,
|
||||
});
|
||||
if (comment !== null) {
|
||||
const value = { ...this.action };
|
||||
if (comment === "") {
|
||||
delete value.comment;
|
||||
} else {
|
||||
value.comment = comment;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value,
|
||||
});
|
||||
|
||||
if (this._selected && this.optionsInSidebar) {
|
||||
this.openSidebar(value); // refresh sidebar
|
||||
} else if (this._yamlMode) {
|
||||
this._actionEditor?.yamlEditor?.setValue(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private _duplicateAction = () => {
|
||||
fireEvent(this, "duplicate");
|
||||
};
|
||||
@@ -1089,7 +1026,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
rename: () => {
|
||||
this._renameAction();
|
||||
},
|
||||
editComment: this._editCommentAction,
|
||||
toggleYamlMode: () => {
|
||||
this._toggleYamlMode();
|
||||
this.openSidebar();
|
||||
@@ -1185,9 +1121,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
case "rename":
|
||||
this._renameAction();
|
||||
break;
|
||||
case "edit_comment":
|
||||
this._editCommentAction();
|
||||
break;
|
||||
case "duplicate":
|
||||
this._duplicateAction();
|
||||
break;
|
||||
|
||||
@@ -18,7 +18,6 @@ import { getValueFromDynamic, isDynamic } from "../../../../data/automation";
|
||||
import type { Action } from "../../../../data/script";
|
||||
import { EDITOR_SAVE_FAB_TOAST_BOTTOM_OFFSET } from "../editor-toast";
|
||||
import {
|
||||
getAddAutomationElementTargetFromQuery,
|
||||
PASTE_VALUE,
|
||||
showAddAutomationElementDialog,
|
||||
} from "../show-add-automation-element-dialog";
|
||||
@@ -42,8 +41,6 @@ export default class HaAutomationAction extends AutomationSortableListMixin<Acti
|
||||
@queryAll("ha-automation-action-row")
|
||||
private _actionRowElements?: HaAutomationActionRow[];
|
||||
|
||||
private _openedAddDialogFromQuery = false;
|
||||
|
||||
protected get items(): Action[] {
|
||||
return this.actions;
|
||||
}
|
||||
@@ -136,34 +133,6 @@ export default class HaAutomationAction extends AutomationSortableListMixin<Acti
|
||||
protected updated(changedProps: PropertyValues<this>) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
|
||||
const addActionTargetFromQuery = getAddAutomationElementTargetFromQuery(
|
||||
this.hass.states,
|
||||
this.hass.devices,
|
||||
this.hass.areas,
|
||||
"action"
|
||||
);
|
||||
|
||||
if (changedProps.has("actions") && addActionTargetFromQuery) {
|
||||
this._openedAddDialogFromQuery = false;
|
||||
}
|
||||
|
||||
if (
|
||||
!this._openedAddDialogFromQuery &&
|
||||
this.root &&
|
||||
!this.disabled &&
|
||||
this.actions.length === 0 &&
|
||||
addActionTargetFromQuery
|
||||
) {
|
||||
this._openedAddDialogFromQuery = true;
|
||||
queueMicrotask(() => this._addActionDialog());
|
||||
} else if (this._openedAddDialogFromQuery && !addActionTargetFromQuery) {
|
||||
this._openedAddDialogFromQuery = false;
|
||||
}
|
||||
|
||||
if (
|
||||
changedProps.has("actions") &&
|
||||
(this.focusLastItemOnChange || this.focusItemIndexOnChange !== undefined)
|
||||
|
||||
@@ -112,11 +112,11 @@ export class HaDeviceAction extends LitElement {
|
||||
.schema=${this._capabilities.extra_fields}
|
||||
.disabled=${this.disabled}
|
||||
.computeLabel=${localizeExtraFieldsComputeLabelCallback(
|
||||
this.hass.localize,
|
||||
this.hass,
|
||||
this.action
|
||||
)}
|
||||
.computeHelper=${localizeExtraFieldsComputeHelperCallback(
|
||||
this.hass.localize,
|
||||
this.hass,
|
||||
this.action
|
||||
)}
|
||||
@value-changed=${this._extraFieldsChanged}
|
||||
@@ -149,7 +149,7 @@ export class HaDeviceAction extends LitElement {
|
||||
|
||||
private async _getCapabilities() {
|
||||
this._capabilities = this.action.domain
|
||||
? await fetchDeviceActionCapabilities(this.hass.callWS, this.action)
|
||||
? await fetchDeviceActionCapabilities(this.hass, this.action)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
@@ -186,10 +186,6 @@ export class HaDeviceAction extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
margin-bottom: var(--ha-space-3);
|
||||
}
|
||||
ha-device-picker {
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user