mirror of
https://github.com/home-assistant/frontend.git
synced 2026-09-28 07:51:10 +00:00
Compare commits
49
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
747f160a02 | ||
|
|
baabdaf3fa | ||
|
|
d6b85ea47e | ||
|
|
66321c57d2 | ||
|
|
f990c85b32 | ||
|
|
97336c4009 | ||
|
|
c3e84e4bb4 | ||
|
|
90f8e2f8d6 | ||
|
|
daaa673113 | ||
|
|
113d645dad | ||
|
|
2d05e8f90f | ||
|
|
c376786885 | ||
|
|
61e15a6a4a | ||
|
|
b4d65e3b83 | ||
|
|
728bb8055c | ||
|
|
70e8cd376b | ||
|
|
9fc555ced6 | ||
|
|
72b3a52a2e | ||
|
|
dbbaa3e626 | ||
|
|
11401b2a79 | ||
|
|
01124ffbb2 | ||
|
|
fd19a0e4e3 | ||
|
|
40270f5a86 | ||
|
|
e7d76ce6c9 | ||
|
|
6c53fd6c4f | ||
|
|
d54b818d83 | ||
|
|
c4f1a1bea2 | ||
|
|
0cb50303c3 | ||
|
|
d16e04a2b4 | ||
|
|
21957ed41f | ||
|
|
8b5302ac06 | ||
|
|
4db73f6a3d | ||
|
|
7c5a7c81e5 | ||
|
|
8da3713ec4 | ||
|
|
e7f60b8583 | ||
|
|
6d7ecb90a1 | ||
|
|
8fcf822025 | ||
|
|
6878828996 | ||
|
|
5c187a4e13 | ||
|
|
56f695f4ed | ||
|
|
e0b7b929dd | ||
|
|
f0c63f8840 | ||
|
|
1ac04b0a65 | ||
|
|
a7eb100132 | ||
|
|
237bf119e7 | ||
|
|
4a6765d886 | ||
|
|
8c5c35ca06 | ||
|
|
a3cb6d8202 | ||
|
|
f60a40de72 |
@@ -1,24 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
# Dependabot only scans .github/workflows by default; composite actions
|
||||
# under .github/actions must be listed explicitly to stay updated.
|
||||
# https://github.com/dependabot/dependabot-core/issues/6704
|
||||
directories:
|
||||
- "/"
|
||||
- "/.github/actions/setup"
|
||||
- "/.github/actions/build"
|
||||
- "/.github/actions/netlify-deploy"
|
||||
schedule:
|
||||
interval: weekly
|
||||
time: "06:00"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
open-pull-requests-limit: 10
|
||||
groups:
|
||||
codeql-action:
|
||||
patterns:
|
||||
- "github/codeql-action/*"
|
||||
labels:
|
||||
- Dependencies
|
||||
- GitHub Actions
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
deploy_dev:
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy Development
|
||||
if: github.event_name != 'push'
|
||||
if: github.repository == 'home-assistant/frontend' && github.event_name != 'push'
|
||||
environment:
|
||||
name: Cast Development
|
||||
url: ${{ steps.deploy.outputs.netlify_url }}
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
deploy_master:
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy Production
|
||||
if: github.event_name == 'push'
|
||||
if: github.repository == 'home-assistant/frontend' && github.event_name == 'push'
|
||||
environment:
|
||||
name: Cast Production
|
||||
url: ${{ steps.deploy.outputs.netlify_url }}
|
||||
|
||||
@@ -32,12 +32,12 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
||||
uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
build-mode: none
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
||||
uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
|
||||
with:
|
||||
category: "/language:javascript-typescript"
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
deploy_dev:
|
||||
runs-on: ubuntu-latest
|
||||
name: Demo Development
|
||||
if: github.event_name != 'push' || github.ref_name != 'master'
|
||||
if: github.repository == 'home-assistant/frontend' && (github.event_name != 'push' || github.ref_name != 'master')
|
||||
environment:
|
||||
name: Demo Development
|
||||
url: ${{ steps.deploy.outputs.unique_deploy_url }}
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
deploy_master:
|
||||
runs-on: ubuntu-latest
|
||||
name: Demo Production
|
||||
if: github.event_name == 'push' && github.ref_name == 'master'
|
||||
if: github.repository == 'home-assistant/frontend' && github.event_name == 'push' && github.ref_name == 'master'
|
||||
environment:
|
||||
name: Demo Production
|
||||
url: ${{ steps.deploy.outputs.netlify_url }}
|
||||
|
||||
@@ -13,6 +13,7 @@ env:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
if: github.repository == 'home-assistant/frontend'
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: Design
|
||||
|
||||
@@ -11,6 +11,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
if: github.repository == 'home-assistant/frontend'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6.0.2
|
||||
|
||||
@@ -14,6 +14,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
nightly:
|
||||
if: github.repository == 'home-assistant/frontend'
|
||||
name: Nightly
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
@@ -113,6 +114,7 @@ jobs:
|
||||
# needed for the uploaded artifact, not the wheel, so they are downloaded in
|
||||
# parallel here instead of blocking the build above.
|
||||
translations:
|
||||
if: github.repository == 'home-assistant/frontend'
|
||||
name: Translations
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@@ -12,6 +12,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
if: github.repository == 'home-assistant/frontend'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 90 days stale policy
|
||||
|
||||
@@ -14,6 +14,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
if: github.repository == 'home-assistant/frontend'
|
||||
name: Sync
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
|
||||
@@ -15,6 +15,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
if: github.repository == 'home-assistant/frontend'
|
||||
name: Sync
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
|
||||
+174
-174
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.18.0.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.18.1.cjs
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"_comment": "Initial JS budget (raw/uncompressed bytes) for the cold-load critical entrypoints. Enforced by build-scripts/check-bundle-size.cjs in CI. Re-seed after an intentional change with `--update --headroom=<percent>`.",
|
||||
"frontend-modern": {
|
||||
"app": 576583,
|
||||
"core": 54790,
|
||||
"authorize": 543547,
|
||||
"onboarding": 655556
|
||||
"app": 593517,
|
||||
"core": 54884,
|
||||
"authorize": 557490,
|
||||
"onboarding": 676131
|
||||
},
|
||||
"frontend-legacy": {
|
||||
"app": 717124,
|
||||
"core": 181583,
|
||||
"authorize": 699175,
|
||||
"onboarding": 816133
|
||||
"app": 738388,
|
||||
"core": 188119,
|
||||
"authorize": 716748,
|
||||
"onboarding": 840680
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,16 +47,23 @@ const convertToJSON = async (
|
||||
`Intl\\.${obj}\\.${addFunc}\\((?<data>.*)\\)`,
|
||||
"s"
|
||||
);
|
||||
const encodedData = localeData.match(
|
||||
/\(JSON\.parse\((?<data>"(?:[^"\\]|\\.)*")\)\);?\s*$/s
|
||||
)?.groups?.data;
|
||||
localeData = firstBlock.match(dataRegex)?.groups?.data;
|
||||
if (!localeData) {
|
||||
if (!localeData && !encodedData) {
|
||||
throw Error(`Failed to extract data for language ${lang} from ${pkg}`);
|
||||
}
|
||||
// Parse to validate JSON, then stringify to minify
|
||||
try {
|
||||
localeData = JSON.stringify(JSON.parse(localeData));
|
||||
localeData = JSON.stringify(
|
||||
JSON.parse(localeData || JSON.parse(encodedData))
|
||||
);
|
||||
await writeFile(join(outDir, `${pkg}/${lang}.json`), localeData);
|
||||
} catch (e) {
|
||||
throw Error(`Failed to parse JSON for language ${lang} from ${pkg}: ${e}`);
|
||||
throw Error(`Failed to parse JSON for language ${lang} from ${pkg}: ${e}`, {
|
||||
cause: e,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ const pipelines: AssistPipeline[] = [
|
||||
tts_voice: "JennyNeural",
|
||||
wake_word_entity: null,
|
||||
wake_word_id: null,
|
||||
prefer_local_intents: false,
|
||||
},
|
||||
{
|
||||
id: "01local",
|
||||
@@ -29,6 +30,7 @@ const pipelines: AssistPipeline[] = [
|
||||
tts_voice: null,
|
||||
wake_word_entity: null,
|
||||
wake_word_id: null,
|
||||
prefer_local_intents: false,
|
||||
},
|
||||
{
|
||||
id: "01chatgpt",
|
||||
@@ -43,6 +45,7 @@ const pipelines: AssistPipeline[] = [
|
||||
tts_voice: "ElviraNeural",
|
||||
wake_word_entity: null,
|
||||
wake_word_id: null,
|
||||
prefer_local_intents: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type {
|
||||
BackupAgentsInfo,
|
||||
BackupConfig,
|
||||
BackupContent,
|
||||
BackupInfo,
|
||||
} from "../../../src/data/backup";
|
||||
import { BackupScheduleRecurrence } from "../../../src/data/backup";
|
||||
@@ -28,7 +27,16 @@ const backupInfo: BackupInfo = {
|
||||
"backup.local": { size: 1024 * 1024 * 512, protected: true },
|
||||
"cloud.cloud": { size: 1024 * 1024 * 512, protected: true },
|
||||
},
|
||||
} as BackupContent,
|
||||
failed_agent_ids: [],
|
||||
failed_addons: [],
|
||||
failed_folders: [],
|
||||
extra_metadata: {},
|
||||
addons: [],
|
||||
database_included: true,
|
||||
folders: [],
|
||||
homeassistant_included: true,
|
||||
homeassistant_version: "2026.9.0",
|
||||
},
|
||||
],
|
||||
agent_errors: {},
|
||||
last_attempted_automatic_backup: recent,
|
||||
|
||||
@@ -20,12 +20,14 @@ const demoWebhooks: Webhook[] = [
|
||||
domain: "automation",
|
||||
name: "Front door motion",
|
||||
local_only: false,
|
||||
allowed_methods: ["POST", "PUT"],
|
||||
},
|
||||
{
|
||||
webhook_id: "demo_companion_app",
|
||||
domain: "mobile_app",
|
||||
name: "Companion app",
|
||||
local_only: false,
|
||||
allowed_methods: ["POST", "PUT"],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -49,12 +51,13 @@ const demoCloudhooks = Object.fromEntries(
|
||||
// object directly and reset on reload.
|
||||
const cloudStatus: CloudStatusLoggedIn = {
|
||||
logged_in: true,
|
||||
auto_login: null,
|
||||
cloud: "connected",
|
||||
cloud_last_disconnect_reason: null,
|
||||
email: "[email protected]",
|
||||
google_registered: false,
|
||||
google_local_connected: false,
|
||||
google_entities: emptyFilter(),
|
||||
google_domains: ["light", "switch", "climate", "cover"],
|
||||
alexa_registered: true,
|
||||
alexa_entities: emptyFilter(),
|
||||
remote_domain: "demo-instance.ui.nabu.casa",
|
||||
@@ -75,10 +78,11 @@ const cloudStatus: CloudStatusLoggedIn = {
|
||||
alexa_enabled: true,
|
||||
remote_enabled: true,
|
||||
remote_allow_remote_enable: true,
|
||||
strict_connection: "disabled",
|
||||
google_secure_devices_pin: undefined,
|
||||
google_secure_devices_pin: null,
|
||||
cloudhooks: demoCloudhooks,
|
||||
alexa_default_expose: null,
|
||||
alexa_report_state: true,
|
||||
google_default_expose: null,
|
||||
google_report_state: true,
|
||||
tts_default_voice: ["en-US", "JennyNeural"],
|
||||
cloud_ice_servers_enabled: false,
|
||||
|
||||
@@ -192,9 +192,9 @@ const buildNodeDiagnostics = (
|
||||
node_type: node.is_bridge
|
||||
? NodeType.BRIDGE
|
||||
: (NODE_TYPE_BY_ROLE[node.role ?? ""] ?? NodeType.END_DEVICE),
|
||||
network_name: node.network_name ?? node.ssid ?? undefined,
|
||||
network_name: node.network_name ?? node.ssid ?? null,
|
||||
ip_adresses: [nodeIpAddress(node)],
|
||||
mac_address: node.ext_address?.match(/.{2}/g)?.join(":"),
|
||||
mac_address: node.ext_address?.match(/.{2}/g)?.join(":") ?? null,
|
||||
available: node.available !== false,
|
||||
active_fabrics: FABRICS,
|
||||
active_fabric_index: 1,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type {
|
||||
MQTTDeviceDebugInfo,
|
||||
MQTTLoggedMessage,
|
||||
MQTTMessage,
|
||||
} from "../../../../../src/data/mqtt";
|
||||
import type { MockHomeAssistant } from "../../../../../src/fake_data/provide_hass";
|
||||
@@ -35,11 +36,11 @@ const resolveFilter = (filter: string): string =>
|
||||
})
|
||||
.join("/") || "homeassistant/status";
|
||||
|
||||
const buildMessage = (topic: string, qos: number): MQTTMessage => ({
|
||||
const buildMessage = (topic: string, qos: number): MQTTLoggedMessage => ({
|
||||
topic,
|
||||
payload: (PAYLOADS[topic] ?? PAYLOADS.default)(),
|
||||
qos,
|
||||
retain: 0,
|
||||
retain: false,
|
||||
time: new Date().toISOString(),
|
||||
});
|
||||
|
||||
@@ -167,11 +168,11 @@ export const mockMqtt = (hass: MockHomeAssistant) => {
|
||||
?.filter((action) => action.action === "mqtt.publish")
|
||||
.forEach((action) => {
|
||||
const topic = String(action.data?.topic ?? "");
|
||||
const message: MQTTMessage = {
|
||||
const message: MQTTLoggedMessage = {
|
||||
topic,
|
||||
payload: String(action.data?.payload ?? ""),
|
||||
qos: Number(action.data?.qos ?? 0),
|
||||
retain: action.data?.retain ? 1 : 0,
|
||||
retain: Boolean(action.data?.retain),
|
||||
time: new Date().toISOString(),
|
||||
};
|
||||
subscriptions.forEach((subscription) => {
|
||||
|
||||
@@ -45,7 +45,7 @@ const DEVICES: ZHADevice[] = [
|
||||
ieee: COORDINATOR_IEEE,
|
||||
nwk: 0x0000,
|
||||
lqi: 255,
|
||||
rssi: "0",
|
||||
rssi: 0,
|
||||
last_seen: minutesAgo(0),
|
||||
manufacturer: "Nabu Casa",
|
||||
model: "Connect ZBT-1",
|
||||
@@ -72,7 +72,7 @@ const DEVICES: ZHADevice[] = [
|
||||
ieee: PORCH_IEEE,
|
||||
nwk: 0x1a2b,
|
||||
lqi: 224,
|
||||
rssi: "-58",
|
||||
rssi: -58,
|
||||
last_seen: minutesAgo(2),
|
||||
manufacturer: "IKEA of Sweden",
|
||||
model: "TRADFRI bulb E27 CWS 806lm",
|
||||
@@ -99,7 +99,7 @@ const DEVICES: ZHADevice[] = [
|
||||
ieee: MOTION_IEEE,
|
||||
nwk: 0x5e6f,
|
||||
lqi: 142,
|
||||
rssi: "-77",
|
||||
rssi: -77,
|
||||
last_seen: minutesAgo(9),
|
||||
manufacturer: "IKEA of Sweden",
|
||||
model: "TRADFRI motion sensor",
|
||||
@@ -122,7 +122,7 @@ const DEVICES: ZHADevice[] = [
|
||||
ieee: PLUG_IEEE,
|
||||
nwk: 0x3c4d,
|
||||
lqi: 198,
|
||||
rssi: "-64",
|
||||
rssi: -64,
|
||||
last_seen: minutesAgo(240),
|
||||
manufacturer: "Innr",
|
||||
model: "SP 220",
|
||||
@@ -145,7 +145,7 @@ const DEVICES: ZHADevice[] = [
|
||||
ieee: KITCHEN_IEEE,
|
||||
nwk: 0x9c0d,
|
||||
lqi: 186,
|
||||
rssi: "-69",
|
||||
rssi: -69,
|
||||
last_seen: minutesAgo(4),
|
||||
manufacturer: "IKEA of Sweden",
|
||||
model: "TRADFRI on/off switch",
|
||||
@@ -168,7 +168,7 @@ const DEVICES: ZHADevice[] = [
|
||||
ieee: LANDING_IEEE,
|
||||
nwk: 0xab12,
|
||||
lqi: 164,
|
||||
rssi: "-74",
|
||||
rssi: -74,
|
||||
last_seen: minutesAgo(6),
|
||||
manufacturer: "Aqara",
|
||||
model: "WSDCGQ11LM",
|
||||
@@ -191,7 +191,7 @@ const DEVICES: ZHADevice[] = [
|
||||
ieee: GARAGE_IEEE,
|
||||
nwk: 0xcd34,
|
||||
lqi: 118,
|
||||
rssi: "-83",
|
||||
rssi: -83,
|
||||
last_seen: minutesAgo(21),
|
||||
manufacturer: "Aqara",
|
||||
model: "MCCGQ11LM",
|
||||
@@ -214,7 +214,7 @@ const DEVICES: ZHADevice[] = [
|
||||
ieee: OFFICE_IEEE,
|
||||
nwk: 0x7a8b,
|
||||
lqi: 211,
|
||||
rssi: "-61",
|
||||
rssi: -61,
|
||||
last_seen: minutesAgo(1),
|
||||
manufacturer: "Innr",
|
||||
model: "SP 240",
|
||||
|
||||
@@ -160,7 +160,6 @@ const buildNodeStatistics = (
|
||||
return {
|
||||
event: "statistics updated",
|
||||
source: "node",
|
||||
nodeId,
|
||||
node_id: nodeId,
|
||||
commands_tx: 1200 + nodeId * 7,
|
||||
commands_rx: 980 + nodeId * 5,
|
||||
|
||||
@@ -11,7 +11,6 @@ export const mockPersistentNotification = (hass: MockHomeAssistant) => {
|
||||
message: "There was motion detected in the backyard.",
|
||||
notification_id: "demo-1",
|
||||
title: "Motion Detected!",
|
||||
status: "unread",
|
||||
},
|
||||
},
|
||||
} as PersistentNotificationMessage);
|
||||
|
||||
@@ -121,12 +121,14 @@ const demoWebhooks: Webhook[] = [
|
||||
domain: "automation",
|
||||
name: "Front door motion",
|
||||
local_only: false,
|
||||
allowed_methods: ["POST", "PUT"],
|
||||
},
|
||||
{
|
||||
webhook_id: "demo_companion_app",
|
||||
domain: "mobile_app",
|
||||
name: "Companion app",
|
||||
local_only: false,
|
||||
allowed_methods: ["POST", "PUT"],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -155,12 +157,13 @@ const buildCloudStatus = (scenario: CloudDemoScenario): CloudStatusLoggedIn => {
|
||||
: {};
|
||||
return {
|
||||
logged_in: true,
|
||||
auto_login: null,
|
||||
cloud: "connected",
|
||||
cloud_last_disconnect_reason: null,
|
||||
email: "[email protected]",
|
||||
google_registered: scenario.google,
|
||||
google_local_connected: false,
|
||||
google_entities: emptyFilter(),
|
||||
google_domains: ["light", "switch", "climate", "cover"],
|
||||
alexa_registered: scenario.alexa,
|
||||
alexa_entities: emptyFilter(),
|
||||
remote_domain: "demo-instance.ui.nabu.casa",
|
||||
@@ -181,10 +184,11 @@ const buildCloudStatus = (scenario: CloudDemoScenario): CloudStatusLoggedIn => {
|
||||
alexa_enabled: scenario.alexa,
|
||||
remote_enabled: scenario.remote,
|
||||
remote_allow_remote_enable: true,
|
||||
strict_connection: "disabled",
|
||||
google_secure_devices_pin: undefined,
|
||||
google_secure_devices_pin: null,
|
||||
cloudhooks,
|
||||
alexa_default_expose: null,
|
||||
alexa_report_state: true,
|
||||
google_default_expose: null,
|
||||
google_report_state: true,
|
||||
tts_default_voice: ["en-US", "JennyNeural"],
|
||||
cloud_ice_servers_enabled: scenario.webrtc,
|
||||
|
||||
+29
-29
@@ -42,25 +42,25 @@
|
||||
"@babel/runtime": "8.0.5",
|
||||
"@braintree/sanitize-url": "7.1.2",
|
||||
"@codemirror/autocomplete": "6.20.3",
|
||||
"@codemirror/commands": "6.11.0",
|
||||
"@codemirror/commands": "6.11.1",
|
||||
"@codemirror/lang-jinja": "6.0.1",
|
||||
"@codemirror/lang-yaml": "6.1.3",
|
||||
"@codemirror/language": "6.12.4",
|
||||
"@codemirror/lint": "6.9.7",
|
||||
"@codemirror/search": "6.7.2",
|
||||
"@codemirror/state": "6.7.4",
|
||||
"@codemirror/view": "6.43.11",
|
||||
"@codemirror/state": "6.7.6",
|
||||
"@codemirror/view": "6.43.13",
|
||||
"@date-fns/tz": "1.5.0",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "7.6.2",
|
||||
"@formatjs/intl-displaynames": "7.3.14",
|
||||
"@formatjs/intl-durationformat": "0.11.0",
|
||||
"@formatjs/intl-datetimeformat": "7.8.0",
|
||||
"@formatjs/intl-displaynames": "7.3.15",
|
||||
"@formatjs/intl-durationformat": "0.11.1",
|
||||
"@formatjs/intl-getcanonicallocales": "3.2.12",
|
||||
"@formatjs/intl-listformat": "8.3.14",
|
||||
"@formatjs/intl-locale": "5.3.11",
|
||||
"@formatjs/intl-numberformat": "9.4.1",
|
||||
"@formatjs/intl-pluralrules": "6.3.14",
|
||||
"@formatjs/intl-relativetimeformat": "12.3.14",
|
||||
"@formatjs/intl-listformat": "8.3.15",
|
||||
"@formatjs/intl-locale": "5.3.12",
|
||||
"@formatjs/intl-numberformat": "9.4.3",
|
||||
"@formatjs/intl-pluralrules": "6.3.15",
|
||||
"@formatjs/intl-relativetimeformat": "12.3.15",
|
||||
"@fullcalendar/core": "6.1.21",
|
||||
"@fullcalendar/daygrid": "6.1.21",
|
||||
"@fullcalendar/interaction": "6.1.21",
|
||||
@@ -68,7 +68,7 @@
|
||||
"@fullcalendar/luxon3": "6.1.21",
|
||||
"@fullcalendar/timegrid": "6.1.21",
|
||||
"@home-assistant/webawesome": "3.7.0-ha.1",
|
||||
"@lezer/highlight": "1.2.3",
|
||||
"@lezer/highlight": "1.2.4",
|
||||
"@lit-labs/motion": "1.1.0",
|
||||
"@lit-labs/observers": "2.1.0",
|
||||
"@lit-labs/virtualizer": "2.1.1",
|
||||
@@ -104,17 +104,17 @@
|
||||
"element-internals-polyfill": "3.0.2",
|
||||
"fuse.js": "7.5.0",
|
||||
"hls.js": "1.7.3",
|
||||
"home-assistant-js-websocket": "9.6.0",
|
||||
"home-assistant-js-websocket": "9.7.0",
|
||||
"idb-keyval": "6.3.0",
|
||||
"intl-messageformat": "11.2.15",
|
||||
"js-yaml": "5.4.1",
|
||||
"js-yaml": "5.4.2",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet.markercluster": "1.5.3",
|
||||
"lit": "3.3.3",
|
||||
"lit-html": "3.3.3",
|
||||
"luxon": "3.7.2",
|
||||
"maplibre-gl": "5.24.0",
|
||||
"marked": "18.0.12",
|
||||
"marked": "18.0.14",
|
||||
"memoize-one": "6.0.0",
|
||||
"nanoid": "6.0.1",
|
||||
"node-vibrant": "4.0.4",
|
||||
@@ -139,21 +139,21 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ampproject/remapping": "2.3.0",
|
||||
"@babel/core": "8.0.5",
|
||||
"@babel/core": "8.0.6",
|
||||
"@babel/helper-define-polyfill-provider": "1.0.0",
|
||||
"@babel/plugin-transform-runtime": "8.0.1",
|
||||
"@babel/preset-env": "8.0.5",
|
||||
"@babel/plugin-transform-runtime": "8.0.6",
|
||||
"@babel/preset-env": "8.0.6",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.22.3",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@gfx/zopfli": "1.0.15",
|
||||
"@html-eslint/eslint-plugin": "0.65.0",
|
||||
"@lokalise/node-api": "16.4.1",
|
||||
"@html-eslint/eslint-plugin": "0.66.1",
|
||||
"@lokalise/node-api": "16.6.0",
|
||||
"@octokit/auth-oauth-device": "8.0.5",
|
||||
"@octokit/plugin-retry": "8.1.1",
|
||||
"@octokit/rest": "22.0.1",
|
||||
"@playwright/test": "1.62.1",
|
||||
"@rsdoctor/rspack-plugin": "1.6.4",
|
||||
"@rspack/core": "2.2.3",
|
||||
"@rspack/core": "2.2.7",
|
||||
"@rspack/dev-server": "2.2.1",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.26",
|
||||
@@ -170,13 +170,13 @@
|
||||
"@types/tar": "7.0.87",
|
||||
"@typescript/native": "npm:[email protected]",
|
||||
"@versatiles/style": "5.13.1",
|
||||
"@vitest/coverage-v8": "5.0.0",
|
||||
"@vitest/coverage-v8": "5.0.1",
|
||||
"babel-loader": "10.1.1",
|
||||
"babel-plugin-polyfill-corejs3": "1.0.0",
|
||||
"browserslist": "4.28.9",
|
||||
"browserslist": "4.29.1",
|
||||
"browserslist-useragent-regexp": "4.1.4",
|
||||
"del": "8.0.1",
|
||||
"eslint": "10.10.0",
|
||||
"eslint": "10.11.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-import-resolver-webpack": "0.13.11",
|
||||
"eslint-plugin-import-x": "4.17.1",
|
||||
@@ -185,7 +185,7 @@
|
||||
"eslint-plugin-unused-imports": "4.4.1",
|
||||
"eslint-plugin-wc": "3.1.0",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.4.0",
|
||||
"fs-extra": "11.4.1",
|
||||
"generate-license-file": "4.2.5",
|
||||
"glob": "13.0.6",
|
||||
"globals": "17.12.0",
|
||||
@@ -213,10 +213,10 @@
|
||||
"terser-webpack-plugin": "5.6.1",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "8.70.0",
|
||||
"vite": "8.3.0",
|
||||
"typescript-eslint": "8.70.1",
|
||||
"vite": "8.3.1",
|
||||
"vite-tsconfig-paths": "6.1.1",
|
||||
"vitest": "5.0.0",
|
||||
"vitest": "5.0.1",
|
||||
"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"
|
||||
@@ -230,7 +230,7 @@
|
||||
"tslib": "2.8.1",
|
||||
"@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"
|
||||
},
|
||||
"packageManager": "[email protected].0",
|
||||
"packageManager": "[email protected].1",
|
||||
"volta": {
|
||||
"node": "24.21.0"
|
||||
}
|
||||
|
||||
+37
-1
@@ -10,7 +10,7 @@
|
||||
"group:monorepos",
|
||||
"group:recommended"
|
||||
],
|
||||
"enabledManagers": ["npm", "nvm", "custom.regex"],
|
||||
"enabledManagers": ["npm", "nvm", "custom.regex", "github-actions"],
|
||||
"minimumReleaseAge": "3 days",
|
||||
"postUpdateOptions": ["yarnDedupeHighest"],
|
||||
"lockFileMaintenance": {
|
||||
@@ -108,6 +108,42 @@
|
||||
"groupName": "Playwright",
|
||||
"matchPackageNames": ["@playwright/test", "mcr.microsoft.com/playwright"],
|
||||
"minimumGroupSize": 5
|
||||
},
|
||||
{
|
||||
"description": "MCR returns no release timestamps, so the minimum release age would keep the Playwright container pending forever. The npm package still gates the group.",
|
||||
"matchPackageNames": ["mcr.microsoft.com/playwright"],
|
||||
"minimumReleaseAge": null
|
||||
},
|
||||
{
|
||||
"description": "GitHub Actions: weekly, with a 7 day cooldown, like Dependabot did",
|
||||
"matchManagers": ["github-actions"],
|
||||
"addLabels": ["GitHub Actions"],
|
||||
"schedule": ["before 8am on monday"],
|
||||
"minimumReleaseAge": "7 days"
|
||||
},
|
||||
{
|
||||
"description": "Leave runner labels and action version inputs alone, Dependabot never updated those",
|
||||
"matchManagers": ["github-actions"],
|
||||
"matchDepTypes": ["github-runner", "uses-with"],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"description": "The Playwright container is already managed by the custom regex manager. Extracting it twice would break the minimumGroupSize count of the Playwright group.",
|
||||
"matchManagers": ["github-actions"],
|
||||
"matchPackageNames": ["mcr.microsoft.com/playwright"],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"description": "Group the github/codeql-action subactions into one PR (they share a release)",
|
||||
"matchPackageNames": ["/^github\\/codeql-action(?:\\/|$)/"],
|
||||
"groupName": "github/codeql-action",
|
||||
"groupSlug": "codeql-action"
|
||||
},
|
||||
{
|
||||
"description": "Group the actions/cache subactions into one PR (they share a release)",
|
||||
"matchPackageNames": ["/^actions\\/cache(?:\\/|$)/"],
|
||||
"groupName": "actions/cache",
|
||||
"groupSlug": "actions-cache"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@ export class HaAuthFlow extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public oauth2State?: string;
|
||||
|
||||
@property({ attribute: false }) public codeChallenge?: string;
|
||||
|
||||
@property({ attribute: false }) public codeChallengeMethod?: string;
|
||||
|
||||
@property({ attribute: false }) public step?: DataEntryFlowStep;
|
||||
|
||||
@property({ attribute: false }) public initStoreToken = false;
|
||||
@@ -241,7 +245,7 @@ export class HaAuthFlow extends LitElement {
|
||||
html`<ha-auth-form
|
||||
.data=${this._stepData!}
|
||||
.schema=${autocompleteLoginFields(step.data_schema)}
|
||||
.error=${step.errors}
|
||||
.error=${step.errors ?? undefined}
|
||||
.disabled=${this._submitting}
|
||||
.computeLabel=${this._computeLabelCallback(step)}
|
||||
.computeError=${this._computeErrorCallback(step)}
|
||||
@@ -298,10 +302,13 @@ export class HaAuthFlow extends LitElement {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await createLoginFlow(this.clientId, this.redirectUri, [
|
||||
newProvider.type,
|
||||
newProvider.id,
|
||||
]);
|
||||
const response = await createLoginFlow(
|
||||
this.clientId,
|
||||
this.redirectUri,
|
||||
[newProvider.type, newProvider.id],
|
||||
this.codeChallenge,
|
||||
this.codeChallengeMethod
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
|
||||
@@ -33,6 +33,10 @@ export class HaAuthorize extends provideLiteI18nMixin(
|
||||
|
||||
@property({ attribute: false }) public oauth2State?: string;
|
||||
|
||||
@property({ attribute: false }) public codeChallenge?: string;
|
||||
|
||||
@property({ attribute: false }) public codeChallengeMethod?: string;
|
||||
|
||||
@property({ attribute: false }) public translationFragment = "page-authorize";
|
||||
|
||||
@state() private _authProvider?: AuthProvider;
|
||||
@@ -57,6 +61,12 @@ export class HaAuthorize extends provideLiteI18nMixin(
|
||||
if (query.state) {
|
||||
this.oauth2State = query.state;
|
||||
}
|
||||
if (query.code_challenge) {
|
||||
this.codeChallenge = query.code_challenge;
|
||||
}
|
||||
if (query.code_challenge_method) {
|
||||
this.codeChallengeMethod = query.code_challenge_method;
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -185,6 +195,8 @@ export class HaAuthorize extends provideLiteI18nMixin(
|
||||
.clientId=${this.clientId}
|
||||
.redirectUri=${this.redirectUri}
|
||||
.oauth2State=${this.oauth2State}
|
||||
.codeChallenge=${this.codeChallenge}
|
||||
.codeChallengeMethod=${this.codeChallengeMethod}
|
||||
.authProvider=${this._authProvider}
|
||||
.initStoreToken=${this._preselectStoreToken}
|
||||
></ha-auth-flow>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { mdiAppleKeyboardCommand } from "@mdi/js";
|
||||
import { html, type TemplateResult } from "lit";
|
||||
import "../../components/ha-svg-icon";
|
||||
import { isMac } from "../../util/is_mac";
|
||||
import type { LocalizeFunc } from "../translations/localize";
|
||||
|
||||
export const ctrlOrCmdLabel = (localize: LocalizeFunc): string =>
|
||||
isMac ? "⌘" : localize("ui.dialogs.shortcuts.keys.ctrl");
|
||||
|
||||
export const renderCtrlOrCmd = (
|
||||
localize: LocalizeFunc
|
||||
): TemplateResult | string =>
|
||||
isMac
|
||||
? html`<ha-svg-icon .path=${mdiAppleKeyboardCommand}></ha-svg-icon>`
|
||||
: localize("ui.dialogs.shortcuts.keys.ctrl");
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
import { isTouch } from "../../../util/is_touch";
|
||||
import type {
|
||||
MapCircleOptions,
|
||||
MapClusterIcon,
|
||||
MapClusterOptions,
|
||||
MapControlPosition,
|
||||
MapDraggableMarkerOptions,
|
||||
@@ -132,12 +133,18 @@ interface ClusterGroup {
|
||||
/** Members are shown in a bubble at their spot instead of an icon */
|
||||
open?: boolean;
|
||||
/** Grouped by key (a zone), so it bubbles even with a single member */
|
||||
keyed?: boolean;
|
||||
key?: string;
|
||||
members: ManagedMarker[];
|
||||
center: MapLatLng;
|
||||
icon?: MapClusterIcon;
|
||||
iconMarker?: MapLibreMarker;
|
||||
}
|
||||
|
||||
const centerOf = (members: ManagedMarker[]): MapLatLng => [
|
||||
members.reduce((sum, m) => sum + m.location[0], 0) / members.length,
|
||||
members.reduce((sum, m) => sum + m.location[1], 0) / members.length,
|
||||
];
|
||||
|
||||
/**
|
||||
* The MapLibre GL engine: native vector rendering, requires WebGL2. The host
|
||||
* falls back to Leaflet without it or after a fatal context loss.
|
||||
@@ -1236,9 +1243,9 @@ export class MapLibreMapEngine implements MapEngine {
|
||||
if (regroup || !this._clusterGroups.length) {
|
||||
const { radius, groupKey, groupRadius } = this._clusterOptions;
|
||||
const groups: {
|
||||
seed: { x: number; y: number };
|
||||
points: { x: number; y: number }[];
|
||||
members: ManagedMarker[];
|
||||
keyed?: boolean;
|
||||
key?: string;
|
||||
}[] = [];
|
||||
|
||||
// Keyed groups first; one spread too wide falls through to proximity
|
||||
@@ -1253,7 +1260,7 @@ export class MapLibreMapEngine implements MapEngine {
|
||||
(byKey[key] ??= []).push(managed);
|
||||
}
|
||||
}
|
||||
for (const members of Object.values(byKey)) {
|
||||
for (const [key, members] of Object.entries(byKey)) {
|
||||
const points = members.map((m) => this._project(m.location));
|
||||
const xs = points.map((p) => p.x);
|
||||
const ys = points.map((p) => p.y);
|
||||
@@ -1264,7 +1271,7 @@ export class MapLibreMapEngine implements MapEngine {
|
||||
// A keyed group (a zone) bubbles even with a single member, so a lone
|
||||
// person or device in a zone still shows in a bubble pinned to it.
|
||||
if (members.length === 1 || spread <= (groupRadius ?? radius)) {
|
||||
groups.push({ seed: points[0], members, keyed: true });
|
||||
groups.push({ points, members, key });
|
||||
} else {
|
||||
ungrouped.push(...members);
|
||||
}
|
||||
@@ -1275,28 +1282,23 @@ export class MapLibreMapEngine implements MapEngine {
|
||||
|
||||
for (const managed of ungrouped) {
|
||||
const point = this._project(managed.location);
|
||||
const group = groups.find(
|
||||
(candidate) =>
|
||||
Math.hypot(
|
||||
candidate.seed.x - point.x,
|
||||
candidate.seed.y - point.y
|
||||
) <= radius
|
||||
const group = groups.find((candidate) =>
|
||||
candidate.points.some(
|
||||
(member) =>
|
||||
Math.hypot(member.x - point.x, member.y - point.y) <= radius
|
||||
)
|
||||
);
|
||||
if (group) {
|
||||
group.members.push(managed);
|
||||
group.points.push(point);
|
||||
} else {
|
||||
groups.push({ seed: point, members: [managed] });
|
||||
groups.push({ points: [point], members: [managed] });
|
||||
}
|
||||
}
|
||||
this._clusterGroups = groups.map((group) => ({
|
||||
members: group.members,
|
||||
keyed: group.keyed,
|
||||
center: [
|
||||
group.members.reduce((sum, m) => sum + m.location[0], 0) /
|
||||
group.members.length,
|
||||
group.members.reduce((sum, m) => sum + m.location[1], 0) /
|
||||
group.members.length,
|
||||
] as MapLatLng,
|
||||
key: group.key,
|
||||
center: centerOf(group.members),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1304,20 +1306,27 @@ export class MapLibreMapEngine implements MapEngine {
|
||||
group.iconMarker = undefined;
|
||||
// A lone non-keyed marker shows plainly; a lone zone occupant falls
|
||||
// through to the bubble path so it renders in a bubble at its zone.
|
||||
if (group.members.length === 1 && !group.keyed) {
|
||||
this._showMarker(group.members[0]);
|
||||
continue;
|
||||
}
|
||||
if (group.open) {
|
||||
this._openGroup(group);
|
||||
group.icon =
|
||||
(group.members.length > 1 || group.key !== undefined) && !group.open
|
||||
? this._buildIcon(group)
|
||||
: undefined;
|
||||
}
|
||||
if (regroup) {
|
||||
this._mergeOverlappingBubbles();
|
||||
}
|
||||
|
||||
for (const group of this._clusterGroups) {
|
||||
if (!group.icon) {
|
||||
if (group.open) {
|
||||
this._openGroup(group);
|
||||
} else {
|
||||
this._showMarker(group.members[0]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
group.members.forEach((managed) => this._hideMarker(managed));
|
||||
|
||||
const icon = this._clusterOptions.iconBuilder(
|
||||
group.members.map((managed) => managed.handle),
|
||||
group.center
|
||||
);
|
||||
const { icon } = group;
|
||||
icon.element.style.width = `${icon.size[0]}px`;
|
||||
icon.element.style.height = `${icon.size[1]}px`;
|
||||
// Clicking a bubble zooms in on its members; when zooming cannot
|
||||
@@ -1380,4 +1389,60 @@ export class MapLibreMapEngine implements MapEngine {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _buildIcon(group: ClusterGroup): MapClusterIcon {
|
||||
return this._clusterOptions!.iconBuilder(
|
||||
group.members.map((managed) => managed.handle),
|
||||
group.center,
|
||||
group.key
|
||||
);
|
||||
}
|
||||
|
||||
private _iconRect(group: ClusterGroup) {
|
||||
const { size, anchor, location } = group.icon!;
|
||||
const point = this._project(location ?? group.center);
|
||||
const [left, top] = anchor
|
||||
? [point.x - anchor[0], point.y - anchor[1]]
|
||||
: [point.x - size[0] / 2, point.y - size[1] / 2];
|
||||
return { left, top, right: left + size[0], bottom: top + size[1] };
|
||||
}
|
||||
|
||||
private _mergeOverlappingBubbles(): void {
|
||||
for (;;) {
|
||||
const bubbles = this._clusterGroups.filter((group) => group.icon);
|
||||
const rects = bubbles.map((group) => this._iconRect(group));
|
||||
let pair: [ClusterGroup, ClusterGroup] | undefined;
|
||||
for (let i = 0; i < bubbles.length && !pair; i++) {
|
||||
for (let j = i + 1; j < bubbles.length && !pair; j++) {
|
||||
const a = rects[i];
|
||||
const b = rects[j];
|
||||
if (
|
||||
a.left < b.right &&
|
||||
b.left < a.right &&
|
||||
a.top < b.bottom &&
|
||||
b.top < a.bottom
|
||||
) {
|
||||
pair = [bubbles[i], bubbles[j]];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!pair) {
|
||||
return;
|
||||
}
|
||||
const members = [...pair[0].members, ...pair[1].members];
|
||||
// Two zones cannot share a pinned bubble
|
||||
const keys = [pair[0].key, pair[1].key].filter(
|
||||
(key) => key !== undefined
|
||||
);
|
||||
const merged: ClusterGroup = {
|
||||
members,
|
||||
key: keys.length === 1 ? keys[0] : undefined,
|
||||
center: centerOf(members),
|
||||
};
|
||||
merged.icon = this._buildIcon(merged);
|
||||
this._clusterGroups = this._clusterGroups
|
||||
.filter((group) => !pair!.includes(group))
|
||||
.concat(merged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,11 @@ export interface MapClusterOptions {
|
||||
groupKey?(marker: MapMarkerHandle): string | undefined;
|
||||
groupRadius?: number;
|
||||
/** Builds a cluster's element; called when its members change and on refreshClusters() */
|
||||
iconBuilder(members: MapMarkerHandle[], location: MapLatLng): MapClusterIcon;
|
||||
iconBuilder(
|
||||
members: MapMarkerHandle[],
|
||||
location: MapLatLng,
|
||||
key?: string
|
||||
): MapClusterIcon;
|
||||
}
|
||||
|
||||
export interface MapEngine {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Character ranges for Hebrew and Arabic
|
||||
const rtlRange = "\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC";
|
||||
// Character ranges for ASCII and Latin characters, explicit LTR marks, etc.
|
||||
const ltrRange =
|
||||
"A-Za-z\u00C0-\u00D6\u00D8-\u00F6" +
|
||||
"\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF\u200E\u2C00-\uFB1C" +
|
||||
"\uFE00-\uFE6F\uFEFD-\uFFFF";
|
||||
|
||||
/* eslint-disable no-misleading-character-class */
|
||||
const rtl = new RegExp(`^[^${ltrRange}]*[${rtlRange}]`);
|
||||
const ltr = new RegExp(`^[^${rtlRange}]*[${ltrRange}]`);
|
||||
/* eslint-enable no-misleading-character-class */
|
||||
|
||||
/**
|
||||
* Detect the direction of text: left-to-right, right-to-left, or neutral
|
||||
*
|
||||
* @param {string} value
|
||||
* @returns {'rtl'|'ltr'|'neutral'}
|
||||
*/
|
||||
export function estimateDirection(value: string) {
|
||||
const source = String(value || "");
|
||||
return rtl.test(source) ? "rtl" : ltr.test(source) ? "ltr" : "neutral";
|
||||
}
|
||||
@@ -1126,6 +1126,10 @@ export class HaChartBase extends MobileAwareMixin(LitElement) {
|
||||
},
|
||||
line: {
|
||||
lineStyle: { width: 1.5 },
|
||||
// At this size the symbols are invisible, but drawing one per data point
|
||||
// makes every tooltip move repaint hundreds of them. ECharts still draws
|
||||
// the symbol of the hovered point.
|
||||
showSymbol: false,
|
||||
symbolSize: 1,
|
||||
symbol: "circle",
|
||||
smooth: false,
|
||||
|
||||
@@ -32,11 +32,8 @@ import { internationalizationContext } from "../../data/context";
|
||||
import type { FrontendLocaleData } from "../../data/translation";
|
||||
import { haStyleScrollbar } from "../../resources/styles";
|
||||
import { loadVirtualizer } from "../../resources/virtualizer";
|
||||
import "../animation/ha-fade-in";
|
||||
import "../ha-checkbox";
|
||||
import type { HaCheckbox } from "../ha-checkbox";
|
||||
import "../skeleton/ha-skeleton-icon";
|
||||
import "../skeleton/ha-skeleton-text";
|
||||
import "../ha-svg-icon";
|
||||
import "../input/ha-input-search";
|
||||
import { filterData, sortData } from "./sort-filter";
|
||||
@@ -114,25 +111,6 @@ export type SortableColumnContainer = Record<string, ClonedDataTableColumnData>;
|
||||
const UNDEFINED_GROUP_KEY = "zzzzz_undefined";
|
||||
const AUTO_FOCUS_ALLOWED_ACTIVE_TAGS = ["BODY", "HTML", "HOME-ASSISTANT"];
|
||||
|
||||
// Default row height, used to fill the viewport with skeleton rows.
|
||||
const ROW_HEIGHT = 52;
|
||||
|
||||
const cellClasses = (column: DataTableColumnData) => ({
|
||||
"mdc-data-table__cell--flex": column.type === "flex",
|
||||
"mdc-data-table__cell--numeric": column.type === "numeric",
|
||||
"mdc-data-table__cell--icon": column.type === "icon",
|
||||
"mdc-data-table__cell--icon-button": column.type === "icon-button",
|
||||
"mdc-data-table__cell--overflow-menu": column.type === "overflow-menu",
|
||||
"mdc-data-table__cell--overflow": column.type === "overflow",
|
||||
forceLTR: Boolean(column.forceLTR),
|
||||
});
|
||||
|
||||
const cellStyles = (column: DataTableColumnData) => ({
|
||||
minWidth: column.minWidth,
|
||||
maxWidth: column.maxWidth,
|
||||
flex: column.flex || 1,
|
||||
});
|
||||
|
||||
@customElement("ha-data-table")
|
||||
export class HaDataTable extends LitElement {
|
||||
@state()
|
||||
@@ -542,79 +520,20 @@ export class HaDataTable extends LitElement {
|
||||
</div>
|
||||
${
|
||||
!this._filteredData?.length
|
||||
? this.loading ||
|
||||
!this._filteredData ||
|
||||
(this.data.length && !this._filteredDataSourceLength)
|
||||
? html`
|
||||
<div class="mdc-data-table__content" role="row">
|
||||
<ha-fade-in .duration=${300} easing="ease-in">
|
||||
<div role="cell">
|
||||
<div
|
||||
role="progressbar"
|
||||
aria-label=${
|
||||
this._i18n?.localize?.("ui.common.loading") ||
|
||||
? html`
|
||||
<div class="mdc-data-table__content">
|
||||
<div class="mdc-data-table__row" role="row">
|
||||
<div
|
||||
class="mdc-data-table__cell grows center"
|
||||
role="cell"
|
||||
>
|
||||
${
|
||||
this.loading ||
|
||||
!this._filteredData ||
|
||||
(this.data.length && !this._filteredDataSourceLength)
|
||||
? this._i18n?.localize?.("ui.common.loading") ||
|
||||
"Loading"
|
||||
}
|
||||
>
|
||||
${Array.from(
|
||||
{
|
||||
length: this.autoHeight
|
||||
? 1
|
||||
: Math.ceil(window.innerHeight / ROW_HEIGHT),
|
||||
},
|
||||
() => html`
|
||||
<div class="mdc-data-table__row">
|
||||
${
|
||||
this.selectable
|
||||
? html`<div
|
||||
class="mdc-data-table__cell mdc-data-table__cell--checkbox"
|
||||
></div>`
|
||||
: nothing
|
||||
}
|
||||
${Object.entries(columns).map(
|
||||
([key, column]) =>
|
||||
(this.narrow &&
|
||||
!column.main &&
|
||||
!column.showNarrow) ||
|
||||
!this._isColumnVisible(key, column)
|
||||
? nothing
|
||||
: html`
|
||||
<div
|
||||
class="mdc-data-table__cell ${classMap(
|
||||
cellClasses(column)
|
||||
)}"
|
||||
style=${styleMap(cellStyles(column))}
|
||||
>
|
||||
${
|
||||
column.type === "icon"
|
||||
? html`<ha-skeleton-icon></ha-skeleton-icon>`
|
||||
: column.type ===
|
||||
"icon-button" ||
|
||||
column.type ===
|
||||
"overflow-menu"
|
||||
? nothing
|
||||
: html`<ha-skeleton-text></ha-skeleton-text>`
|
||||
}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ha-fade-in>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div class="mdc-data-table__content">
|
||||
<div class="mdc-data-table__row" role="row">
|
||||
<div
|
||||
class="mdc-data-table__cell grows center"
|
||||
role="cell"
|
||||
>
|
||||
${
|
||||
this.data.length
|
||||
: this.data.length
|
||||
? this._i18n?.localize?.(
|
||||
"ui.components.data-table.no_match_filter"
|
||||
) || "No rows matching current filters"
|
||||
@@ -623,11 +542,11 @@ export class HaDataTable extends LitElement {
|
||||
"ui.components.data-table.no-data"
|
||||
) ||
|
||||
"No data"
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<lit-virtualizer
|
||||
scroller
|
||||
@@ -721,8 +640,22 @@ export class HaDataTable extends LitElement {
|
||||
@mouseover=${this._setTitle}
|
||||
@focus=${this._setTitle}
|
||||
role=${column.main ? "rowheader" : "cell"}
|
||||
class="mdc-data-table__cell ${classMap(cellClasses(column))}"
|
||||
style=${styleMap(cellStyles(column))}
|
||||
class="mdc-data-table__cell ${classMap({
|
||||
"mdc-data-table__cell--flex": column.type === "flex",
|
||||
"mdc-data-table__cell--numeric": column.type === "numeric",
|
||||
"mdc-data-table__cell--icon": column.type === "icon",
|
||||
"mdc-data-table__cell--icon-button":
|
||||
column.type === "icon-button",
|
||||
"mdc-data-table__cell--overflow-menu":
|
||||
column.type === "overflow-menu",
|
||||
"mdc-data-table__cell--overflow": column.type === "overflow",
|
||||
forceLTR: Boolean(column.forceLTR),
|
||||
})}"
|
||||
style=${styleMap({
|
||||
minWidth: column.minWidth,
|
||||
maxWidth: column.maxWidth,
|
||||
flex: column.flex || 1,
|
||||
})}
|
||||
>
|
||||
${
|
||||
column.template
|
||||
@@ -1397,8 +1330,7 @@ export class HaDataTable extends LitElement {
|
||||
.mdc-data-table__cell--icon:first-child ha-svg-icon,
|
||||
.mdc-data-table__cell--icon:first-child ha-state-icon,
|
||||
.mdc-data-table__cell--icon:first-child ha-domain-icon,
|
||||
.mdc-data-table__cell--icon:first-child ha-service-icon,
|
||||
.mdc-data-table__cell--icon:first-child ha-skeleton-icon {
|
||||
.mdc-data-table__cell--icon:first-child ha-service-icon {
|
||||
margin-left: 8px;
|
||||
margin-inline-start: 8px;
|
||||
margin-inline-end: initial;
|
||||
|
||||
@@ -188,7 +188,9 @@ export class HaDateRangePicker extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="container">
|
||||
<div class="date-range-inputs">${this._renderField()}</div>
|
||||
<div class="date-range-inputs" part="range-input">
|
||||
${this._renderField()}
|
||||
</div>
|
||||
${
|
||||
this._pickerWrapperOpen || this._opened
|
||||
? this._openedNarrow
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { estimateDirection } from "../common/util/estimate-direction";
|
||||
import { consumeLocalize } from "../common/decorators/consume-context-entry";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
@@ -48,6 +49,7 @@ class HaAlert extends LitElement {
|
||||
<div
|
||||
class="issue-type ${classMap({
|
||||
[this.alertType]: true,
|
||||
ltr: estimateDirection(this.title) !== "rtl",
|
||||
})}"
|
||||
role="alert"
|
||||
>
|
||||
@@ -175,6 +177,9 @@ class HaAlert extends LitElement {
|
||||
margin: 0;
|
||||
padding-inline-start: 20px;
|
||||
}
|
||||
.ltr {
|
||||
direction: ltr;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -844,7 +844,7 @@ ${JSON.stringify(toolCall.result, null, 2)}</pre>
|
||||
progress.continueConversation =
|
||||
event.data.intent_output.continue_conversation;
|
||||
const response =
|
||||
event.data.intent_output.response.speech?.plain.speech;
|
||||
event.data.intent_output.response.speech.plain?.speech;
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
mdiDragHorizontalVariant,
|
||||
mdiPencil,
|
||||
} from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import { css, html, LitElement, nothing, type PropertyValues } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -217,6 +218,9 @@ export class HaObjectSelector extends LitElement {
|
||||
name: key,
|
||||
selector: field.selector,
|
||||
required: field.required ?? false,
|
||||
...("default" in field
|
||||
? { default: field.default as HaFormSchema["default"] }
|
||||
: {}),
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -237,13 +241,21 @@ export class HaObjectSelector extends LitElement {
|
||||
ev.stopPropagation();
|
||||
|
||||
const schema = this._schema(this.selector);
|
||||
const data = {
|
||||
...computeInitialHaFormData(schema, {
|
||||
skipUnsupportedSelectors: true,
|
||||
}),
|
||||
...Object.fromEntries(
|
||||
schema
|
||||
.filter((field) => "default" in field)
|
||||
.map((field) => [field.name, deepClone(field.default)])
|
||||
),
|
||||
};
|
||||
|
||||
const newItem = await showFormDialog(this, {
|
||||
title: this.hass.localize("ui.common.add"),
|
||||
schema,
|
||||
data: computeInitialHaFormData(schema, {
|
||||
skipUnsupportedSelectors: true,
|
||||
}),
|
||||
data,
|
||||
computeLabel: this._computeLabel,
|
||||
computeHelper: this._computeHelper,
|
||||
submitText: this.hass.localize("ui.common.add"),
|
||||
|
||||
@@ -59,22 +59,20 @@ export class HaSTTPicker extends LitElement {
|
||||
value = NONE;
|
||||
}
|
||||
|
||||
const options: HaSelectOption[] = this._engines
|
||||
.filter((engine) => !engine.deprecated || engine.engine_id !== value)
|
||||
.map((engine) => {
|
||||
let label: string;
|
||||
if (engine.engine_id.includes(".")) {
|
||||
const stateObj = this.hass.states[engine.engine_id];
|
||||
label = stateObj ? computeStateName(stateObj) : engine.engine_id;
|
||||
} else {
|
||||
label = engine.name || engine.engine_id;
|
||||
}
|
||||
return {
|
||||
value: engine.engine_id,
|
||||
label,
|
||||
disabled: engine.supported_languages?.length === 0,
|
||||
};
|
||||
});
|
||||
const options: HaSelectOption[] = this._engines.map((engine) => {
|
||||
let label: string;
|
||||
if (engine.engine_id.includes(".")) {
|
||||
const stateObj = this.hass.states[engine.engine_id];
|
||||
label = stateObj ? computeStateName(stateObj) : engine.engine_id;
|
||||
} else {
|
||||
label = engine.name || engine.engine_id;
|
||||
}
|
||||
return {
|
||||
value: engine.engine_id,
|
||||
label,
|
||||
disabled: engine.supported_languages?.length === 0,
|
||||
};
|
||||
});
|
||||
|
||||
if (this.required || value === NONE) {
|
||||
options.unshift({
|
||||
|
||||
@@ -31,7 +31,10 @@ import type {
|
||||
MapEditableMarkerHandle,
|
||||
MapEditingSupport,
|
||||
} from "../../common/map/map-engine";
|
||||
import { circleBoundsPoints } from "../../common/map/map-engine";
|
||||
import {
|
||||
circleBoundsPoints,
|
||||
distanceMeters,
|
||||
} from "../../common/map/map-engine";
|
||||
import { editableCircleStyles } from "../../common/map/editable-circle";
|
||||
import { entityMapColor, zoneColor } from "../../common/map/entity-map-colors";
|
||||
import {
|
||||
@@ -311,6 +314,8 @@ export class HaMap extends ReactiveElement {
|
||||
|
||||
private _zonePositions: Record<string, MapLatLng> = {};
|
||||
|
||||
private _zoneRadii: Record<string, number> = {};
|
||||
|
||||
@property({ attribute: "theme-mode", type: String })
|
||||
public themeMode: ThemeMode = "auto";
|
||||
|
||||
@@ -1133,6 +1138,7 @@ export class HaMap extends ReactiveElement {
|
||||
// A person's state is "home" for the home zone, the zone name otherwise
|
||||
const zoneByState: Record<string, string> = {};
|
||||
this._zonePositions = {};
|
||||
this._zoneRadii = {};
|
||||
for (const entity of this.entities) {
|
||||
const stateObj = states[getEntityId(entity)];
|
||||
// A zone that is not drawn cannot anchor a bubble either
|
||||
@@ -1154,6 +1160,9 @@ export class HaMap extends ReactiveElement {
|
||||
stateObj.attributes.latitude,
|
||||
stateObj.attributes.longitude,
|
||||
];
|
||||
if (typeof stateObj.attributes.radius === "number") {
|
||||
this._zoneRadii[stateObj.entity_id] = stateObj.attributes.radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1301,7 +1310,7 @@ export class HaMap extends ReactiveElement {
|
||||
zoneId: ["person", "device_tracker"].includes(
|
||||
computeStateDomain(stateObj)
|
||||
)
|
||||
? zoneByState[stateObj.state]
|
||||
? (zoneByState[stateObj.state] ?? this._zoneContaining(position))
|
||||
: undefined,
|
||||
};
|
||||
|
||||
@@ -1339,9 +1348,26 @@ export class HaMap extends ReactiveElement {
|
||||
);
|
||||
}
|
||||
|
||||
private _zoneContaining(position: MapLatLng): string | undefined {
|
||||
let found: string | undefined;
|
||||
let foundRadius = Infinity;
|
||||
for (const [zoneId, radius] of Object.entries(this._zoneRadii)) {
|
||||
if (
|
||||
radius < foundRadius &&
|
||||
distanceMeters(position, this._zonePositions[zoneId]) <= radius
|
||||
) {
|
||||
found = zoneId;
|
||||
foundRadius = radius;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
// Renders a marker cluster as a bubble of its members' avatars
|
||||
private _createClusterBubble = (
|
||||
members: MapMarkerHandle[]
|
||||
members: MapMarkerHandle[],
|
||||
_location: MapLatLng,
|
||||
zoneId?: string
|
||||
): MapClusterIcon => {
|
||||
const data = members.map((member) => member.clusterData as ClusterData);
|
||||
const shown = data.slice(0, CLUSTER_MAX_AVATARS);
|
||||
@@ -1387,12 +1413,8 @@ export class HaMap extends ReactiveElement {
|
||||
}
|
||||
|
||||
// A cluster of one zone's occupants attaches to that zone's marker
|
||||
const zoneId = data[0]?.zoneId;
|
||||
const zonePosition = zoneId ? this._zonePositions[zoneId] : undefined;
|
||||
const atZone =
|
||||
!!zoneId &&
|
||||
!!zonePosition &&
|
||||
data.every((member) => member?.zoneId === zoneId);
|
||||
const atZone = !!zonePosition;
|
||||
|
||||
let height = CLUSTER_AVATAR_SIZE + 2 * CLUSTER_BUBBLE_PADDING;
|
||||
let root: HTMLElement = bubble;
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from "@mdi/js";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import type { PropertyValues } from "lit";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { consumeLocalize } from "../../common/decorators/consume-context-entry";
|
||||
@@ -74,6 +75,9 @@ export class HatScriptGraph extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public selected?: string;
|
||||
|
||||
/** Accessible name per node path, from `buildTraceLabels`. */
|
||||
@property({ attribute: false }) public labels?: Record<string, string>;
|
||||
|
||||
@query("hat-graph-node[active], hat-graph-branch[active]")
|
||||
private _activeNode?: HTMLElement;
|
||||
|
||||
@@ -106,6 +110,9 @@ export class HatScriptGraph extends LitElement {
|
||||
?active=${this.selected === path}
|
||||
.notEnabled=${node.disabled}
|
||||
.error=${node.error}
|
||||
role="img"
|
||||
aria-label=${ifDefined(this.labels?.[path])}
|
||||
aria-current=${ifDefined(this.selected === path || undefined)}
|
||||
tabindex=${hasTrace ? "0" : "-1"}
|
||||
>
|
||||
<ha-trigger-icon
|
||||
@@ -153,6 +160,10 @@ export class HatScriptGraph extends LitElement {
|
||||
?track=${track}
|
||||
?active=${this.selected === path}
|
||||
.notEnabled=${node.disabled}
|
||||
role="group"
|
||||
aria-label=${ifDefined(this.labels?.[path])}
|
||||
aria-current=${ifDefined(this.selected === path || undefined)}
|
||||
aria-disabled=${ifDefined(node.disabled || undefined)}
|
||||
>
|
||||
<hat-graph-node
|
||||
.graphStart=${graphStart}
|
||||
@@ -164,6 +175,7 @@ export class HatScriptGraph extends LitElement {
|
||||
.error=${node.error}
|
||||
slot="head"
|
||||
nofocus
|
||||
aria-hidden="true"
|
||||
></hat-graph-node>
|
||||
|
||||
${node.branches.slice(0, -1).map(
|
||||
@@ -187,6 +199,9 @@ export class HatScriptGraph extends LitElement {
|
||||
?track=${branch.hasTrace}
|
||||
?active=${this.selected === branch.path}
|
||||
.notEnabled=${branch.disabled}
|
||||
role="img"
|
||||
aria-label=${ifDefined(this.labels?.[branch.path])}
|
||||
aria-current=${ifDefined(this.selected === branch.path || undefined)}
|
||||
></hat-graph-node>
|
||||
${branch.children.map((action) => this._renderActionNode(action))}
|
||||
</div>
|
||||
@@ -196,7 +211,10 @@ export class HatScriptGraph extends LitElement {
|
||||
?track=${defaultBranch.hasTrace}
|
||||
?unfinished=${defaultBranch.unfinished}
|
||||
>
|
||||
<hat-graph-spacer ?track=${defaultBranch.hasTrace}></hat-graph-spacer>
|
||||
<hat-graph-spacer
|
||||
aria-hidden="true"
|
||||
?track=${defaultBranch.hasTrace}
|
||||
></hat-graph-spacer>
|
||||
${defaultBranch.children.map((action) =>
|
||||
this._renderActionNode(action)
|
||||
)}
|
||||
@@ -215,6 +233,10 @@ export class HatScriptGraph extends LitElement {
|
||||
?track=${track}
|
||||
?active=${this.selected === path}
|
||||
.notEnabled=${node.disabled}
|
||||
role="group"
|
||||
aria-label=${ifDefined(this.labels?.[path])}
|
||||
aria-current=${ifDefined(this.selected === path || undefined)}
|
||||
aria-disabled=${ifDefined(node.disabled || undefined)}
|
||||
>
|
||||
<hat-graph-node
|
||||
.graphStart=${graphStart}
|
||||
@@ -226,6 +248,7 @@ export class HatScriptGraph extends LitElement {
|
||||
.error=${node.error}
|
||||
slot="head"
|
||||
nofocus
|
||||
aria-hidden="true"
|
||||
></hat-graph-node>
|
||||
${
|
||||
config.else
|
||||
@@ -240,12 +263,14 @@ export class HatScriptGraph extends LitElement {
|
||||
?active=${this.selected === path}
|
||||
.notEnabled=${elseBranch.disabled}
|
||||
nofocus
|
||||
aria-hidden="true"
|
||||
></hat-graph-node
|
||||
>${elseBranch.children.map((action) =>
|
||||
this._renderActionNode(action)
|
||||
)}
|
||||
</div>`
|
||||
: html`<hat-graph-spacer
|
||||
aria-hidden="true"
|
||||
?track=${elseBranch.hasTrace}
|
||||
></hat-graph-spacer>`
|
||||
}
|
||||
@@ -260,6 +285,7 @@ export class HatScriptGraph extends LitElement {
|
||||
?active=${this.selected === path}
|
||||
.notEnabled=${thenBranch.disabled}
|
||||
nofocus
|
||||
aria-hidden="true"
|
||||
></hat-graph-node>
|
||||
${thenBranch.children.map((action) => this._renderActionNode(action))}
|
||||
</div>
|
||||
@@ -281,6 +307,10 @@ export class HatScriptGraph extends LitElement {
|
||||
?track=${track}
|
||||
?active=${this.selected === path}
|
||||
.notEnabled=${model.disabled}
|
||||
role="group"
|
||||
aria-label=${ifDefined(this.labels?.[path])}
|
||||
aria-current=${ifDefined(this.selected === path || undefined)}
|
||||
aria-disabled=${ifDefined(model.disabled || undefined)}
|
||||
tabindex=${hasTrace ? "0" : "-1"}
|
||||
short
|
||||
>
|
||||
@@ -293,6 +323,7 @@ export class HatScriptGraph extends LitElement {
|
||||
.error=${model.error}
|
||||
?building-block=${CONDITION_BUILDING_BLOCKS.includes(condition)}
|
||||
nofocus
|
||||
aria-hidden="true"
|
||||
>
|
||||
<ha-condition-icon
|
||||
slot="icon"
|
||||
@@ -308,6 +339,7 @@ export class HatScriptGraph extends LitElement {
|
||||
<hat-graph-node
|
||||
.iconPath=${mdiClose}
|
||||
nofocus
|
||||
aria-hidden="true"
|
||||
?track=${failed}
|
||||
?active=${this.selected === path}
|
||||
.notEnabled=${model.disabled}
|
||||
@@ -329,6 +361,10 @@ export class HatScriptGraph extends LitElement {
|
||||
?track=${track}
|
||||
?active=${this.selected === path}
|
||||
.notEnabled=${model.disabled}
|
||||
role="group"
|
||||
aria-label=${ifDefined(this.labels?.[path])}
|
||||
aria-current=${ifDefined(this.selected === path || undefined)}
|
||||
aria-disabled=${ifDefined(model.disabled || undefined)}
|
||||
>
|
||||
<hat-graph-node
|
||||
.graphStart=${graphStart}
|
||||
@@ -341,6 +377,7 @@ export class HatScriptGraph extends LitElement {
|
||||
.badge=${model.badge}
|
||||
slot="head"
|
||||
nofocus
|
||||
aria-hidden="true"
|
||||
></hat-graph-node>
|
||||
<div
|
||||
class="repeat-sequence"
|
||||
@@ -372,6 +409,9 @@ export class HatScriptGraph extends LitElement {
|
||||
?active=${this.selected === path}
|
||||
.notEnabled=${model.disabled}
|
||||
.error=${model.error}
|
||||
role="img"
|
||||
aria-label=${ifDefined(this.labels?.[path])}
|
||||
aria-current=${ifDefined(this.selected === path || undefined)}
|
||||
tabindex=${model.hasTrace ? "0" : "-1"}
|
||||
>
|
||||
${
|
||||
@@ -405,6 +445,9 @@ export class HatScriptGraph extends LitElement {
|
||||
?active=${this.selected === path}
|
||||
.notEnabled=${model.disabled}
|
||||
.error=${model.error}
|
||||
role="img"
|
||||
aria-label=${ifDefined(this.labels?.[path])}
|
||||
aria-current=${ifDefined(this.selected === path || undefined)}
|
||||
tabindex=${model.hasTrace ? "0" : "-1"}
|
||||
></hat-graph-node>
|
||||
`;
|
||||
@@ -423,6 +466,10 @@ export class HatScriptGraph extends LitElement {
|
||||
?track=${track}
|
||||
?active=${this.selected === path}
|
||||
.notEnabled=${model.disabled}
|
||||
role="group"
|
||||
aria-label=${ifDefined(this.labels?.[path])}
|
||||
aria-current=${ifDefined(this.selected === path || undefined)}
|
||||
aria-disabled=${ifDefined(model.disabled || undefined)}
|
||||
>
|
||||
<div
|
||||
class="graph-container"
|
||||
@@ -439,6 +486,7 @@ export class HatScriptGraph extends LitElement {
|
||||
.error=${model.error}
|
||||
slot="head"
|
||||
nofocus
|
||||
aria-hidden="true"
|
||||
></hat-graph-node>
|
||||
${branch.children.map((action) => this._renderActionNode(action))}
|
||||
</div>
|
||||
@@ -458,6 +506,10 @@ export class HatScriptGraph extends LitElement {
|
||||
?track=${track}
|
||||
?active=${this.selected === path}
|
||||
.notEnabled=${model.disabled}
|
||||
role="group"
|
||||
aria-label=${ifDefined(this.labels?.[path])}
|
||||
aria-current=${ifDefined(this.selected === path || undefined)}
|
||||
aria-disabled=${ifDefined(model.disabled || undefined)}
|
||||
>
|
||||
<hat-graph-node
|
||||
.graphStart=${graphStart}
|
||||
@@ -469,6 +521,7 @@ export class HatScriptGraph extends LitElement {
|
||||
.error=${model.error}
|
||||
slot="head"
|
||||
nofocus
|
||||
aria-hidden="true"
|
||||
></hat-graph-node>
|
||||
${model.branches.map(
|
||||
(branch) =>
|
||||
@@ -496,6 +549,9 @@ export class HatScriptGraph extends LitElement {
|
||||
?active=${this.selected === path}
|
||||
.error=${model.error}
|
||||
.notEnabled=${model.disabled}
|
||||
role="img"
|
||||
aria-label=${ifDefined(this.labels?.[path])}
|
||||
aria-current=${ifDefined(this.selected === path || undefined)}
|
||||
></hat-graph-node>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -119,7 +119,11 @@ class HaUserPicker extends LitElement {
|
||||
|
||||
private _getItems = () => this._getUsers(this.users);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
protected render(): TemplateResult | typeof nothing {
|
||||
if (!this.users) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const placeholder =
|
||||
this.placeholder ?? this.hass.localize("ui.components.user-picker.user");
|
||||
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ import type { HomeAssistant } from "../types";
|
||||
|
||||
export interface AlexaEntity {
|
||||
entity_id: string;
|
||||
display_categories: string[];
|
||||
display_categories: string[] | null;
|
||||
interfaces: string[];
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ export const fetchCloudAlexaEntities = (hass: HomeAssistant) =>
|
||||
hass.callWS<AlexaEntity[]>({ type: "cloud/alexa/entities" });
|
||||
|
||||
export const fetchCloudAlexaEntity = (hass: HomeAssistant, entity_id: string) =>
|
||||
hass.callWS<AlexaEntity>({
|
||||
hass.callWS<undefined>({
|
||||
type: "cloud/alexa/entities/get",
|
||||
entity_id,
|
||||
});
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export interface ApplicationCredentialsDomainConfig {
|
||||
description_placeholders: Record<string, string>;
|
||||
description_placeholders?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface ApplicationCredentialsConfig {
|
||||
domains: string[];
|
||||
integrations: Record<string, ApplicationCredentialsDomainConfig>;
|
||||
}
|
||||
|
||||
@@ -17,7 +18,8 @@ export interface ApplicationCredential {
|
||||
domain: string;
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
name: string;
|
||||
name?: string;
|
||||
auth_domain?: string;
|
||||
}
|
||||
|
||||
export const fetchApplicationCredentialsConfig = async (hass: HomeAssistant) =>
|
||||
|
||||
+43
-10
@@ -9,7 +9,7 @@ export interface AssistPipeline {
|
||||
language: string;
|
||||
conversation_engine: string;
|
||||
conversation_language: string | null;
|
||||
prefer_local_intents?: boolean;
|
||||
prefer_local_intents: boolean;
|
||||
stt_engine: string | null;
|
||||
stt_language: string | null;
|
||||
tts_engine: string | null;
|
||||
@@ -17,11 +17,12 @@ export interface AssistPipeline {
|
||||
tts_voice: string | null;
|
||||
wake_word_entity: string | null;
|
||||
wake_word_id: string | null;
|
||||
user_id?: string | null;
|
||||
}
|
||||
|
||||
export interface AssistDevice {
|
||||
device_id: string;
|
||||
pipeline_entity: string;
|
||||
pipeline_entity: string | null;
|
||||
}
|
||||
|
||||
export interface AssistPipelineMutableParams {
|
||||
@@ -37,6 +38,7 @@ export interface AssistPipelineMutableParams {
|
||||
tts_voice: string | null;
|
||||
wake_word_entity: string | null;
|
||||
wake_word_id: string | null;
|
||||
user_id?: string | null;
|
||||
}
|
||||
|
||||
export interface AssistRunListing {
|
||||
@@ -54,6 +56,7 @@ interface PipelineRunStartEvent extends PipelineEventBase {
|
||||
pipeline: string;
|
||||
language: string;
|
||||
conversation_id: string;
|
||||
satellite_id?: string;
|
||||
runner_data: {
|
||||
stt_binary_handler_id: number | null;
|
||||
timeout: number;
|
||||
@@ -62,12 +65,13 @@ interface PipelineRunStartEvent extends PipelineEventBase {
|
||||
token: string;
|
||||
url: string;
|
||||
mime_type: string;
|
||||
stream_response: boolean;
|
||||
};
|
||||
};
|
||||
}
|
||||
interface PipelineRunEndEvent extends PipelineEventBase {
|
||||
type: "run-end";
|
||||
data: Record<string, never>;
|
||||
data: null;
|
||||
}
|
||||
|
||||
interface PipelineErrorEvent extends PipelineEventBase {
|
||||
@@ -81,14 +85,21 @@ interface PipelineErrorEvent extends PipelineEventBase {
|
||||
interface PipelineWakeWordStartEvent extends PipelineEventBase {
|
||||
type: "wake_word-start";
|
||||
data: {
|
||||
engine: string;
|
||||
metadata: SpeechMetadata;
|
||||
entity_id: string;
|
||||
metadata: Omit<SpeechMetadata, "language">;
|
||||
timeout: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface PipelineWakeWordEndEvent extends PipelineEventBase {
|
||||
type: "wake_word-end";
|
||||
data: { wake_word_output: { ww_id: string; timestamp: number } };
|
||||
data: {
|
||||
wake_word_output: {
|
||||
wake_word_id?: string;
|
||||
wake_word_phrase?: string;
|
||||
timestamp?: number | null;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface PipelineSTTStartEvent extends PipelineEventBase {
|
||||
@@ -96,8 +107,23 @@ interface PipelineSTTStartEvent extends PipelineEventBase {
|
||||
data: {
|
||||
engine: string;
|
||||
metadata: SpeechMetadata;
|
||||
audio_processing: {
|
||||
requires_external_vad: boolean;
|
||||
prefers_auto_gain_enabled: boolean;
|
||||
prefers_noise_reduction_enabled: boolean;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface PipelineSTTVADStartEvent extends PipelineEventBase {
|
||||
type: "stt-vad-start";
|
||||
data: { timestamp: number };
|
||||
}
|
||||
|
||||
interface PipelineSTTVADEndEvent extends PipelineEventBase {
|
||||
type: "stt-vad-end";
|
||||
data: { timestamp: number };
|
||||
}
|
||||
interface PipelineSTTEndEvent extends PipelineEventBase {
|
||||
type: "stt-end";
|
||||
data: {
|
||||
@@ -110,8 +136,11 @@ interface PipelineIntentStartEvent extends PipelineEventBase {
|
||||
data: {
|
||||
engine: string;
|
||||
language: string;
|
||||
prefer_local_intents: boolean;
|
||||
intent_input: string;
|
||||
conversation_id: string;
|
||||
device_id: string | null;
|
||||
satellite_id: string | null;
|
||||
prefer_local_intents: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -123,6 +152,7 @@ export interface ConversationChatLogAssistantDelta {
|
||||
id: string;
|
||||
tool_name: string;
|
||||
tool_args: Record<string, unknown>;
|
||||
external: boolean;
|
||||
}[];
|
||||
}
|
||||
|
||||
@@ -157,9 +187,10 @@ interface PipelineTTSStartEvent extends PipelineEventBase {
|
||||
type: "tts-start";
|
||||
data: {
|
||||
engine: string;
|
||||
language: string;
|
||||
voice: string;
|
||||
language: string | null;
|
||||
voice: string | null;
|
||||
tts_input: string;
|
||||
acknowledge_override: boolean;
|
||||
};
|
||||
}
|
||||
interface PipelineTTSEndEvent extends PipelineEventBase {
|
||||
@@ -181,6 +212,8 @@ export type PipelineRunEvent =
|
||||
| PipelineWakeWordStartEvent
|
||||
| PipelineWakeWordEndEvent
|
||||
| PipelineSTTStartEvent
|
||||
| PipelineSTTVADStartEvent
|
||||
| PipelineSTTVADEndEvent
|
||||
| PipelineSTTEndEvent
|
||||
| PipelineIntentStartEvent
|
||||
| PipelineIntentProgressEvent
|
||||
@@ -426,7 +459,7 @@ export const deleteAssistPipeline = (hass: HomeAssistant, pipelineId: string) =>
|
||||
});
|
||||
|
||||
export const fetchAssistPipelineLanguages = (hass: HomeAssistant) =>
|
||||
hass.callWS<{ languages: string[] }>({
|
||||
hass.callWS<{ languages: string[] | null }>({
|
||||
type: "assist_pipeline/language/list",
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export enum AssistSatelliteEntityFeature {
|
||||
}
|
||||
|
||||
export interface WakeWordInterceptMessage {
|
||||
wake_word_phrase: string;
|
||||
wake_word_phrase: string | null;
|
||||
}
|
||||
|
||||
export interface WakeWordOption {
|
||||
@@ -21,8 +21,8 @@ export interface AssistSatelliteConfiguration {
|
||||
active_wake_words: string[];
|
||||
available_wake_words: WakeWordOption[];
|
||||
max_active_wake_words: number;
|
||||
pipeline_entity_id: string;
|
||||
vad_entity_id: string;
|
||||
pipeline_entity_id: string | null;
|
||||
vad_entity_id: string | null;
|
||||
}
|
||||
|
||||
export const interceptWakeWord = (
|
||||
|
||||
+7
-2
@@ -6,13 +6,14 @@ export interface AuthUrlSearchParams {
|
||||
client_id?: string;
|
||||
redirect_uri?: string;
|
||||
state?: string;
|
||||
code_challenge?: string;
|
||||
code_challenge_method?: string;
|
||||
}
|
||||
|
||||
export interface AuthProvider {
|
||||
name: string;
|
||||
id: string;
|
||||
type: string;
|
||||
users?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface Credential {
|
||||
@@ -53,7 +54,9 @@ export const fetchAuthProviders = () =>
|
||||
export const createLoginFlow = (
|
||||
client_id: string | undefined,
|
||||
redirect_uri: string | undefined,
|
||||
handler: (string | null)[]
|
||||
handler: (string | null)[],
|
||||
code_challenge?: string,
|
||||
code_challenge_method?: string
|
||||
) =>
|
||||
fetch("/auth/login_flow", {
|
||||
method: "POST",
|
||||
@@ -62,6 +65,8 @@ export const createLoginFlow = (
|
||||
client_id,
|
||||
handler,
|
||||
redirect_uri,
|
||||
...(code_challenge && { code_challenge }),
|
||||
...(code_challenge_method && { code_challenge_method }),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
+20
-15
@@ -47,7 +47,7 @@ export interface BackupConfig {
|
||||
last_attempted_automatic_backup: string | null;
|
||||
last_completed_automatic_backup: string | null;
|
||||
next_automatic_backup: string | null;
|
||||
next_automatic_backup_additional?: boolean;
|
||||
next_automatic_backup_additional: boolean;
|
||||
create_backup: {
|
||||
agent_ids: string[];
|
||||
include_addons: string[] | null;
|
||||
@@ -60,7 +60,7 @@ export interface BackupConfig {
|
||||
retention: Retention;
|
||||
schedule: {
|
||||
recurrence: BackupScheduleRecurrence;
|
||||
time?: string | null;
|
||||
time: string | null;
|
||||
days: BackupDay[];
|
||||
};
|
||||
agents: BackupAgentsConfig;
|
||||
@@ -79,9 +79,9 @@ export interface BackupMutableConfig {
|
||||
};
|
||||
retention?: Retention;
|
||||
schedule?: {
|
||||
recurrence: BackupScheduleRecurrence;
|
||||
recurrence?: BackupScheduleRecurrence;
|
||||
time?: string | null;
|
||||
days?: BackupDay[] | null;
|
||||
days?: BackupDay[];
|
||||
};
|
||||
agents?: BackupAgentsConfig;
|
||||
}
|
||||
@@ -114,21 +114,22 @@ export interface BackupContent {
|
||||
date: string;
|
||||
name: string;
|
||||
agents: Record<string, BackupContentAgent>;
|
||||
failed_agent_ids?: string[];
|
||||
failed_addons?: AddonInfo[];
|
||||
failed_folders?: string[];
|
||||
extra_metadata?: {
|
||||
failed_agent_ids: string[];
|
||||
failed_addons: AddonInfo[];
|
||||
failed_folders: string[];
|
||||
extra_metadata: {
|
||||
"supervisor.addon_update"?: string;
|
||||
"supervisor.app_update"?: string;
|
||||
[key: string]: string | boolean | undefined;
|
||||
};
|
||||
with_automatic_settings: boolean;
|
||||
with_automatic_settings: boolean | null;
|
||||
}
|
||||
|
||||
export interface BackupData {
|
||||
addons: BackupAddon[];
|
||||
database_included: boolean;
|
||||
folders: string[];
|
||||
homeassistant_version: string;
|
||||
homeassistant_version: string | null;
|
||||
homeassistant_included: boolean;
|
||||
}
|
||||
|
||||
@@ -141,7 +142,7 @@ export interface BackupAddon {
|
||||
export interface BackupContentExtended extends BackupContent, BackupData {}
|
||||
|
||||
export interface BackupInfo {
|
||||
backups: BackupContent[];
|
||||
backups: BackupContentExtended[];
|
||||
agent_errors: Record<string, string>;
|
||||
last_attempted_automatic_backup: string | null;
|
||||
last_completed_automatic_backup: string | null;
|
||||
@@ -152,6 +153,7 @@ export interface BackupInfo {
|
||||
}
|
||||
|
||||
export interface BackupDetails {
|
||||
agent_errors: Record<string, string>;
|
||||
backup: BackupContentExtended;
|
||||
}
|
||||
|
||||
@@ -232,7 +234,10 @@ export const fetchBackupAgentsInfo = (
|
||||
type: "backup/agents/info",
|
||||
});
|
||||
|
||||
export const deleteBackup = (hass: HomeAssistant, id: string): Promise<void> =>
|
||||
export const deleteBackup = (
|
||||
hass: HomeAssistant,
|
||||
id: string
|
||||
): Promise<{ agent_errors: Record<string, string> }> =>
|
||||
hass.callWS({
|
||||
type: "backup/delete",
|
||||
backup_id: id,
|
||||
@@ -241,7 +246,7 @@ export const deleteBackup = (hass: HomeAssistant, id: string): Promise<void> =>
|
||||
export const generateBackup = (
|
||||
hass: HomeAssistant,
|
||||
params: GenerateBackupParams
|
||||
): Promise<{ backup_id: string }> =>
|
||||
): Promise<{ backup_job_id: string }> =>
|
||||
hass.callWS({
|
||||
type: "backup/generate",
|
||||
...params,
|
||||
@@ -249,7 +254,7 @@ export const generateBackup = (
|
||||
|
||||
export const generateBackupWithAutomaticSettings = (
|
||||
hass: HomeAssistant
|
||||
): Promise<void> =>
|
||||
): Promise<{ backup_job_id: string }> =>
|
||||
hass.callWS({
|
||||
type: "backup/generate_with_automatic_settings",
|
||||
});
|
||||
@@ -257,7 +262,7 @@ export const generateBackupWithAutomaticSettings = (
|
||||
export const restoreBackup = (
|
||||
hass: HomeAssistant,
|
||||
params: RestoreBackupParams
|
||||
): Promise<{ backup_id: string }> =>
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "backup/restore",
|
||||
...params,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export type BackupManagerState =
|
||||
"idle" | "create_backup" | "receive_backup" | "restore_backup";
|
||||
"idle" | "create_backup" | "receive_backup" | "restore_backup" | "blocked";
|
||||
|
||||
export type CreateBackupStage =
|
||||
| "addon_repositories"
|
||||
@@ -39,7 +39,8 @@ export type RestoreBackupStage =
|
||||
| "remove_delta_addons"
|
||||
| "remove_delta_apps";
|
||||
|
||||
export type RestoreBackupState = "completed" | "failed" | "in_progress";
|
||||
export type RestoreBackupState =
|
||||
"completed" | "core_restart" | "failed" | "in_progress";
|
||||
|
||||
interface IdleEvent {
|
||||
manager_state: "idle";
|
||||
@@ -47,22 +48,29 @@ interface IdleEvent {
|
||||
|
||||
interface CreateBackupEvent {
|
||||
manager_state: "create_backup";
|
||||
reason: string | null;
|
||||
stage: CreateBackupStage | null;
|
||||
state: CreateBackupState;
|
||||
}
|
||||
|
||||
interface ReceiveBackupEvent {
|
||||
manager_state: "receive_backup";
|
||||
reason: string | null;
|
||||
stage: ReceiveBackupStage | null;
|
||||
state: ReceiveBackupState;
|
||||
}
|
||||
|
||||
interface RestoreBackupEvent {
|
||||
manager_state: "restore_backup";
|
||||
reason: string | null;
|
||||
stage: RestoreBackupStage | null;
|
||||
state: RestoreBackupState;
|
||||
}
|
||||
|
||||
interface BlockedEvent {
|
||||
manager_state: "blocked";
|
||||
}
|
||||
|
||||
export interface UploadBackupEvent {
|
||||
manager_state: BackupManagerState;
|
||||
agent_id: string;
|
||||
@@ -71,10 +79,14 @@ export interface UploadBackupEvent {
|
||||
}
|
||||
|
||||
export type ManagerState =
|
||||
"idle" | "create_backup" | "receive_backup" | "restore_backup";
|
||||
"idle" | "create_backup" | "receive_backup" | "restore_backup" | "blocked";
|
||||
|
||||
export type ManagerStateEvent =
|
||||
IdleEvent | CreateBackupEvent | ReceiveBackupEvent | RestoreBackupEvent;
|
||||
| IdleEvent
|
||||
| CreateBackupEvent
|
||||
| ReceiveBackupEvent
|
||||
| RestoreBackupEvent
|
||||
| BlockedEvent;
|
||||
|
||||
export type BackupSubscriptionEvent = ManagerStateEvent | UploadBackupEvent;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
|
||||
export interface BackupOnboardingInfo {
|
||||
state: BackupManagerState;
|
||||
last_action_event?: {
|
||||
last_action_event: {
|
||||
manager_state: BackupManagerState;
|
||||
stage: RestoreBackupStage | null;
|
||||
state: RestoreBackupState;
|
||||
|
||||
@@ -9,14 +9,14 @@ import type { DataTableRowData } from "../components/data-table/ha-data-table";
|
||||
export interface BluetoothDeviceData extends DataTableRowData {
|
||||
address: string;
|
||||
connectable: boolean;
|
||||
manufacturer_data: Record<number, string>;
|
||||
manufacturer_data: Record<string, string>;
|
||||
name: string;
|
||||
rssi: number;
|
||||
service_data: Record<string, string>;
|
||||
service_uuids: string[];
|
||||
source: string;
|
||||
time: number;
|
||||
tx_power: number;
|
||||
tx_power: number | null;
|
||||
raw: string | null;
|
||||
}
|
||||
|
||||
|
||||
@@ -217,6 +217,8 @@ export interface CalendarEventApiData {
|
||||
uid?: string | null;
|
||||
recurrence_id?: string | null;
|
||||
rrule?: string | null;
|
||||
status?: "confirmed" | "tentative" | null;
|
||||
all_day?: boolean;
|
||||
}
|
||||
|
||||
export interface CalendarEventSubscription {
|
||||
|
||||
+3
-4
@@ -16,11 +16,10 @@ export type StreamType = typeof STREAM_TYPE_HLS | typeof STREAM_TYPE_WEB_RTC;
|
||||
export { CameraEntityFeature } from "./feature/camera_entity_feature";
|
||||
|
||||
interface CameraEntityAttributes extends HassEntityAttributeBase {
|
||||
model_name: string;
|
||||
model_name?: string;
|
||||
access_token?: string;
|
||||
brand: string;
|
||||
motion_detection: boolean;
|
||||
frontend_stream_type: string;
|
||||
brand?: string;
|
||||
motion_detection?: boolean;
|
||||
}
|
||||
|
||||
export interface CameraEntity extends HassEntityBase {
|
||||
|
||||
@@ -4,8 +4,9 @@ import type { Store } from "home-assistant-js-websocket/dist/store";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import type { RegistryEntry } from "./registry";
|
||||
|
||||
export interface CategoryRegistryEntry {
|
||||
export interface CategoryRegistryEntry extends RegistryEntry {
|
||||
category_id: string;
|
||||
name: string;
|
||||
icon: string | null;
|
||||
|
||||
@@ -138,7 +138,7 @@ interface ChatLogIndexInitialStateEvent {
|
||||
interface ChatLogCreatedEvent {
|
||||
conversation_id: string;
|
||||
event_type: ChatLogEventType.CREATED;
|
||||
data: ChatLogWire;
|
||||
data: { chat_log: ChatLogWire };
|
||||
}
|
||||
|
||||
interface ChatLogUpdatedEvent {
|
||||
@@ -150,7 +150,7 @@ interface ChatLogUpdatedEvent {
|
||||
interface ChatLogDeletedEvent {
|
||||
conversation_id: string;
|
||||
event_type: ChatLogEventType.DELETED;
|
||||
data: ChatLogWire;
|
||||
data: Record<string, never>;
|
||||
}
|
||||
|
||||
interface ChatLogContentAddedEvent {
|
||||
@@ -215,7 +215,7 @@ export const subscribeChatLogIndex = (
|
||||
chatLogs = event.data.map(processChatLog);
|
||||
callback(chatLogs);
|
||||
} else if (event.event_type === ChatLogEventType.CREATED) {
|
||||
chatLogs = [...chatLogs, processChatLog(event.data)];
|
||||
chatLogs = [...chatLogs, processChatLog(event.data.chat_log)];
|
||||
callback(chatLogs);
|
||||
} else if (event.event_type === ChatLogEventType.DELETED) {
|
||||
chatLogs = chatLogs.filter(
|
||||
|
||||
+2
-3
@@ -39,13 +39,12 @@ export type HvacAction =
|
||||
|
||||
export type ClimateEntity = HassEntityBase & {
|
||||
attributes: HassEntityAttributeBase & {
|
||||
hvac_mode: HvacMode;
|
||||
hvac_modes: HvacMode[];
|
||||
hvac_action?: HvacAction;
|
||||
current_temperature: number;
|
||||
current_temperature?: number;
|
||||
min_temp: number;
|
||||
max_temp: number;
|
||||
temperature: number;
|
||||
temperature?: number;
|
||||
target_temp_step?: number;
|
||||
target_temp_high?: number;
|
||||
target_temp_low?: number;
|
||||
|
||||
+8
-9
@@ -1,8 +1,6 @@
|
||||
import type { EntityDomainFilter } from "../common/entity/entity_domain_filter";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
type StrictConnectionMode = "disabled" | "guard_page" | "drop_connection";
|
||||
|
||||
export interface CloudAutoLogin {
|
||||
email: string;
|
||||
failed: string | null;
|
||||
@@ -27,10 +25,11 @@ export interface CloudPreferences {
|
||||
alexa_enabled: boolean;
|
||||
remote_enabled: boolean;
|
||||
remote_allow_remote_enable: boolean;
|
||||
strict_connection: StrictConnectionMode;
|
||||
google_secure_devices_pin: string | undefined;
|
||||
google_secure_devices_pin: string | null;
|
||||
cloudhooks: Record<string, CloudWebhook>;
|
||||
alexa_default_expose: string[] | null;
|
||||
alexa_report_state: boolean;
|
||||
google_default_expose: string[] | null;
|
||||
google_report_state: boolean;
|
||||
tts_default_voice: [string, string];
|
||||
cloud_ice_servers_enabled: boolean;
|
||||
@@ -43,18 +42,19 @@ export type RemoteCertificateStatus =
|
||||
|
||||
export interface CloudStatusLoggedIn {
|
||||
logged_in: true;
|
||||
auto_login: null;
|
||||
cloud: "disconnected" | "connecting" | "connected";
|
||||
cloud_last_disconnect_reason: { clean: boolean; reason: string } | null;
|
||||
email: string;
|
||||
google_registered: boolean;
|
||||
google_local_connected: boolean;
|
||||
google_entities: EntityDomainFilter;
|
||||
google_domains: string[];
|
||||
alexa_registered: boolean;
|
||||
alexa_entities: EntityDomainFilter;
|
||||
prefs: CloudPreferences;
|
||||
remote_domain: string | undefined;
|
||||
remote_domain: string | null;
|
||||
remote_connected: boolean;
|
||||
remote_certificate: undefined | CertificateInformation;
|
||||
remote_certificate: CertificateInformation | null;
|
||||
remote_certificate_status: RemoteCertificateStatus | null;
|
||||
http_use_ssl: boolean;
|
||||
active_subscription: boolean;
|
||||
@@ -115,7 +115,7 @@ export interface CloudLoginMFA extends CloudLoginBase {
|
||||
|
||||
export type CloudEvent =
|
||||
| { type: "login" | "logout" | "auto_login_cancelled" }
|
||||
| { type: "auto_login_failed"; translation_key: string };
|
||||
| { type: "auto_login_failed"; translation_key: string | null };
|
||||
|
||||
export const cloudLogin = ({
|
||||
hass,
|
||||
@@ -196,7 +196,6 @@ export const updateCloudPref = (
|
||||
google_secure_devices_pin?: CloudPreferences["google_secure_devices_pin"];
|
||||
tts_default_voice?: CloudPreferences["tts_default_voice"];
|
||||
remote_allow_remote_enable?: CloudPreferences["remote_allow_remote_enable"];
|
||||
strict_connection?: CloudPreferences["strict_connection"];
|
||||
cloud_ice_servers_enabled?: CloudPreferences["cloud_ice_servers_enabled"];
|
||||
}
|
||||
) =>
|
||||
|
||||
@@ -14,7 +14,8 @@ export interface ConfigEntry {
|
||||
| "setup_retry"
|
||||
| "not_loaded"
|
||||
| "failed_unload"
|
||||
| "setup_in_progress";
|
||||
| "setup_in_progress"
|
||||
| "unload_in_progress";
|
||||
supports_options: boolean;
|
||||
supports_remove_device: boolean;
|
||||
supports_unload: boolean;
|
||||
@@ -34,7 +35,7 @@ export interface SubEntry {
|
||||
subentry_id: string;
|
||||
subentry_type: string;
|
||||
title: string;
|
||||
unique_id: string;
|
||||
unique_id: string | null;
|
||||
}
|
||||
|
||||
export const getSubEntries = (hass: HomeAssistant, entry_id: string) =>
|
||||
@@ -102,7 +103,6 @@ export const subscribeConfigEntries = (
|
||||
callbackFunction: (message: ConfigEntryUpdate[]) => void,
|
||||
filters?: {
|
||||
type?: IntegrationType[];
|
||||
domain?: string;
|
||||
}
|
||||
): Promise<UnsubscribeFunc> => {
|
||||
const params: any = {
|
||||
|
||||
+12
-7
@@ -5,7 +5,6 @@ import type {
|
||||
DataEntryFlowProgress,
|
||||
DataEntryFlowStep,
|
||||
} from "./data_entry_flow";
|
||||
import type { IntegrationType } from "./integration";
|
||||
import { domainToName } from "./integration";
|
||||
|
||||
export const DISCOVERY_SOURCES = [
|
||||
@@ -75,9 +74,10 @@ export const ignoreConfigFlow = (
|
||||
export const deleteConfigFlow = (hass: HomeAssistant, flowId: string) =>
|
||||
hass.callApi("DELETE", `config/config_entries/flow/${flowId}`);
|
||||
|
||||
// The backend indexes a single key, and only these two exist.
|
||||
export const getConfigFlowHandlers = (
|
||||
hass: HomeAssistant,
|
||||
type?: IntegrationType[]
|
||||
type?: "helper" | "integration"
|
||||
) =>
|
||||
hass.callApi<string[]>(
|
||||
"GET",
|
||||
@@ -91,11 +91,16 @@ export const fetchConfigFlowInProgress = (
|
||||
type: "config_entries/flow/progress",
|
||||
});
|
||||
|
||||
export interface ConfigFlowInProgressMessage {
|
||||
type: null | "added" | "removed";
|
||||
flow_id: string;
|
||||
flow: DataEntryFlowProgress;
|
||||
}
|
||||
export type ConfigFlowInProgressMessage =
|
||||
| {
|
||||
type: "removed";
|
||||
flow_id: string;
|
||||
}
|
||||
| {
|
||||
type: null | "added";
|
||||
flow_id: string;
|
||||
flow: DataEntryFlowProgress;
|
||||
};
|
||||
|
||||
export const subscribeConfigFlowInProgress = (
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -6,20 +6,28 @@ export enum ConversationEntityFeature {
|
||||
}
|
||||
|
||||
interface IntentTarget {
|
||||
type: "area" | "device" | "entity" | "domain" | "device_class" | "custom";
|
||||
type:
|
||||
| "area"
|
||||
| "floor"
|
||||
| "device"
|
||||
| "entity"
|
||||
| "domain"
|
||||
| "device_class"
|
||||
| "custom";
|
||||
name: string;
|
||||
id: string | null;
|
||||
}
|
||||
|
||||
interface IntentResultBase {
|
||||
language: string;
|
||||
speech: Record<"plain" | "ssml", { extra_data: any; speech: string }> | null;
|
||||
speech: Partial<
|
||||
Record<"plain" | "ssml", { extra_data: any; speech: string }>
|
||||
>;
|
||||
}
|
||||
|
||||
interface IntentResultActionDone extends IntentResultBase {
|
||||
response_type: "action_done";
|
||||
data: {
|
||||
targets: IntentTarget[];
|
||||
success: IntentTarget[];
|
||||
failed: IntentTarget[];
|
||||
};
|
||||
@@ -28,7 +36,6 @@ interface IntentResultActionDone extends IntentResultBase {
|
||||
interface IntentResultQueryAnswer extends IntentResultBase {
|
||||
response_type: "query_answer";
|
||||
data: {
|
||||
targets: IntentTarget[];
|
||||
success: IntentTarget[];
|
||||
failed: IntentTarget[];
|
||||
};
|
||||
@@ -56,17 +63,24 @@ export interface Agent {
|
||||
}
|
||||
|
||||
export interface AssistDebugResult {
|
||||
intent: {
|
||||
match: boolean;
|
||||
sentence_template: string;
|
||||
source?: "trigger" | "custom" | "builtin";
|
||||
file?: string | null;
|
||||
intent?: {
|
||||
name: string;
|
||||
};
|
||||
entities: Record<
|
||||
slots?: Record<string, unknown>;
|
||||
details?: Record<
|
||||
string,
|
||||
{
|
||||
name: string;
|
||||
value: string;
|
||||
text: string;
|
||||
value: unknown;
|
||||
text: string | null;
|
||||
}
|
||||
>;
|
||||
targets?: Record<string, { matched: boolean }>;
|
||||
unmatched_slots?: Record<string, string | number>;
|
||||
}
|
||||
|
||||
export interface AssistDebugResponse {
|
||||
@@ -132,7 +146,7 @@ export const getLanguageScores = (
|
||||
hass: HomeAssistant,
|
||||
language?: string,
|
||||
country?: string
|
||||
): Promise<{ languages: LanguageScores; preferred_language: string | null }> =>
|
||||
): Promise<{ languages: LanguageScores; preferred_language: string }> =>
|
||||
hass.callWS({
|
||||
type: "conversation/agent/homeassistant/language_scores",
|
||||
language,
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ export interface Counter {
|
||||
icon?: string;
|
||||
initial?: number;
|
||||
restore?: boolean;
|
||||
minimum?: number;
|
||||
maximum?: number;
|
||||
minimum?: number | null;
|
||||
maximum?: number | null;
|
||||
step?: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,7 @@ export function canStopTilt(stateObj: CoverEntity): boolean {
|
||||
interface CoverEntityAttributes extends HassEntityAttributeBase {
|
||||
current_position?: number;
|
||||
current_tilt_position?: number;
|
||||
supported_speeds?: string[];
|
||||
}
|
||||
|
||||
export interface CoverEntity extends HassEntityBase {
|
||||
|
||||
@@ -29,7 +29,7 @@ export interface DataEntryFlowProgress {
|
||||
handler: string;
|
||||
step_id: string;
|
||||
context: {
|
||||
title_placeholders: Record<string, string>;
|
||||
title_placeholders?: Record<string, string>;
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export interface DataEntryFlowStepForm {
|
||||
handler: string;
|
||||
step_id: string;
|
||||
data_schema: HaFormSchema[];
|
||||
errors: Record<string, string>;
|
||||
errors: Record<string, string> | null;
|
||||
description_placeholders?: Record<string, string>;
|
||||
last_step: boolean | null;
|
||||
preview?: string;
|
||||
@@ -61,13 +61,13 @@ export interface DataEntryFlowStepCreateEntry<
|
||||
TResult extends ConfigEntry | RepairsIssue = ConfigEntry,
|
||||
> {
|
||||
type: "create_entry";
|
||||
version: number;
|
||||
version?: number;
|
||||
flow_id: string;
|
||||
next_flow?: [FlowType, string]; // [flow_type, flow_id]
|
||||
handler: string;
|
||||
title: string;
|
||||
result?: TResult;
|
||||
description: string;
|
||||
title?: string;
|
||||
result?: TResult | null;
|
||||
description: string | null;
|
||||
description_placeholders?: Record<string, string>;
|
||||
translation_domain?: string;
|
||||
}
|
||||
@@ -81,7 +81,7 @@ export interface DataEntryFlowStepAbort<
|
||||
reason: string;
|
||||
description_placeholders?: Record<string, string>;
|
||||
translation_domain?: string;
|
||||
result?: TResult;
|
||||
result?: TResult | null;
|
||||
next_flow?: [FlowType, string]; // [flow_type, flow_id]
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -242,8 +242,8 @@ export interface EnergyInfo {
|
||||
|
||||
export interface EnergyValidationIssue {
|
||||
type: string;
|
||||
affected_entities: [string, unknown][];
|
||||
translation_placeholders: Record<string, string>;
|
||||
affected_entities: [string, string | number | null][];
|
||||
translation_placeholders: Record<string, string> | null;
|
||||
}
|
||||
|
||||
export interface EnergyPreferencesValidation {
|
||||
|
||||
@@ -37,7 +37,7 @@ export interface EntityRegistryDisplayEntryResponse {
|
||||
ec?: number;
|
||||
en?: string;
|
||||
ic?: string;
|
||||
pl?: string;
|
||||
pl: string;
|
||||
tk?: string;
|
||||
hb?: boolean;
|
||||
dp?: number;
|
||||
@@ -58,7 +58,7 @@ export interface EntityRegistryEntry extends RegistryEntry {
|
||||
area_id: string | null;
|
||||
labels: string[];
|
||||
disabled_by: "user" | "device" | "integration" | "config_entry" | null;
|
||||
hidden_by: Exclude<EntityRegistryEntry["disabled_by"], "config_entry">;
|
||||
hidden_by: "integration" | "user" | null;
|
||||
entity_category: EntityCategory | null;
|
||||
has_entity_name: boolean;
|
||||
original_name?: string;
|
||||
@@ -69,7 +69,7 @@ export interface EntityRegistryEntry extends RegistryEntry {
|
||||
}
|
||||
|
||||
export interface ExtEntityRegistryEntry extends EntityRegistryEntry {
|
||||
capabilities: Record<string, unknown>;
|
||||
capabilities: Record<string, unknown> | null;
|
||||
original_icon?: string;
|
||||
device_class?: string;
|
||||
original_device_class?: string;
|
||||
@@ -196,7 +196,7 @@ export interface EntityRegistryEntryUpdateParams {
|
||||
device_class?: string | null;
|
||||
area_id?: string | null;
|
||||
disabled_by?: string | null;
|
||||
hidden_by: string | null;
|
||||
hidden_by?: string | null;
|
||||
new_entity_id?: string;
|
||||
options_domain?: string;
|
||||
options?:
|
||||
@@ -278,7 +278,7 @@ export const getExtendedEntityRegistryEntry = (
|
||||
export const getExtendedEntityRegistryEntries = (
|
||||
hass: HomeAssistant,
|
||||
entityIds: string[]
|
||||
): Promise<Record<string, ExtEntityRegistryEntry>> =>
|
||||
): Promise<Record<string, ExtEntityRegistryEntry | null>> =>
|
||||
hass.callWS({
|
||||
type: "config/entity_registry/get_entries",
|
||||
entity_ids: entityIds,
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export interface ESPHomeEncryptionKey {
|
||||
encryption_key: string;
|
||||
encryption_key: string | null;
|
||||
}
|
||||
|
||||
export const fetchESPHomeEncryptionKey = (
|
||||
|
||||
@@ -7,4 +7,5 @@ export enum CoverEntityFeature {
|
||||
CLOSE_TILT = 32,
|
||||
STOP_TILT = 64,
|
||||
SET_TILT_POSITION = 128,
|
||||
SPEED = 256,
|
||||
}
|
||||
|
||||
@@ -19,4 +19,7 @@ export enum MediaPlayerEntityFeature {
|
||||
BROWSE_MEDIA = 131072,
|
||||
REPEAT_SET = 262144,
|
||||
GROUPING = 524288,
|
||||
MEDIA_ANNOUNCE = 1048576,
|
||||
MEDIA_ENQUEUE = 2097152,
|
||||
SEARCH_MEDIA = 4194304,
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export const updateFloorRegistryEntry = (
|
||||
floorId: string,
|
||||
updates: Partial<FloorRegistryEntryMutableParams>
|
||||
) =>
|
||||
hass.callWS<AreaRegistryEntry>({
|
||||
hass.callWS<FloorRegistryEntry>({
|
||||
type: "config/floor_registry/update",
|
||||
floor_id: floorId,
|
||||
...updates,
|
||||
|
||||
@@ -4,7 +4,7 @@ export interface GoogleEntity {
|
||||
entity_id: string;
|
||||
traits: string[];
|
||||
might_2fa: boolean;
|
||||
disable_2fa?: boolean;
|
||||
disable_2fa?: boolean | null;
|
||||
}
|
||||
|
||||
export const fetchCloudGoogleEntities = (hass: HomeAssistant) =>
|
||||
|
||||
+5
-5
@@ -80,7 +80,7 @@ export interface EntityHistoryState {
|
||||
/** state */
|
||||
s: string;
|
||||
/** attributes */
|
||||
a: Record<string, any>;
|
||||
a?: Record<string, any>;
|
||||
/** last_changed; if set, also applies to lu */
|
||||
lc?: number;
|
||||
/** last_updated */
|
||||
@@ -384,7 +384,7 @@ const processTimelineEntity = (
|
||||
? " ◂ "
|
||||
: " ▸ ",
|
||||
}) || current_state.entity_id
|
||||
: computeStateNameFromEntityAttributes(entityId, first.a),
|
||||
: computeStateNameFromEntityAttributes(entityId, first.a ?? {}),
|
||||
entity_id: entityId,
|
||||
data,
|
||||
};
|
||||
@@ -423,7 +423,7 @@ const processLineChartEntities = (
|
||||
};
|
||||
|
||||
for (const attr of LINE_ATTRIBUTES_TO_KEEP) {
|
||||
if (attr in state.a) {
|
||||
if (state.a && attr in state.a) {
|
||||
processedState.attributes![attr] = state.a[attr];
|
||||
}
|
||||
}
|
||||
@@ -456,7 +456,7 @@ const processLineChartEntities = (
|
||||
}) || entityId
|
||||
: computeStateNameFromEntityAttributes(
|
||||
entityId,
|
||||
"friendly_name" in first.a ? first.a : {}
|
||||
first.a && "friendly_name" in first.a ? first.a : {}
|
||||
);
|
||||
|
||||
data.push({
|
||||
@@ -603,7 +603,7 @@ export const computeHistory = (
|
||||
if (isNumeric) {
|
||||
unit =
|
||||
currentState?.attributes.unit_of_measurement ||
|
||||
numericStateFromHistory?.a.unit_of_measurement ||
|
||||
numericStateFromHistory?.a?.unit_of_measurement ||
|
||||
BLANK_UNIT;
|
||||
} else {
|
||||
unit = {
|
||||
|
||||
@@ -75,7 +75,7 @@ export const updateImage = (
|
||||
) =>
|
||||
hass.callWS<Image>({
|
||||
type: "image/update",
|
||||
media_id: id,
|
||||
image_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface InputText {
|
||||
max?: number;
|
||||
pattern?: string;
|
||||
mode?: "text" | "password";
|
||||
unit_of_measurement?: string;
|
||||
}
|
||||
|
||||
export interface InputTextMutableParams {
|
||||
|
||||
+23
-5
@@ -37,7 +37,14 @@ export const getConfigPanelPath = (
|
||||
?.url_path || integrationsWithPanel[domain];
|
||||
|
||||
export type IntegrationType =
|
||||
"device" | "helper" | "hub" | "service" | "hardware" | "entity" | "system";
|
||||
| "device"
|
||||
| "helper"
|
||||
| "hub"
|
||||
| "service"
|
||||
| "hardware"
|
||||
| "entity"
|
||||
| "system"
|
||||
| "virtual";
|
||||
|
||||
export type DomainManifestLookup = Record<string, IntegrationManifest>;
|
||||
|
||||
@@ -46,7 +53,7 @@ export interface IntegrationManifest {
|
||||
overwrites_built_in?: boolean;
|
||||
domain: string;
|
||||
name: string;
|
||||
config_flow: boolean;
|
||||
config_flow?: boolean;
|
||||
documentation?: string;
|
||||
issue_tracker?: string;
|
||||
dependencies?: string[];
|
||||
@@ -54,8 +61,18 @@ export interface IntegrationManifest {
|
||||
codeowners?: string[];
|
||||
requirements?: string[];
|
||||
ssdp?: { manufacturer?: string; modelName?: string; st?: string }[];
|
||||
zeroconf?: string[];
|
||||
homekit?: { models: string[] };
|
||||
zeroconf?: (
|
||||
| string
|
||||
| {
|
||||
type: string;
|
||||
macaddress?: string;
|
||||
manufacturer?: string;
|
||||
model?: string;
|
||||
name?: string;
|
||||
properties?: Record<string, string>;
|
||||
}
|
||||
)[];
|
||||
homekit?: { models?: string[] };
|
||||
integration_type?: IntegrationType;
|
||||
loggers?: string[];
|
||||
quality_scale?:
|
||||
@@ -67,8 +84,9 @@ export interface IntegrationManifest {
|
||||
| "internal"
|
||||
| "legacy"
|
||||
| "custom";
|
||||
iot_class:
|
||||
iot_class?:
|
||||
| "assumed_state"
|
||||
| "calculated"
|
||||
| "cloud_polling"
|
||||
| "cloud_push"
|
||||
| "local_polling"
|
||||
|
||||
@@ -69,12 +69,9 @@ export const getLightCurrentModeRgbColor = (
|
||||
interface LightEntityAttributes extends HassEntityAttributeBase {
|
||||
min_color_temp_kelvin?: number;
|
||||
max_color_temp_kelvin?: number;
|
||||
min_mireds?: number;
|
||||
max_mireds?: number;
|
||||
brightness?: number;
|
||||
xy_color?: [number, number];
|
||||
hs_color?: [number, number];
|
||||
color_temp?: number;
|
||||
color_temp_kelvin?: number;
|
||||
rgb_color?: [number, number, number];
|
||||
rgbw_color?: [number, number, number, number];
|
||||
|
||||
@@ -42,9 +42,9 @@ export interface MatterLockUser {
|
||||
user_index: number | null;
|
||||
user_name: string | null;
|
||||
user_unique_id: number | null;
|
||||
user_status: MatterLockUserStatus;
|
||||
user_status: MatterLockUserStatus | "unknown";
|
||||
user_type: MatterLockUserType;
|
||||
credential_rule: MatterLockCredentialRule;
|
||||
credential_rule: MatterLockCredentialRule | "unknown";
|
||||
credentials: MatterLockCredentialRef[];
|
||||
next_user_index: number | null;
|
||||
}
|
||||
@@ -64,9 +64,9 @@ export interface SetMatterLockUserParams {
|
||||
export interface SetMatterLockCredentialParams {
|
||||
credential_type: MatterLockCredentialType;
|
||||
credential_data: string;
|
||||
credential_index?: number | null;
|
||||
user_index?: number | null;
|
||||
user_status?: MatterLockUserStatus;
|
||||
credential_index?: number;
|
||||
user_index?: number;
|
||||
user_status?: "occupied_enabled" | "occupied_disabled";
|
||||
user_type?: MatterLockUserType;
|
||||
}
|
||||
|
||||
|
||||
+5
-8
@@ -27,17 +27,17 @@ export interface MatterFabricData {
|
||||
fabric_id: number;
|
||||
vendor_id: number;
|
||||
fabric_index: number;
|
||||
fabric_label?: string;
|
||||
vendor_name?: string;
|
||||
fabric_label: string | null;
|
||||
vendor_name: string | null;
|
||||
}
|
||||
|
||||
export interface MatterNodeDiagnostics {
|
||||
node_id: number;
|
||||
network_type: NetworkType;
|
||||
node_type: NodeType;
|
||||
network_name?: string;
|
||||
network_name: string | null;
|
||||
ip_adresses: string[];
|
||||
mac_address?: string;
|
||||
mac_address: string | null;
|
||||
available: boolean;
|
||||
active_fabrics: MatterFabricData[];
|
||||
active_fabric_index: number;
|
||||
@@ -45,9 +45,6 @@ export interface MatterNodeDiagnostics {
|
||||
|
||||
export type MatterPingResult = Record<string, boolean>;
|
||||
|
||||
export type MatterTopologyNodeKind =
|
||||
"matter" | "border_router" | "thread_unknown" | "wifi_ap";
|
||||
|
||||
export type MatterTopologyStrength =
|
||||
"strong" | "medium" | "weak" | "none" | "unknown";
|
||||
|
||||
@@ -59,7 +56,7 @@ export interface MatterTopologyDirectionInfo {
|
||||
|
||||
export interface MatterNetworkTopologyNode {
|
||||
id: string;
|
||||
kind: MatterTopologyNodeKind;
|
||||
kind: string;
|
||||
network_type: string;
|
||||
node_id?: number | null;
|
||||
ha_device_id?: string | null;
|
||||
|
||||
@@ -48,10 +48,14 @@ interface MediaPlayerEntityAttributes extends HassEntityAttributeBase {
|
||||
media_content_id?: string;
|
||||
media_content_type?: string;
|
||||
media_artist?: string;
|
||||
media_album_name?: string;
|
||||
media_album_artist?: string;
|
||||
media_track?: number;
|
||||
media_playlist?: string;
|
||||
media_series_title?: string;
|
||||
media_season?: any;
|
||||
media_episode?: any;
|
||||
app_id?: string;
|
||||
app_name?: string;
|
||||
media_position_updated_at?: string | number | Date;
|
||||
media_duration?: number;
|
||||
|
||||
+7
-3
@@ -4,13 +4,17 @@ export interface MQTTMessage {
|
||||
topic: string;
|
||||
payload: string;
|
||||
qos: number;
|
||||
retain: number;
|
||||
retain: boolean;
|
||||
}
|
||||
|
||||
// Debug info replays stored messages, so it also carries when each was logged.
|
||||
export interface MQTTLoggedMessage extends MQTTMessage {
|
||||
time: string;
|
||||
}
|
||||
|
||||
export interface MQTTTopicDebugInfo {
|
||||
topic: string;
|
||||
messages: MQTTMessage[];
|
||||
messages: MQTTLoggedMessage[];
|
||||
}
|
||||
|
||||
export interface MQTTDiscoveryDebugInfo {
|
||||
@@ -29,7 +33,7 @@ export interface MQTTEntityDebugInfo {
|
||||
}
|
||||
|
||||
export interface MQTTTriggerDebugInfo {
|
||||
discovery_data: MQTTDiscoveryDebugInfo;
|
||||
discovery_data: MQTTDiscoveryDebugInfo | null;
|
||||
}
|
||||
|
||||
export interface MQTTDeviceDebugInfo {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { handleFetchPromise } from "../util/hass-call-api";
|
||||
import type { CloudStatus } from "./cloud";
|
||||
|
||||
export interface InstallationType {
|
||||
installation_type:
|
||||
| "Home Assistant Operating System"
|
||||
| "Home Assistant OS"
|
||||
| "Home Assistant Container"
|
||||
| "Unsupported Third Party Container"
|
||||
| "Home Assistant Supervised"
|
||||
| "Home Assistant Core"
|
||||
| "Unknown";
|
||||
@@ -49,6 +49,10 @@ export interface CloudLoginMFA extends CloudLoginBase {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface OnboardingCloudStatus {
|
||||
logged_in: boolean;
|
||||
}
|
||||
|
||||
export const fetchOnboardingOverview = () =>
|
||||
fetch(`${__HASS_URL__}/api/onboarding`, { credentials: "same-origin" });
|
||||
|
||||
@@ -115,7 +119,7 @@ export const loginHaCloud = async (
|
||||
})
|
||||
);
|
||||
|
||||
export const fetchHaCloudStatus = async (): Promise<CloudStatus> =>
|
||||
export const fetchHaCloudStatus = async (): Promise<OnboardingCloudStatus> =>
|
||||
handleFetchPromise(fetch("/api/onboarding/cloud/status"));
|
||||
|
||||
export const signOutHaCloud = async () =>
|
||||
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export interface OTBRInfo {
|
||||
active_dataset_tlvs: string;
|
||||
active_dataset_tlvs: string | null;
|
||||
border_agent_id: string;
|
||||
channel: number;
|
||||
channel: number | null;
|
||||
extended_address: string;
|
||||
extended_pan_id: string;
|
||||
extended_pan_id: string | null;
|
||||
url: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,7 @@ export interface PersistentNotification {
|
||||
created_at: string;
|
||||
message: string;
|
||||
notification_id: string;
|
||||
title: string;
|
||||
status: "read" | "unread";
|
||||
title: string | null;
|
||||
}
|
||||
|
||||
export type PersistentNotifications = Record<string, PersistentNotification>;
|
||||
|
||||
+1
-2
@@ -14,8 +14,7 @@ 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",
|
||||
user_input: Record<string, any>,
|
||||
callback: (preview: GenericPreview) => void
|
||||
): Promise<UnsubscribeFunc> =>
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { HomeAssistant } from "../types";
|
||||
import { firstWeekday } from "../common/datetime/first_weekday";
|
||||
|
||||
export interface RecorderInfo {
|
||||
backlog: number | null;
|
||||
backlog: number;
|
||||
db_in_default_location: boolean;
|
||||
max_backlog: number;
|
||||
migration_in_progress: boolean;
|
||||
@@ -33,10 +33,10 @@ export interface StatisticValue {
|
||||
}
|
||||
|
||||
export interface Statistic {
|
||||
max: number | null;
|
||||
mean: number | null;
|
||||
min: number | null;
|
||||
change: number | null;
|
||||
max?: number | null;
|
||||
mean?: number | null;
|
||||
min?: number | null;
|
||||
change?: number | null;
|
||||
}
|
||||
|
||||
export enum StatisticMeanType {
|
||||
@@ -96,11 +96,11 @@ export interface StatisticsValidationResultUnitsChanged {
|
||||
type: "units_changed";
|
||||
data: {
|
||||
statistic_id: string;
|
||||
state_unit: string;
|
||||
state_unit: string | null;
|
||||
state_unit_class: string | null;
|
||||
metadata_unit: string;
|
||||
metadata_unit: string | null;
|
||||
metadata_unit_class: string | null;
|
||||
supported_unit: string;
|
||||
supported_unit: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ export const fetchStatistic = (
|
||||
period: {
|
||||
fixed_period?: { start: string | Date; end: string | Date };
|
||||
calendar?: { period: string; offset?: number };
|
||||
rolling_window?: { duration: HaDurationData; offset: HaDurationData };
|
||||
rolling_window?: { duration: HaDurationData; offset?: HaDurationData };
|
||||
},
|
||||
units?: StatisticsUnitConfiguration
|
||||
) =>
|
||||
|
||||
@@ -4,17 +4,18 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export type RefreshTokenType = "normal" | "long_lived_access_token";
|
||||
export type RefreshTokenType = "normal" | "system" | "long_lived_access_token";
|
||||
|
||||
export interface RefreshToken {
|
||||
client_icon?: string;
|
||||
client_id: string;
|
||||
client_name?: string;
|
||||
auth_provider_type: string | null;
|
||||
client_icon: string | null;
|
||||
client_id: string | null;
|
||||
client_name: string | null;
|
||||
created_at: string;
|
||||
expire_at?: string;
|
||||
expire_at: string | null;
|
||||
id: string;
|
||||
is_current: boolean;
|
||||
last_used_at?: string;
|
||||
last_used_ip?: string;
|
||||
last_used_at: string | null;
|
||||
last_used_ip: string | null;
|
||||
type: RefreshTokenType;
|
||||
}
|
||||
|
||||
+2
-2
@@ -11,8 +11,8 @@ export enum RemoteEntityFeature {
|
||||
|
||||
export type RemoteEntity = HassEntityBase & {
|
||||
attributes: HassEntityAttributeBase & {
|
||||
current_activity: string | null;
|
||||
activity_list: string[] | null;
|
||||
current_activity?: string | null;
|
||||
activity_list?: string[] | null;
|
||||
[key: string]: any;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ export const weekdays = [
|
||||
export interface ScheduleDay {
|
||||
from: string;
|
||||
to: string;
|
||||
data?: Record<string, boolean | string | number>;
|
||||
}
|
||||
|
||||
type ScheduleDays = Partial<Record<(typeof weekdays)[number], ScheduleDay[]>>;
|
||||
|
||||
@@ -438,6 +438,7 @@ interface ObjectSelectorField {
|
||||
label?: string;
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
default?: unknown;
|
||||
}
|
||||
|
||||
export interface ObjectSelector {
|
||||
|
||||
+7
-7
@@ -7,15 +7,15 @@ import type { Store } from "home-assistant-js-websocket/dist/store";
|
||||
import type { DataTableRowData } from "../components/data-table/ha-data-table";
|
||||
|
||||
export interface SSDPDiscoveryData extends DataTableRowData {
|
||||
name: string | undefined;
|
||||
name: string | null;
|
||||
ssdp_usn: string;
|
||||
ssdp_st: string;
|
||||
upnp: Record<string, unknown>;
|
||||
ssdp_location: string | undefined;
|
||||
ssdp_nt: string | undefined;
|
||||
ssdp_udn: string | undefined;
|
||||
ssdp_ext: string | undefined;
|
||||
ssdp_server: string | undefined;
|
||||
ssdp_location: string | null;
|
||||
ssdp_nt: string | null;
|
||||
ssdp_udn: string | null;
|
||||
ssdp_ext: string | null;
|
||||
ssdp_server: string | null;
|
||||
ssdp_headers: Record<string, unknown>;
|
||||
ssdp_all_locations: string[];
|
||||
x_homeassistant_matching_domains: string[];
|
||||
@@ -23,7 +23,7 @@ export interface SSDPDiscoveryData extends DataTableRowData {
|
||||
|
||||
interface SSDPRemoveDiscoveryData {
|
||||
ssdp_st: string;
|
||||
ssdp_location: string | undefined;
|
||||
ssdp_location: string | null;
|
||||
}
|
||||
|
||||
interface SSDPSubscriptionMessage {
|
||||
|
||||
+1
-2
@@ -12,9 +12,8 @@ export interface SpeechMetadata {
|
||||
|
||||
export interface STTEngine {
|
||||
engine_id: string;
|
||||
supported_languages?: string[];
|
||||
supported_languages: string[];
|
||||
name?: string;
|
||||
deprecated: boolean;
|
||||
}
|
||||
|
||||
export const listSTTEngines = (
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface Tag {
|
||||
name?: string;
|
||||
description?: string;
|
||||
last_scanned?: string;
|
||||
device_id?: string;
|
||||
}
|
||||
|
||||
export interface UpdateTagParams {
|
||||
|
||||
+12
-7
@@ -2,7 +2,7 @@ import type { HomeAssistant } from "../types";
|
||||
|
||||
export interface ThreadRouter {
|
||||
instance_name: string;
|
||||
addresses: [string];
|
||||
addresses: string[];
|
||||
border_agent_id: string | null;
|
||||
brand: string | null;
|
||||
extended_address: string;
|
||||
@@ -28,11 +28,16 @@ export interface ThreadDataSet {
|
||||
source: string;
|
||||
}
|
||||
|
||||
export interface ThreadRouterDiscoveryEvent {
|
||||
key: string;
|
||||
type: "router_discovered" | "router_removed";
|
||||
data: ThreadRouter;
|
||||
}
|
||||
export type ThreadRouterDiscoveryEvent =
|
||||
| {
|
||||
key: string;
|
||||
type: "router_discovered";
|
||||
data: ThreadRouter;
|
||||
}
|
||||
| {
|
||||
key: string;
|
||||
type: "router_removed";
|
||||
};
|
||||
|
||||
class DiscoveryStream {
|
||||
routers: Record<string, ThreadRouter>;
|
||||
@@ -81,7 +86,7 @@ export const addThreadDataSet = (
|
||||
hass: HomeAssistant,
|
||||
source: string,
|
||||
tlv: string
|
||||
): Promise<void> =>
|
||||
): Promise<{ result: "stored" | "discarded" }> =>
|
||||
hass.callWS({
|
||||
type: "thread/add_dataset_tlv",
|
||||
source,
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ export const subscribeItems = (
|
||||
entity_id: string,
|
||||
callback: (update: TodoItems) => void
|
||||
) =>
|
||||
hass.connection.subscribeMessage<any>(callback, {
|
||||
hass.connection.subscribeMessage<TodoItems>(callback, {
|
||||
type: "todo/item/subscribe",
|
||||
entity_id,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
import { ensureArray } from "../common/array/ensure-array";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { Condition, Trigger } from "./automation";
|
||||
import { describeCondition, describeTrigger } from "./automation_i18n";
|
||||
import type { EntityRegistryEntry } from "./entity/entity_registry";
|
||||
import type { DomainManifestLookup } from "./integration";
|
||||
import { describeAction } from "./script_i18n";
|
||||
import type { TraceExtended } from "./trace";
|
||||
import { TraceTree } from "./trace-tree";
|
||||
import type { TraceActionNode, TraceBranch, TraceNode } from "./trace-tree";
|
||||
|
||||
const STATE_KEY = "ui.panel.config.automation.trace.graph.state";
|
||||
|
||||
/** Shared so an absent registry does not break the callers' memoization. */
|
||||
const NO_ENTITY_REGISTRY: EntityRegistryEntry[] = [];
|
||||
|
||||
interface LabelContext {
|
||||
hass: HomeAssistant;
|
||||
entityRegistry: EntityRegistryEntry[];
|
||||
manifests?: DomainManifestLookup;
|
||||
}
|
||||
|
||||
type StatefulNode = Pick<TraceNode, "disabled" | "error" | "track"> &
|
||||
Partial<Pick<TraceNode, "notTriggered" | "condition">>;
|
||||
|
||||
/**
|
||||
* Whether a condition passed is drawn as the tracked path, and failing as a
|
||||
* cross on a node that is aria-hidden, so the outcome needs saying. A repeated
|
||||
* condition can have done both across its evaluations.
|
||||
*/
|
||||
const conditionOutcome = (
|
||||
condition: NonNullable<TraceNode["condition"]>
|
||||
): "passed" | "failed" | "passed_and_failed" | undefined => {
|
||||
if (condition.passed && condition.failed) {
|
||||
return "passed_and_failed";
|
||||
}
|
||||
if (condition.passed) {
|
||||
return "passed";
|
||||
}
|
||||
if (condition.failed) {
|
||||
return "failed";
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/** The graph shows the run outcome only visually, so the label says it. */
|
||||
const withState = (
|
||||
{ hass }: LabelContext,
|
||||
description: string,
|
||||
node: StatefulNode,
|
||||
badge?: number
|
||||
): string => {
|
||||
const outcome = node.condition && conditionOutcome(node.condition);
|
||||
let state: string;
|
||||
if (node.disabled) {
|
||||
state = hass.localize(`${STATE_KEY}.disabled`);
|
||||
} else if (node.error) {
|
||||
state = hass.localize(`${STATE_KEY}.error`);
|
||||
} else if (node.notTriggered) {
|
||||
state = hass.localize(`${STATE_KEY}.not_triggered`);
|
||||
} else if (outcome) {
|
||||
state = hass.localize(`${STATE_KEY}.${outcome}`);
|
||||
} else if (node.track && badge) {
|
||||
state = hass.localize(`${STATE_KEY}.repeated`, { count: badge });
|
||||
} else if (node.track) {
|
||||
state = hass.localize(`${STATE_KEY}.executed`);
|
||||
} else {
|
||||
state = hass.localize(`${STATE_KEY}.not_executed`);
|
||||
}
|
||||
return hass.localize("ui.panel.config.automation.trace.graph.node_label", {
|
||||
description,
|
||||
state,
|
||||
});
|
||||
};
|
||||
|
||||
/** Named by its first condition, as `ha-automation-option-row` does. */
|
||||
const optionLabel = (ctx: LabelContext, branch: TraceBranch): string => {
|
||||
const conditions = branch.option?.conditions
|
||||
? ensureArray<Condition | string>(branch.option.conditions)
|
||||
: undefined;
|
||||
let description: string;
|
||||
if (!conditions || conditions.length === 0) {
|
||||
description = ctx.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.no_conditions"
|
||||
);
|
||||
} else if (typeof conditions[0] === "string") {
|
||||
description = conditions[0];
|
||||
} else {
|
||||
description = describeCondition(
|
||||
conditions[0],
|
||||
ctx.hass,
|
||||
ctx.entityRegistry
|
||||
);
|
||||
}
|
||||
if (conditions && conditions.length > 1) {
|
||||
description += ctx.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.option_description_additional",
|
||||
{ numberOfAdditionalConditions: conditions.length - 1 }
|
||||
);
|
||||
}
|
||||
return withState(ctx, description, {
|
||||
disabled: branch.disabled,
|
||||
error: false,
|
||||
track: branch.hasTrace,
|
||||
});
|
||||
};
|
||||
|
||||
const addAction = (
|
||||
ctx: LabelContext,
|
||||
labels: Record<string, string>,
|
||||
node: TraceActionNode
|
||||
): void => {
|
||||
labels[node.path] = withState(
|
||||
ctx,
|
||||
// The tree's `actionType` is a wider union than describeAction accepts;
|
||||
// it derives the type from the config anyway.
|
||||
describeAction(
|
||||
ctx.hass,
|
||||
ctx.entityRegistry,
|
||||
node.config,
|
||||
undefined,
|
||||
undefined,
|
||||
ctx.manifests
|
||||
),
|
||||
node,
|
||||
node.badge
|
||||
);
|
||||
for (const branch of node.branches) {
|
||||
// Only `choose` branches carry an option, which gets a node of its own.
|
||||
if (branch.option) {
|
||||
labels[branch.path] = optionLabel(ctx, branch);
|
||||
}
|
||||
for (const child of branch.children) {
|
||||
addAction(ctx, labels, child);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Accessible names for every step of a trace, keyed by node path. Called from
|
||||
* the trace panels, which own `hass`; memoize on everything but `hass`, which
|
||||
* is replaced on every state update.
|
||||
*/
|
||||
export const buildTraceLabels = (
|
||||
trace: TraceExtended,
|
||||
hass: HomeAssistant,
|
||||
entityRegistry: EntityRegistryEntry[] | undefined,
|
||||
manifests?: DomainManifestLookup
|
||||
): Record<string, string> => {
|
||||
const entities = entityRegistry ?? NO_ENTITY_REGISTRY;
|
||||
const ctx: LabelContext = { hass, entityRegistry: entities, manifests };
|
||||
const tree = new TraceTree(trace);
|
||||
const labels: Record<string, string> = {};
|
||||
|
||||
tree.triggers?.forEach((node: TraceNode<Trigger>) => {
|
||||
labels[node.path] = withState(
|
||||
ctx,
|
||||
describeTrigger(node.config, hass, entities),
|
||||
node
|
||||
);
|
||||
});
|
||||
tree.conditions.forEach((node: TraceNode<Condition>) => {
|
||||
labels[node.path] = withState(
|
||||
ctx,
|
||||
describeCondition(node.config, hass, entities),
|
||||
node
|
||||
);
|
||||
});
|
||||
tree.actions.forEach((node) => addAction(ctx, labels, node));
|
||||
tree.sequence.forEach((node) => addAction(ctx, labels, node));
|
||||
|
||||
return labels;
|
||||
};
|
||||
+2
-2
@@ -2,9 +2,9 @@ import type { HomeAssistant } from "../types";
|
||||
|
||||
export interface TTSEngine {
|
||||
engine_id: string;
|
||||
supported_languages?: string[];
|
||||
supported_languages: string[];
|
||||
name?: string;
|
||||
deprecated: boolean;
|
||||
deprecated?: boolean;
|
||||
}
|
||||
|
||||
export interface TTSVoice {
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ export function canReturnHome(stateObj: VacuumEntity): boolean {
|
||||
export interface Segment {
|
||||
id: string;
|
||||
name: string;
|
||||
group?: string;
|
||||
group: string | null;
|
||||
}
|
||||
|
||||
export const getVacuumSegments = (
|
||||
|
||||
+1
-2
@@ -68,8 +68,7 @@ export function canStop(stateObj: ValveEntity): boolean {
|
||||
}
|
||||
|
||||
interface ValveEntityAttributes extends HassEntityAttributeBase {
|
||||
current_position?: number;
|
||||
position?: number;
|
||||
current_position?: number | null;
|
||||
}
|
||||
|
||||
export interface ValveEntity extends HassEntityBase {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { HomeAssistant } from "../types";
|
||||
export interface WakeWord {
|
||||
id: string;
|
||||
name: string;
|
||||
phrase: string | null;
|
||||
}
|
||||
|
||||
export const fetchWakeWordInfo = (hass: HomeAssistant, entity_id: string) =>
|
||||
|
||||
@@ -7,6 +7,7 @@ export enum WaterHeaterEntityFeature {
|
||||
TARGET_TEMPERATURE = 1,
|
||||
OPERATION_MODE = 2,
|
||||
AWAY_MODE = 4,
|
||||
ON_OFF = 8,
|
||||
}
|
||||
|
||||
export const OPERATION_MODES = [
|
||||
@@ -28,8 +29,10 @@ export type WaterHeaterEntity = HassEntityBase & {
|
||||
max_temp: number;
|
||||
current_temperature?: number;
|
||||
temperature?: number;
|
||||
operation_mode: OperationMode;
|
||||
operation_list: OperationMode[];
|
||||
target_temp_high?: number;
|
||||
target_temp_low?: number;
|
||||
operation_mode?: OperationMode;
|
||||
operation_list?: OperationMode[];
|
||||
away_mode?: "on" | "off";
|
||||
};
|
||||
};
|
||||
|
||||
+14
-3
@@ -56,13 +56,19 @@ export interface ForecastAttribute {
|
||||
temperature: number;
|
||||
datetime: string;
|
||||
templow?: number;
|
||||
apparent_temperature?: number;
|
||||
dew_point?: number;
|
||||
precipitation?: number;
|
||||
precipitation_probability?: number;
|
||||
humidity?: number;
|
||||
cloud_coverage?: number;
|
||||
uv_index?: number;
|
||||
condition?: string;
|
||||
is_daytime?: boolean;
|
||||
pressure?: number;
|
||||
wind_speed?: string;
|
||||
wind_bearing?: number | string;
|
||||
wind_gust_speed?: number;
|
||||
wind_speed?: number;
|
||||
}
|
||||
|
||||
export type ForecastPrecipitationType = "amount" | "probability";
|
||||
@@ -77,13 +83,18 @@ export const getForecastPrecipitation = (
|
||||
|
||||
interface WeatherEntityAttributes extends HassEntityAttributeBase {
|
||||
attribution?: string;
|
||||
apparent_temperature?: number;
|
||||
cloud_coverage?: number;
|
||||
dew_point?: number;
|
||||
humidity?: number;
|
||||
forecast?: ForecastAttribute[];
|
||||
is_daytime?: boolean;
|
||||
ozone?: number;
|
||||
pressure?: number;
|
||||
temperature?: number;
|
||||
uv_index?: number;
|
||||
visibility?: number;
|
||||
wind_bearing?: number | string;
|
||||
wind_gust_speed?: number;
|
||||
wind_speed?: number;
|
||||
precipitation_unit: string;
|
||||
pressure_unit: string;
|
||||
@@ -94,7 +105,7 @@ interface WeatherEntityAttributes extends HassEntityAttributeBase {
|
||||
|
||||
export interface ForecastEvent {
|
||||
type: "hourly" | "daily" | "twice_daily";
|
||||
forecast: [ForecastAttribute] | null;
|
||||
forecast: ForecastAttribute[] | null;
|
||||
}
|
||||
|
||||
export interface WeatherEntity extends HassEntityBase {
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface Webhook {
|
||||
domain: string;
|
||||
name: string;
|
||||
local_only: boolean;
|
||||
allowed_methods: string[];
|
||||
}
|
||||
export interface WebhookError {
|
||||
code: number;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user