mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +00:00
20231227.0 (#19157)
This commit is contained in:
commit
9d9e789f4b
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -24,6 +24,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have tried a different browser to see if it is related to my browser.
|
- label: I have tried a different browser to see if it is related to my browser.
|
||||||
required: true
|
required: true
|
||||||
|
- label: I have tried reproducing the issue in [safe mode](https://www.home-assistant.io/blog/2023/11/01/release-202311/#restarting-into-safe-mode) to rule out problems with unsupported custom resources.
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
|
50
.github/labeler.yml
vendored
50
.github/labeler.yml
vendored
@ -1,31 +1,45 @@
|
|||||||
Build:
|
Build:
|
||||||
- build-scripts/**
|
- changed-files:
|
||||||
- .browserslistrc
|
- any-glob-to-any-file:
|
||||||
- gulpfile.js
|
- build-scripts/**
|
||||||
|
- .browserslistrc
|
||||||
|
- gulpfile.js
|
||||||
|
|
||||||
Cast:
|
Cast:
|
||||||
- cast/src/**
|
- changed-files:
|
||||||
- src/cast/**
|
- any-glob-to-any-file:
|
||||||
|
- cast/src/**
|
||||||
|
- src/cast/**
|
||||||
|
|
||||||
Demo:
|
Demo:
|
||||||
- demo/src/**
|
- changed-files:
|
||||||
- src/fake_data/**
|
- any-glob-to-any-file:
|
||||||
|
- demo/src/**
|
||||||
|
- src/fake_data/**
|
||||||
|
|
||||||
Design:
|
Design:
|
||||||
- gallery/src/**
|
- changed-files:
|
||||||
- src/fake_data/**
|
- any-glob-to-any-file:
|
||||||
|
- gallery/src/**
|
||||||
|
- src/fake_data/**
|
||||||
|
|
||||||
Dependencies:
|
Dependencies:
|
||||||
- package.json
|
- changed-files:
|
||||||
- renovate.json
|
- any-glob-to-any-file:
|
||||||
- yarn.lock
|
- package.json
|
||||||
- .yarn/**
|
- renovate.json
|
||||||
- .yarnrc.yml
|
- yarn.lock
|
||||||
- .nvmrc
|
- .yarn/**
|
||||||
|
- .yarnrc.yml
|
||||||
|
- .nvmrc
|
||||||
|
|
||||||
GitHub Actions:
|
GitHub Actions:
|
||||||
- .github/workflows/**
|
- changed-files:
|
||||||
- .github/*.yml
|
- any-glob-to-any-file:
|
||||||
|
- .github/workflows/**
|
||||||
|
- .github/*.yml
|
||||||
|
|
||||||
Supervisor:
|
Supervisor:
|
||||||
- hassio/src/**
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- hassio/src/**
|
||||||
|
4
.github/workflows/cast_deployment.yaml
vendored
4
.github/workflows/cast_deployment.yaml
vendored
@ -26,7 +26,7 @@ jobs:
|
|||||||
ref: dev
|
ref: dev
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.0
|
uses: actions/setup-node@v4.0.1
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@ -62,7 +62,7 @@ jobs:
|
|||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.0
|
uses: actions/setup-node@v4.0.1
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
10
.github/workflows/ci.yaml
vendored
10
.github/workflows/ci.yaml
vendored
@ -26,7 +26,7 @@ jobs:
|
|||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.1
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.0
|
uses: actions/setup-node@v4.0.1
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@ -57,14 +57,14 @@ jobs:
|
|||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.1
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.0
|
uses: actions/setup-node@v4.0.1
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn install --immutable
|
run: yarn install --immutable
|
||||||
- name: Build resources
|
- name: Build resources
|
||||||
run: ./node_modules/.bin/gulp build-translations build-locale-data
|
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: yarn run test
|
run: yarn run test
|
||||||
build:
|
build:
|
||||||
@ -75,7 +75,7 @@ jobs:
|
|||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.1
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.0
|
uses: actions/setup-node@v4.0.1
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@ -99,7 +99,7 @@ jobs:
|
|||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.1
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.0
|
uses: actions/setup-node@v4.0.1
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@ -36,14 +36,14 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v2
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://git.io/JvXDl
|
# 📚 https://git.io/JvXDl
|
||||||
@ -57,4 +57,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v3
|
||||||
|
4
.github/workflows/demo_deployment.yaml
vendored
4
.github/workflows/demo_deployment.yaml
vendored
@ -27,7 +27,7 @@ jobs:
|
|||||||
ref: dev
|
ref: dev
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.0
|
uses: actions/setup-node@v4.0.1
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@ -63,7 +63,7 @@ jobs:
|
|||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.0
|
uses: actions/setup-node@v4.0.1
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
2
.github/workflows/design_deployment.yaml
vendored
2
.github/workflows/design_deployment.yaml
vendored
@ -19,7 +19,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.0
|
uses: actions/setup-node@v4.0.1
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
2
.github/workflows/design_preview.yaml
vendored
2
.github/workflows/design_preview.yaml
vendored
@ -24,7 +24,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.0
|
uses: actions/setup-node@v4.0.1
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
2
.github/workflows/labeler.yaml
vendored
2
.github/workflows/labeler.yaml
vendored
@ -10,6 +10,6 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Apply labels
|
- name: Apply labels
|
||||||
uses: actions/labeler@v4.3.0
|
uses: actions/labeler@v5.0.0
|
||||||
with:
|
with:
|
||||||
sync-labels: true
|
sync-labels: true
|
||||||
|
4
.github/workflows/nightly.yaml
vendored
4
.github/workflows/nightly.yaml
vendored
@ -23,12 +23,12 @@ jobs:
|
|||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.0
|
uses: actions/setup-node@v4.0.1
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
@ -29,12 +29,12 @@ jobs:
|
|||||||
uses: home-assistant/actions/helpers/verify-version@master
|
uses: home-assistant/actions/helpers/verify-version@master
|
||||||
|
|
||||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.0
|
uses: actions/setup-node@v4.0.1
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: 90 days stale policy
|
- name: 90 days stale policy
|
||||||
uses: actions/stale@v8.0.0
|
uses: actions/stale@v9.0.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
days-before-stale: 90
|
days-before-stale: 90
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const env = require("./env.cjs");
|
const env = require("./env.cjs");
|
||||||
const paths = require("./paths.cjs");
|
const paths = require("./paths.cjs");
|
||||||
|
const { dependencies } = require("../package.json");
|
||||||
|
|
||||||
// GitHub base URL to use for production source maps
|
// GitHub base URL to use for production source maps
|
||||||
// Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version
|
// Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version
|
||||||
@ -90,7 +91,7 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
|||||||
"@babel/preset-env",
|
"@babel/preset-env",
|
||||||
{
|
{
|
||||||
useBuiltIns: latestBuild ? false : "usage",
|
useBuiltIns: latestBuild ? false : "usage",
|
||||||
corejs: latestBuild ? false : "3.33",
|
corejs: latestBuild ? false : dependencies["core-js"],
|
||||||
bugfixes: true,
|
bugfixes: true,
|
||||||
shippedProposals: true,
|
shippedProposals: true,
|
||||||
},
|
},
|
||||||
@ -140,7 +141,7 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
|||||||
// Import helpers and regenerator from runtime package
|
// Import helpers and regenerator from runtime package
|
||||||
[
|
[
|
||||||
"@babel/plugin-transform-runtime",
|
"@babel/plugin-transform-runtime",
|
||||||
{ version: require("../package.json").dependencies["@babel/runtime"] },
|
{ version: dependencies["@babel/runtime"] },
|
||||||
],
|
],
|
||||||
// Support some proposals still in TC39 process
|
// Support some proposals still in TC39 process
|
||||||
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
|
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
|
||||||
|
@ -509,7 +509,7 @@ export default {
|
|||||||
away_mode: "on",
|
away_mode: "on",
|
||||||
aux_heat: "off",
|
aux_heat: "off",
|
||||||
unit_of_measurement: "°C",
|
unit_of_measurement: "°C",
|
||||||
friendly_name: "Hvac",
|
friendly_name: "HVAC",
|
||||||
supported_features: 3833,
|
supported_features: 3833,
|
||||||
},
|
},
|
||||||
last_changed: "2018-07-19T10:44:46.200650+00:00",
|
last_changed: "2018-07-19T10:44:46.200650+00:00",
|
||||||
|
@ -35,6 +35,18 @@ const ENTITIES = [
|
|||||||
friendly_name: "Nest",
|
friendly_name: "Nest",
|
||||||
supported_features: 43,
|
supported_features: 43,
|
||||||
}),
|
}),
|
||||||
|
getEntity("climate", "sensibo", "fan_only", {
|
||||||
|
current_temperature: null,
|
||||||
|
temperature: null,
|
||||||
|
min_temp: 0,
|
||||||
|
max_temp: 1,
|
||||||
|
target_temp_step: 1,
|
||||||
|
hvac_modes: ["fan_only", "off"],
|
||||||
|
friendly_name: "Sensibo purifier",
|
||||||
|
fan_modes: ["low", "high"],
|
||||||
|
fan_mode: "low",
|
||||||
|
supported_features: 9,
|
||||||
|
}),
|
||||||
getEntity("climate", "unavailable", "unavailable", {
|
getEntity("climate", "unavailable", "unavailable", {
|
||||||
supported_features: 43,
|
supported_features: 43,
|
||||||
}),
|
}),
|
||||||
@ -57,6 +69,23 @@ const CONFIGS = [
|
|||||||
entity: climate.nest
|
entity: climate.nest
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
heading: "Fan only example",
|
||||||
|
config: `
|
||||||
|
- type: thermostat
|
||||||
|
entity: climate.sensibo
|
||||||
|
features:
|
||||||
|
- type: climate-hvac-modes
|
||||||
|
hvac_modes:
|
||||||
|
- fan_only
|
||||||
|
- 'off'
|
||||||
|
- type: climate-fan-modes
|
||||||
|
style: icons
|
||||||
|
fan_modes:
|
||||||
|
- low
|
||||||
|
- high
|
||||||
|
`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
heading: "Unavailable",
|
heading: "Unavailable",
|
||||||
config: `
|
config: `
|
||||||
|
@ -31,6 +31,21 @@ const ENTITIES = [
|
|||||||
max_temp: 30,
|
max_temp: 30,
|
||||||
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE,
|
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE,
|
||||||
}),
|
}),
|
||||||
|
getEntity("climate", "fan", "fan_only", {
|
||||||
|
friendly_name: "Basic fan",
|
||||||
|
hvac_modes: ["fan_only", "off"],
|
||||||
|
hvac_mode: "fan_only",
|
||||||
|
fan_modes: ["low", "high"],
|
||||||
|
fan_mode: "low",
|
||||||
|
current_temperature: null,
|
||||||
|
temperature: null,
|
||||||
|
min_temp: 0,
|
||||||
|
max_temp: 1,
|
||||||
|
target_temp_step: 1,
|
||||||
|
supported_features:
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE,
|
||||||
|
}),
|
||||||
getEntity("climate", "hvac", "auto", {
|
getEntity("climate", "hvac", "auto", {
|
||||||
friendly_name: "Basic hvac",
|
friendly_name: "Basic hvac",
|
||||||
hvac_modes: ["auto", "off"],
|
hvac_modes: ["auto", "off"],
|
||||||
|
@ -1,12 +1,6 @@
|
|||||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
import {
|
|
||||||
UPDATE_SUPPORT_BACKUP,
|
|
||||||
UPDATE_SUPPORT_PROGRESS,
|
|
||||||
UPDATE_SUPPORT_INSTALL,
|
|
||||||
UPDATE_SUPPORT_RELEASE_NOTES,
|
|
||||||
} from "../../../../src/data/update";
|
|
||||||
import "../../../../src/dialogs/more-info/more-info-content";
|
import "../../../../src/dialogs/more-info/more-info-content";
|
||||||
import { getEntity } from "../../../../src/fake_data/entity";
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
import {
|
import {
|
||||||
@ -15,13 +9,14 @@ import {
|
|||||||
} from "../../../../src/fake_data/provide_hass";
|
} from "../../../../src/fake_data/provide_hass";
|
||||||
import "../../components/demo-more-infos";
|
import "../../components/demo-more-infos";
|
||||||
import { LONG_TEXT } from "../../data/text";
|
import { LONG_TEXT } from "../../data/text";
|
||||||
|
import { UpdateEntityFeature } from "../../../../src/data/update";
|
||||||
|
|
||||||
const base_attributes = {
|
const base_attributes = {
|
||||||
title: "Awesome",
|
title: "Awesome",
|
||||||
installed_version: "1.2.2",
|
installed_version: "1.2.2",
|
||||||
latest_version: "1.2.3",
|
latest_version: "1.2.3",
|
||||||
release_url: "https://home-assistant.io",
|
release_url: "https://home-assistant.io",
|
||||||
supported_features: UPDATE_SUPPORT_INSTALL,
|
supported_features: UpdateEntityFeature.INSTALL,
|
||||||
skipped_version: null,
|
skipped_version: null,
|
||||||
in_progress: false,
|
in_progress: false,
|
||||||
release_summary:
|
release_summary:
|
||||||
@ -61,7 +56,7 @@ const ENTITIES = [
|
|||||||
getEntity("update", "update7", "on", {
|
getEntity("update", "update7", "on", {
|
||||||
...base_attributes,
|
...base_attributes,
|
||||||
supported_features:
|
supported_features:
|
||||||
base_attributes.supported_features + UPDATE_SUPPORT_BACKUP,
|
base_attributes.supported_features + UpdateEntityFeature.BACKUP,
|
||||||
friendly_name: "With backup support",
|
friendly_name: "With backup support",
|
||||||
}),
|
}),
|
||||||
getEntity("update", "update8", "on", {
|
getEntity("update", "update8", "on", {
|
||||||
@ -73,21 +68,21 @@ const ENTITIES = [
|
|||||||
...base_attributes,
|
...base_attributes,
|
||||||
in_progress: 25,
|
in_progress: 25,
|
||||||
supported_features:
|
supported_features:
|
||||||
base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
|
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
|
||||||
friendly_name: "With 25 in_progress",
|
friendly_name: "With 25 in_progress",
|
||||||
}),
|
}),
|
||||||
getEntity("update", "update10", "on", {
|
getEntity("update", "update10", "on", {
|
||||||
...base_attributes,
|
...base_attributes,
|
||||||
in_progress: 50,
|
in_progress: 50,
|
||||||
supported_features:
|
supported_features:
|
||||||
base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
|
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
|
||||||
friendly_name: "With 50 in_progress",
|
friendly_name: "With 50 in_progress",
|
||||||
}),
|
}),
|
||||||
getEntity("update", "update11", "on", {
|
getEntity("update", "update11", "on", {
|
||||||
...base_attributes,
|
...base_attributes,
|
||||||
in_progress: 75,
|
in_progress: 75,
|
||||||
supported_features:
|
supported_features:
|
||||||
base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
|
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
|
||||||
friendly_name: "With 75 in_progress",
|
friendly_name: "With 75 in_progress",
|
||||||
}),
|
}),
|
||||||
getEntity("update", "update12", "unavailable", {
|
getEntity("update", "update12", "unavailable", {
|
||||||
@ -114,19 +109,19 @@ const ENTITIES = [
|
|||||||
...base_attributes,
|
...base_attributes,
|
||||||
friendly_name: "Update with release notes",
|
friendly_name: "Update with release notes",
|
||||||
supported_features:
|
supported_features:
|
||||||
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
|
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES,
|
||||||
}),
|
}),
|
||||||
getEntity("update", "update17", "off", {
|
getEntity("update", "update17", "off", {
|
||||||
...base_attributes,
|
...base_attributes,
|
||||||
friendly_name: "Update with release notes error",
|
friendly_name: "Update with release notes error",
|
||||||
supported_features:
|
supported_features:
|
||||||
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
|
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES,
|
||||||
}),
|
}),
|
||||||
getEntity("update", "update18", "off", {
|
getEntity("update", "update18", "off", {
|
||||||
...base_attributes,
|
...base_attributes,
|
||||||
friendly_name: "Update with release notes loading",
|
friendly_name: "Update with release notes loading",
|
||||||
supported_features:
|
supported_features:
|
||||||
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
|
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES,
|
||||||
}),
|
}),
|
||||||
getEntity("update", "update19", "on", {
|
getEntity("update", "update19", "on", {
|
||||||
...base_attributes,
|
...base_attributes,
|
||||||
@ -142,9 +137,10 @@ const ENTITIES = [
|
|||||||
getEntity("update", "update21", "on", {
|
getEntity("update", "update21", "on", {
|
||||||
...base_attributes,
|
...base_attributes,
|
||||||
in_progress: true,
|
in_progress: true,
|
||||||
friendly_name: "Update with in_progress true and UPDATE_SUPPORT_PROGRESS",
|
friendly_name:
|
||||||
|
"Update with in_progress true and UpdateEntityFeature.PROGRESS",
|
||||||
supported_features:
|
supported_features:
|
||||||
base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
|
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import { customElement, property, state } from "lit/decorators";
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
import "../../../../src/components/ha-circular-progress";
|
import "../../../../src/components/ha-circular-progress";
|
||||||
import "../../../../src/components/ha-markdown";
|
|
||||||
import "../../../../src/components/ha-select";
|
import "../../../../src/components/ha-select";
|
||||||
import {
|
import {
|
||||||
extractApiErrorMessage,
|
extractApiErrorMessage,
|
||||||
|
62
package.json
62
package.json
@ -25,15 +25,15 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.23.5",
|
"@babel/runtime": "7.23.6",
|
||||||
"@braintree/sanitize-url": "6.0.4",
|
"@braintree/sanitize-url": "7.0.0",
|
||||||
"@codemirror/autocomplete": "6.11.1",
|
"@codemirror/autocomplete": "6.11.1",
|
||||||
"@codemirror/commands": "6.3.2",
|
"@codemirror/commands": "6.3.2",
|
||||||
"@codemirror/language": "6.9.3",
|
"@codemirror/language": "6.9.3",
|
||||||
"@codemirror/legacy-modes": "6.3.3",
|
"@codemirror/legacy-modes": "6.3.3",
|
||||||
"@codemirror/search": "6.5.5",
|
"@codemirror/search": "6.5.5",
|
||||||
"@codemirror/state": "6.3.2",
|
"@codemirror/state": "6.3.3",
|
||||||
"@codemirror/view": "6.22.1",
|
"@codemirror/view": "6.22.3",
|
||||||
"@egjs/hammerjs": "2.0.17",
|
"@egjs/hammerjs": "2.0.17",
|
||||||
"@formatjs/intl-datetimeformat": "6.12.0",
|
"@formatjs/intl-datetimeformat": "6.12.0",
|
||||||
"@formatjs/intl-displaynames": "6.6.4",
|
"@formatjs/intl-displaynames": "6.6.4",
|
||||||
@ -80,7 +80,7 @@
|
|||||||
"@material/mwc-top-app-bar": "0.27.0",
|
"@material/mwc-top-app-bar": "0.27.0",
|
||||||
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
||||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/web": "=1.0.1",
|
"@material/web": "=1.1.1",
|
||||||
"@mdi/js": "7.3.67",
|
"@mdi/js": "7.3.67",
|
||||||
"@mdi/svg": "7.3.67",
|
"@mdi/svg": "7.3.67",
|
||||||
"@polymer/paper-input": "3.2.1",
|
"@polymer/paper-input": "3.2.1",
|
||||||
@ -90,8 +90,8 @@
|
|||||||
"@polymer/paper-toast": "3.0.1",
|
"@polymer/paper-toast": "3.0.1",
|
||||||
"@polymer/polymer": "3.5.1",
|
"@polymer/polymer": "3.5.1",
|
||||||
"@thomasloven/round-slider": "0.6.0",
|
"@thomasloven/round-slider": "0.6.0",
|
||||||
"@vaadin/combo-box": "24.2.5",
|
"@vaadin/combo-box": "24.3.2",
|
||||||
"@vaadin/vaadin-themable-mixin": "24.2.5",
|
"@vaadin/vaadin-themable-mixin": "24.3.2",
|
||||||
"@vibrant/color": "3.2.1-alpha.1",
|
"@vibrant/color": "3.2.1-alpha.1",
|
||||||
"@vibrant/core": "3.2.1-alpha.1",
|
"@vibrant/core": "3.2.1-alpha.1",
|
||||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||||
@ -101,16 +101,16 @@
|
|||||||
"app-datepicker": "5.1.1",
|
"app-datepicker": "5.1.1",
|
||||||
"chart.js": "4.4.1",
|
"chart.js": "4.4.1",
|
||||||
"comlink": "4.4.1",
|
"comlink": "4.4.1",
|
||||||
"core-js": "3.33.3",
|
"core-js": "3.34.0",
|
||||||
"cropperjs": "1.6.1",
|
"cropperjs": "1.6.1",
|
||||||
"date-fns": "2.30.0",
|
"date-fns": "2.30.0",
|
||||||
"date-fns-tz": "2.0.0",
|
"date-fns-tz": "2.0.0",
|
||||||
"deep-clone-simple": "1.1.1",
|
"deep-clone-simple": "1.1.1",
|
||||||
"deep-freeze": "0.0.1",
|
"deep-freeze": "0.0.1",
|
||||||
"element-internals-polyfill": "1.3.9",
|
"element-internals-polyfill": "1.3.10",
|
||||||
"fuse.js": "7.0.0",
|
"fuse.js": "7.0.0",
|
||||||
"google-timezones-json": "1.2.0",
|
"google-timezones-json": "1.2.0",
|
||||||
"hls.js": "1.4.12",
|
"hls.js": "1.4.14",
|
||||||
"home-assistant-js-websocket": "9.1.0",
|
"home-assistant-js-websocket": "9.1.0",
|
||||||
"idb-keyval": "6.2.1",
|
"idb-keyval": "6.2.1",
|
||||||
"intl-messageformat": "10.5.8",
|
"intl-messageformat": "10.5.8",
|
||||||
@ -119,7 +119,7 @@
|
|||||||
"leaflet-draw": "1.0.4",
|
"leaflet-draw": "1.0.4",
|
||||||
"lit": "2.8.0",
|
"lit": "2.8.0",
|
||||||
"luxon": "3.4.4",
|
"luxon": "3.4.4",
|
||||||
"marked": "11.0.0",
|
"marked": "11.1.0",
|
||||||
"memoize-one": "6.0.0",
|
"memoize-one": "6.0.0",
|
||||||
"node-vibrant": "3.2.1-alpha.1",
|
"node-vibrant": "3.2.1-alpha.1",
|
||||||
"proxy-polyfill": "0.3.2",
|
"proxy-polyfill": "0.3.2",
|
||||||
@ -138,7 +138,7 @@
|
|||||||
"unfetch": "5.0.0",
|
"unfetch": "5.0.0",
|
||||||
"vis-data": "7.1.9",
|
"vis-data": "7.1.9",
|
||||||
"vis-network": "9.1.9",
|
"vis-network": "9.1.9",
|
||||||
"vue": "2.7.15",
|
"vue": "2.7.16",
|
||||||
"vue2-daterange-picker": "0.6.8",
|
"vue2-daterange-picker": "0.6.8",
|
||||||
"weekstart": "2.0.0",
|
"weekstart": "2.0.0",
|
||||||
"workbox-cacheable-response": "7.0.0",
|
"workbox-cacheable-response": "7.0.0",
|
||||||
@ -150,22 +150,22 @@
|
|||||||
"xss": "1.0.14"
|
"xss": "1.0.14"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.23.5",
|
"@babel/core": "7.23.6",
|
||||||
"@babel/helper-define-polyfill-provider": "0.4.3",
|
"@babel/helper-define-polyfill-provider": "0.4.4",
|
||||||
"@babel/plugin-proposal-decorators": "7.23.5",
|
"@babel/plugin-proposal-decorators": "7.23.6",
|
||||||
"@babel/plugin-transform-runtime": "7.23.4",
|
"@babel/plugin-transform-runtime": "7.23.6",
|
||||||
"@babel/preset-env": "7.23.5",
|
"@babel/preset-env": "7.23.6",
|
||||||
"@babel/preset-typescript": "7.23.3",
|
"@babel/preset-typescript": "7.23.3",
|
||||||
"@bundle-stats/plugin-webpack-filter": "4.8.3",
|
"@bundle-stats/plugin-webpack-filter": "4.8.3",
|
||||||
"@koa/cors": "4.0.0",
|
"@koa/cors": "5.0.0",
|
||||||
"@lokalise/node-api": "12.0.0",
|
"@lokalise/node-api": "12.1.0",
|
||||||
"@octokit/auth-oauth-device": "6.0.1",
|
"@octokit/auth-oauth-device": "6.0.1",
|
||||||
"@octokit/plugin-retry": "6.0.1",
|
"@octokit/plugin-retry": "6.0.1",
|
||||||
"@octokit/rest": "20.0.2",
|
"@octokit/rest": "20.0.2",
|
||||||
"@open-wc/dev-server-hmr": "0.1.4",
|
"@open-wc/dev-server-hmr": "0.1.4",
|
||||||
"@rollup/plugin-babel": "6.0.4",
|
"@rollup/plugin-babel": "6.0.4",
|
||||||
"@rollup/plugin-commonjs": "25.0.7",
|
"@rollup/plugin-commonjs": "25.0.7",
|
||||||
"@rollup/plugin-json": "6.0.1",
|
"@rollup/plugin-json": "6.1.0",
|
||||||
"@rollup/plugin-node-resolve": "15.2.3",
|
"@rollup/plugin-node-resolve": "15.2.3",
|
||||||
"@rollup/plugin-replace": "5.0.5",
|
"@rollup/plugin-replace": "5.0.5",
|
||||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||||
@ -184,22 +184,22 @@
|
|||||||
"@types/tar": "6.1.10",
|
"@types/tar": "6.1.10",
|
||||||
"@types/ua-parser-js": "0.7.39",
|
"@types/ua-parser-js": "0.7.39",
|
||||||
"@types/webspeechapi": "0.0.29",
|
"@types/webspeechapi": "0.0.29",
|
||||||
"@typescript-eslint/eslint-plugin": "6.13.2",
|
"@typescript-eslint/eslint-plugin": "6.15.0",
|
||||||
"@typescript-eslint/parser": "6.13.2",
|
"@typescript-eslint/parser": "6.15.0",
|
||||||
"@web/dev-server": "0.1.38",
|
"@web/dev-server": "0.1.38",
|
||||||
"@web/dev-server-rollup": "0.4.1",
|
"@web/dev-server-rollup": "0.4.1",
|
||||||
"babel-loader": "9.1.3",
|
"babel-loader": "9.1.3",
|
||||||
"babel-plugin-template-html-minifier": "4.1.0",
|
"babel-plugin-template-html-minifier": "4.1.0",
|
||||||
"chai": "4.3.10",
|
"chai": "4.3.10",
|
||||||
"del": "7.1.0",
|
"del": "7.1.0",
|
||||||
"eslint": "8.55.0",
|
"eslint": "8.56.0",
|
||||||
"eslint-config-airbnb-base": "15.0.0",
|
"eslint-config-airbnb-base": "15.0.0",
|
||||||
"eslint-config-airbnb-typescript": "17.1.0",
|
"eslint-config-airbnb-typescript": "17.1.0",
|
||||||
"eslint-config-prettier": "9.1.0",
|
"eslint-config-prettier": "9.1.0",
|
||||||
"eslint-import-resolver-webpack": "0.13.8",
|
"eslint-import-resolver-webpack": "0.13.8",
|
||||||
"eslint-plugin-disable": "2.0.3",
|
"eslint-plugin-disable": "2.0.3",
|
||||||
"eslint-plugin-import": "2.29.0",
|
"eslint-plugin-import": "2.29.1",
|
||||||
"eslint-plugin-lit": "1.10.1",
|
"eslint-plugin-lit": "1.11.0",
|
||||||
"eslint-plugin-lit-a11y": "4.1.1",
|
"eslint-plugin-lit-a11y": "4.1.1",
|
||||||
"eslint-plugin-unused-imports": "3.0.0",
|
"eslint-plugin-unused-imports": "3.0.0",
|
||||||
"eslint-plugin-wc": "2.0.4",
|
"eslint-plugin-wc": "2.0.4",
|
||||||
@ -217,19 +217,19 @@
|
|||||||
"instant-mocha": "1.5.2",
|
"instant-mocha": "1.5.2",
|
||||||
"jszip": "3.10.1",
|
"jszip": "3.10.1",
|
||||||
"lint-staged": "15.2.0",
|
"lint-staged": "15.2.0",
|
||||||
"lit-analyzer": "2.0.1",
|
"lit-analyzer": "2.0.2",
|
||||||
"lodash.template": "4.5.0",
|
"lodash.template": "4.5.0",
|
||||||
"magic-string": "0.30.5",
|
"magic-string": "0.30.5",
|
||||||
"map-stream": "0.0.7",
|
"map-stream": "0.0.7",
|
||||||
"mocha": "10.2.0",
|
"mocha": "10.2.0",
|
||||||
"object-hash": "3.0.0",
|
"object-hash": "3.0.0",
|
||||||
"open": "9.1.0",
|
"open": "10.0.1",
|
||||||
"pinst": "3.0.0",
|
"pinst": "3.0.0",
|
||||||
"prettier": "3.1.0",
|
"prettier": "3.1.1",
|
||||||
"rollup": "2.79.1",
|
"rollup": "2.79.1",
|
||||||
"rollup-plugin-string": "3.0.0",
|
"rollup-plugin-string": "3.0.0",
|
||||||
"rollup-plugin-terser": "7.0.2",
|
"rollup-plugin-terser": "7.0.2",
|
||||||
"rollup-plugin-visualizer": "5.10.0",
|
"rollup-plugin-visualizer": "5.11.0",
|
||||||
"serve-handler": "6.1.5",
|
"serve-handler": "6.1.5",
|
||||||
"sinon": "17.0.1",
|
"sinon": "17.0.1",
|
||||||
"source-map-url": "0.4.1",
|
"source-map-url": "0.4.1",
|
||||||
@ -237,7 +237,7 @@
|
|||||||
"tar": "6.2.0",
|
"tar": "6.2.0",
|
||||||
"terser-webpack-plugin": "5.3.9",
|
"terser-webpack-plugin": "5.3.9",
|
||||||
"ts-lit-plugin": "2.0.1",
|
"ts-lit-plugin": "2.0.1",
|
||||||
"typescript": "5.3.2",
|
"typescript": "5.3.3",
|
||||||
"vinyl-buffer": "1.0.1",
|
"vinyl-buffer": "1.0.1",
|
||||||
"vinyl-source-stream": "2.0.0",
|
"vinyl-source-stream": "2.0.0",
|
||||||
"webpack": "5.89.0",
|
"webpack": "5.89.0",
|
||||||
@ -245,7 +245,7 @@
|
|||||||
"webpack-dev-server": "4.15.1",
|
"webpack-dev-server": "4.15.1",
|
||||||
"webpack-manifest-plugin": "5.0.0",
|
"webpack-manifest-plugin": "5.0.0",
|
||||||
"webpack-stats-plugin": "1.1.3",
|
"webpack-stats-plugin": "1.1.3",
|
||||||
"webpackbar": "5.0.2",
|
"webpackbar": "6.0.0",
|
||||||
"workbox-build": "7.0.0"
|
"workbox-build": "7.0.0"
|
||||||
},
|
},
|
||||||
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20231208.2"
|
version = "20231227.0"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
31
src/common/datetime/localize_date.ts
Normal file
31
src/common/datetime/localize_date.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
|
||||||
|
export const localizeWeekdays = memoizeOne(
|
||||||
|
(language: string, short: boolean): string[] => {
|
||||||
|
const days: string[] = [];
|
||||||
|
const format = new Intl.DateTimeFormat(language, {
|
||||||
|
weekday: short ? "short" : "long",
|
||||||
|
timeZone: "UTC",
|
||||||
|
});
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
const date = new Date(Date.UTC(1970, 0, 1 + 3 + i));
|
||||||
|
days.push(format.format(date));
|
||||||
|
}
|
||||||
|
return days;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const localizeMonths = memoizeOne(
|
||||||
|
(language: string, short: boolean): string[] => {
|
||||||
|
const months: string[] = [];
|
||||||
|
const format = new Intl.DateTimeFormat(language, {
|
||||||
|
month: short ? "short" : "long",
|
||||||
|
timeZone: "UTC",
|
||||||
|
});
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
const date = new Date(Date.UTC(1970, 0 + i, 1));
|
||||||
|
months.push(format.format(date));
|
||||||
|
}
|
||||||
|
return months;
|
||||||
|
}
|
||||||
|
);
|
@ -28,10 +28,12 @@ import {
|
|||||||
mdiLockAlert,
|
mdiLockAlert,
|
||||||
mdiLockClock,
|
mdiLockClock,
|
||||||
mdiLockOpen,
|
mdiLockOpen,
|
||||||
|
mdiMeterGas,
|
||||||
mdiMotionSensor,
|
mdiMotionSensor,
|
||||||
mdiPackage,
|
mdiPackage,
|
||||||
mdiPackageDown,
|
mdiPackageDown,
|
||||||
mdiPackageUp,
|
mdiPackageUp,
|
||||||
|
mdiPipeValve,
|
||||||
mdiPowerPlug,
|
mdiPowerPlug,
|
||||||
mdiPowerPlugOff,
|
mdiPowerPlugOff,
|
||||||
mdiRestart,
|
mdiRestart,
|
||||||
@ -274,6 +276,16 @@ export const domainIconWithoutDefault = (
|
|||||||
: mdiPackageUp
|
: mdiPackageUp
|
||||||
: mdiPackage;
|
: mdiPackage;
|
||||||
|
|
||||||
|
case "valve":
|
||||||
|
switch (stateObj?.attributes.device_class) {
|
||||||
|
case "water":
|
||||||
|
return mdiPipeValve;
|
||||||
|
case "gas":
|
||||||
|
return mdiMeterGas;
|
||||||
|
default:
|
||||||
|
return mdiPipeValve;
|
||||||
|
}
|
||||||
|
|
||||||
case "water_heater":
|
case "water_heater":
|
||||||
return compareState === "off" ? mdiWaterBoilerOff : mdiWaterBoiler;
|
return compareState === "off" ? mdiWaterBoilerOff : mdiWaterBoiler;
|
||||||
|
|
||||||
|
@ -42,6 +42,8 @@ export function stateActive(stateObj: HassEntity, state?: string): boolean {
|
|||||||
return compareState !== "standby";
|
return compareState !== "standby";
|
||||||
case "vacuum":
|
case "vacuum":
|
||||||
return !["idle", "docked", "paused"].includes(compareState);
|
return !["idle", "docked", "paused"].includes(compareState);
|
||||||
|
case "valve":
|
||||||
|
return compareState !== "closed";
|
||||||
case "plant":
|
case "plant":
|
||||||
return compareState === "problem";
|
return compareState === "problem";
|
||||||
case "group":
|
case "group":
|
||||||
|
@ -37,6 +37,7 @@ const STATE_COLORED_DOMAIN = new Set([
|
|||||||
"timer",
|
"timer",
|
||||||
"update",
|
"update",
|
||||||
"vacuum",
|
"vacuum",
|
||||||
|
"valve",
|
||||||
"water_heater",
|
"water_heater",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -101,7 +101,8 @@ export class StatisticsChart extends LitElement {
|
|||||||
changedProps.has("unit") ||
|
changedProps.has("unit") ||
|
||||||
changedProps.has("period") ||
|
changedProps.has("period") ||
|
||||||
changedProps.has("chartType") ||
|
changedProps.has("chartType") ||
|
||||||
changedProps.has("logarithmicScale")
|
changedProps.has("logarithmicScale") ||
|
||||||
|
changedProps.has("hideLegend")
|
||||||
) {
|
) {
|
||||||
this._createOptions();
|
this._createOptions();
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,10 @@ import DateRangePicker from "vue2-daterange-picker";
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import dateRangePickerStyles from "vue2-daterange-picker/dist/vue2-daterange-picker.css";
|
import dateRangePickerStyles from "vue2-daterange-picker/dist/vue2-daterange-picker.css";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import {
|
||||||
|
localizeWeekdays,
|
||||||
|
localizeMonths,
|
||||||
|
} from "../common/datetime/localize_date";
|
||||||
|
|
||||||
// Set the current date to the left picker instead of the right picker because the right is hidden
|
// Set the current date to the left picker instead of the right picker because the right is hidden
|
||||||
const CustomDateRangePicker = Vue.extend({
|
const CustomDateRangePicker = Vue.extend({
|
||||||
@ -63,6 +67,10 @@ const Component = Vue.extend({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
language: {
|
||||||
|
type: String,
|
||||||
|
default: "en",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
render(createElement) {
|
render(createElement) {
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
@ -77,6 +85,8 @@ const Component = Vue.extend({
|
|||||||
ranges: this.ranges ? {} : false,
|
ranges: this.ranges ? {} : false,
|
||||||
"locale-data": {
|
"locale-data": {
|
||||||
firstDay: this.firstDay,
|
firstDay: this.firstDay,
|
||||||
|
daysOfWeek: localizeWeekdays(this.language, true),
|
||||||
|
monthNames: localizeMonths(this.language, false),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
model: {
|
model: {
|
||||||
@ -145,6 +155,8 @@ class DateRangePickerElement extends WrappedElement {
|
|||||||
);
|
);
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
min-width: initial !important;
|
min-width: initial !important;
|
||||||
|
max-height: var(--date-range-picker-max-height);
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
.daterangepicker:before {
|
.daterangepicker:before {
|
||||||
display: none;
|
display: none;
|
||||||
@ -162,7 +174,7 @@ class DateRangePickerElement extends WrappedElement {
|
|||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
outline: none;
|
outline: none;
|
||||||
width: 32px;
|
min-width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
}
|
}
|
||||||
.daterangepicker td.off,
|
.daterangepicker td.off,
|
||||||
@ -238,6 +250,9 @@ class DateRangePickerElement extends WrappedElement {
|
|||||||
}
|
}
|
||||||
.daterangepicker .drp-calendar.left {
|
.daterangepicker .drp-calendar.left {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
width: unset;
|
||||||
|
max-width: unset;
|
||||||
|
min-width: 270px;
|
||||||
}
|
}
|
||||||
.daterangepicker.show-calendar .ranges {
|
.daterangepicker.show-calendar .ranges {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
@ -446,6 +446,7 @@ export class HaAreaPicker extends LitElement {
|
|||||||
cancel: () => {
|
cancel: () => {
|
||||||
this._setValue(undefined);
|
this._setValue(undefined);
|
||||||
this._suggestion = undefined;
|
this._suggestion = undefined;
|
||||||
|
this.comboBox.setInputValue("");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,7 @@ export class HaBigNumber extends LitElement {
|
|||||||
.value .decimal {
|
.value .decimal {
|
||||||
font-size: 0.42em;
|
font-size: 0.42em;
|
||||||
line-height: 1.33;
|
line-height: 1.33;
|
||||||
|
min-height: 1.33em;
|
||||||
}
|
}
|
||||||
.value .unit {
|
.value .unit {
|
||||||
font-size: 0.33em;
|
font-size: 0.33em;
|
||||||
|
@ -3,9 +3,15 @@ import { CheckListItemBase } from "@material/mwc-list/mwc-check-list-item-base";
|
|||||||
import { styles as controlStyles } from "@material/mwc-list/mwc-control-list-item.css";
|
import { styles as controlStyles } from "@material/mwc-list/mwc-control-list-item.css";
|
||||||
import { styles } from "@material/mwc-list/mwc-list-item.css";
|
import { styles } from "@material/mwc-list/mwc-list-item.css";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
|
||||||
@customElement("ha-check-list-item")
|
@customElement("ha-check-list-item")
|
||||||
export class HaCheckListItem extends CheckListItemBase {
|
export class HaCheckListItem extends CheckListItemBase {
|
||||||
|
async onChange(event) {
|
||||||
|
super.onChange(event);
|
||||||
|
fireEvent(this, event.type);
|
||||||
|
}
|
||||||
|
|
||||||
static override styles = [
|
static override styles = [
|
||||||
styles,
|
styles,
|
||||||
controlStyles,
|
controlStyles,
|
||||||
@ -22,6 +28,15 @@ export class HaCheckListItem extends CheckListItemBase {
|
|||||||
margin-inline-start: 0px;
|
margin-inline-start: 0px;
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
|
.mdc-deprecated-list-item__meta {
|
||||||
|
flex-shrink: 0;
|
||||||
|
direction: var(--direction);
|
||||||
|
margin-inline-start: auto;
|
||||||
|
margin-inline-end: 0;
|
||||||
|
}
|
||||||
|
.mdc-deprecated-list-item__graphic {
|
||||||
|
margin-top: var(--check-list-item-graphic-margin-top);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -180,7 +180,7 @@ export class HaComboBox extends LitElement {
|
|||||||
></div>`}
|
></div>`}
|
||||||
.icon=${this.icon}
|
.icon=${this.icon}
|
||||||
.invalid=${this.invalid}
|
.invalid=${this.invalid}
|
||||||
helper=${ifDefined(this.helper)}
|
.helper=${this.helper}
|
||||||
helperPersistent
|
helperPersistent
|
||||||
>
|
>
|
||||||
<slot name="icon" slot="leadingIcon"></slot>
|
<slot name="icon" slot="leadingIcon"></slot>
|
||||||
|
@ -253,6 +253,7 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
opening-direction=${this.openingDirection ||
|
opening-direction=${this.openingDirection ||
|
||||||
this._calcedOpeningDirection}
|
this._calcedOpeningDirection}
|
||||||
first-day=${firstWeekdayIndex(this.hass.locale)}
|
first-day=${firstWeekdayIndex(this.hass.locale)}
|
||||||
|
language=${this.hass.locale.language}
|
||||||
>
|
>
|
||||||
<div slot="input" class="date-range-inputs" @click=${this._handleClick}>
|
<div slot="input" class="date-range-inputs" @click=${this._handleClick}>
|
||||||
${!this.minimal
|
${!this.minimal
|
||||||
|
@ -13,13 +13,15 @@ export const createCloseHeading = (
|
|||||||
hass: HomeAssistant | undefined,
|
hass: HomeAssistant | undefined,
|
||||||
title: string | TemplateResult
|
title: string | TemplateResult
|
||||||
) => html`
|
) => html`
|
||||||
<div class="header_title">${title}</div>
|
<div class="header_title">
|
||||||
<ha-icon-button
|
<span>${title}</span>
|
||||||
.label=${hass?.localize("ui.dialogs.generic.close") ?? "Close"}
|
<ha-icon-button
|
||||||
.path=${mdiClose}
|
.label=${hass?.localize("ui.dialogs.generic.close") ?? "Close"}
|
||||||
dialogAction="close"
|
.path=${mdiClose}
|
||||||
class="header_button"
|
dialogAction="close"
|
||||||
></ha-icon-button>
|
class="header_button"
|
||||||
|
></ha-icon-button>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@customElement("ha-dialog")
|
@customElement("ha-dialog")
|
||||||
@ -94,15 +96,12 @@ export class HaDialog extends DialogBase {
|
|||||||
}
|
}
|
||||||
.mdc-dialog__title {
|
.mdc-dialog__title {
|
||||||
padding: 24px 24px 0 24px;
|
padding: 24px 24px 0 24px;
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
.mdc-dialog__actions {
|
.mdc-dialog__actions {
|
||||||
padding: 12px 24px 12px 24px;
|
padding: 12px 24px 12px 24px;
|
||||||
}
|
}
|
||||||
.mdc-dialog__title::before {
|
.mdc-dialog__title::before {
|
||||||
display: block;
|
content: unset;
|
||||||
height: 0px;
|
|
||||||
}
|
}
|
||||||
.mdc-dialog .mdc-dialog__content {
|
.mdc-dialog .mdc-dialog__content {
|
||||||
position: var(--dialog-content-position, relative);
|
position: var(--dialog-content-position, relative);
|
||||||
@ -126,19 +125,26 @@ export class HaDialog extends DialogBase {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
.header_title {
|
.header_title {
|
||||||
margin-right: 32px;
|
position: relative;
|
||||||
margin-inline-end: 32px;
|
padding-right: 40px;
|
||||||
margin-inline-start: initial;
|
padding-inline-end: 40px;
|
||||||
|
padding-inline-start: initial;
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
|
.header_title span {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
.header_button {
|
.header_button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 16px;
|
right: -8px;
|
||||||
top: 14px;
|
top: -8px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
inset-inline-start: initial;
|
inset-inline-start: initial;
|
||||||
inset-inline-end: 16px;
|
inset-inline-end: -8px;
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
.dialog-actions {
|
.dialog-actions {
|
||||||
|
@ -36,17 +36,24 @@ export class HaListItem extends ListItemBase {
|
|||||||
--mdc-list-item-graphic-margin,
|
--mdc-list-item-graphic-margin,
|
||||||
16px
|
16px
|
||||||
) !important;
|
) !important;
|
||||||
direction: var(--direction);
|
direction: var(--direction) !important;
|
||||||
}
|
}
|
||||||
span.material-icons:last-of-type {
|
span.material-icons:last-of-type {
|
||||||
margin-inline-start: auto !important;
|
margin-inline-start: auto !important;
|
||||||
margin-inline-end: 0px !important;
|
margin-inline-end: 0px !important;
|
||||||
direction: var(--direction);
|
direction: var(--direction) !important;
|
||||||
}
|
}
|
||||||
.mdc-deprecated-list-item__meta {
|
.mdc-deprecated-list-item__meta {
|
||||||
display: var(--mdc-list-item-meta-display);
|
display: var(--mdc-list-item-meta-display);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
:host([graphic="icon"]:not([twoline]))
|
||||||
|
.mdc-deprecated-list-item__graphic {
|
||||||
|
margin-inline-end: var(
|
||||||
|
--mdc-list-item-graphic-margin,
|
||||||
|
20px
|
||||||
|
) !important;
|
||||||
|
}
|
||||||
:host([multiline-secondary]) {
|
:host([multiline-secondary]) {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
@ -78,6 +85,15 @@ export class HaListItem extends ListItemBase {
|
|||||||
pointer-events: unset;
|
pointer-events: unset;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
// safari workaround - must be explicit
|
||||||
|
document.dir === "rtl"
|
||||||
|
? css`
|
||||||
|
span.material-icons:first-of-type,
|
||||||
|
span.material-icons:last-of-type {
|
||||||
|
direction: rtl !important;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
: css``,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,6 +95,15 @@ class HaMarkdownElement extends ReactiveElement {
|
|||||||
}
|
}
|
||||||
node.firstElementChild!.replaceWith(alertNote);
|
node.firstElementChild!.replaceWith(alertNote);
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
node instanceof HTMLElement &&
|
||||||
|
["ha-alert", "ha-qr-code", "ha-icon", "ha-svg-icon"].includes(
|
||||||
|
node.localName
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
import(
|
||||||
|
/* webpackInclude: /(ha-alert)|(ha-qr-code)|(ha-icon)|(ha-svg-icon)/ */ `./${node.localName}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,6 @@ import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import "./ha-markdown-element";
|
import "./ha-markdown-element";
|
||||||
|
|
||||||
// Import components that are allwoed to be defined.
|
|
||||||
import "./ha-alert";
|
|
||||||
import "./ha-icon";
|
|
||||||
import "./ha-svg-icon";
|
|
||||||
|
|
||||||
@customElement("ha-markdown")
|
@customElement("ha-markdown")
|
||||||
export class HaMarkdown extends LitElement {
|
export class HaMarkdown extends LitElement {
|
||||||
@property() public content?;
|
@property() public content?;
|
||||||
|
114
src/components/ha-qr-code.ts
Normal file
114
src/components/ha-qr-code.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import { LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||||
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import QRCode from "qrcode";
|
||||||
|
|
||||||
|
@customElement("ha-qr-code")
|
||||||
|
export class HaQrCode extends LitElement {
|
||||||
|
@property() public data?: string;
|
||||||
|
|
||||||
|
@property({ attribute: "error-correction-level" })
|
||||||
|
public errorCorrectionLevel: "low" | "medium" | "quartile" | "high" =
|
||||||
|
"medium";
|
||||||
|
|
||||||
|
@property({ type: Number })
|
||||||
|
public width = 4;
|
||||||
|
|
||||||
|
@property({ type: Number })
|
||||||
|
public scale = 4;
|
||||||
|
|
||||||
|
@property({ type: Number })
|
||||||
|
public margin = 4;
|
||||||
|
|
||||||
|
@property({ type: Number }) public maskPattern?:
|
||||||
|
| 0
|
||||||
|
| 1
|
||||||
|
| 2
|
||||||
|
| 3
|
||||||
|
| 4
|
||||||
|
| 5
|
||||||
|
| 6
|
||||||
|
| 7;
|
||||||
|
|
||||||
|
@property({ attribute: "center-image" }) public centerImage?: string;
|
||||||
|
|
||||||
|
@state() private _error?: string;
|
||||||
|
|
||||||
|
@query("canvas") private _canvas?: HTMLCanvasElement;
|
||||||
|
|
||||||
|
protected willUpdate(changedProperties: PropertyValues): void {
|
||||||
|
super.willUpdate(changedProperties);
|
||||||
|
if (
|
||||||
|
(changedProperties.has("data") ||
|
||||||
|
changedProperties.has("scale") ||
|
||||||
|
changedProperties.has("width") ||
|
||||||
|
changedProperties.has("margin") ||
|
||||||
|
changedProperties.has("maskPattern") ||
|
||||||
|
changedProperties.has("errorCorrectionLevel")) &&
|
||||||
|
this._error
|
||||||
|
) {
|
||||||
|
this._error = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updated(changedProperties: PropertyValues) {
|
||||||
|
const canvas = this._canvas;
|
||||||
|
if (
|
||||||
|
canvas &&
|
||||||
|
this.data &&
|
||||||
|
(changedProperties.has("data") ||
|
||||||
|
changedProperties.has("scale") ||
|
||||||
|
changedProperties.has("width") ||
|
||||||
|
changedProperties.has("margin") ||
|
||||||
|
changedProperties.has("maskPattern") ||
|
||||||
|
changedProperties.has("errorCorrectionLevel") ||
|
||||||
|
changedProperties.has("centerImage"))
|
||||||
|
) {
|
||||||
|
const computedStyles = getComputedStyle(this);
|
||||||
|
|
||||||
|
QRCode.toCanvas(canvas, this.data, {
|
||||||
|
errorCorrectionLevel: this.errorCorrectionLevel,
|
||||||
|
width: this.width,
|
||||||
|
scale: this.scale,
|
||||||
|
margin: this.margin,
|
||||||
|
maskPattern: this.maskPattern,
|
||||||
|
color: {
|
||||||
|
light: computedStyles.getPropertyValue("--card-background-color"),
|
||||||
|
dark: computedStyles.getPropertyValue("--primary-text-color"),
|
||||||
|
},
|
||||||
|
}).catch((err) => {
|
||||||
|
this._error = err.message;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.centerImage) {
|
||||||
|
const context = this._canvas!.getContext("2d");
|
||||||
|
const imageObj = new Image();
|
||||||
|
imageObj.src = this.centerImage;
|
||||||
|
imageObj.onload = () => {
|
||||||
|
context?.drawImage(
|
||||||
|
imageObj,
|
||||||
|
canvas.width * 0.375,
|
||||||
|
canvas.height * 0.375,
|
||||||
|
canvas.width / 4,
|
||||||
|
canvas.height / 4
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.data) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
if (this._error) {
|
||||||
|
return html`<ha-alert alert-type="error">${this._error}</ha-alert>`;
|
||||||
|
}
|
||||||
|
return html`<canvas></canvas>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
@ -43,6 +43,22 @@ export class HaNumberSelector extends LitElement {
|
|||||||
this.selector.number?.min === undefined ||
|
this.selector.number?.min === undefined ||
|
||||||
this.selector.number?.max === undefined;
|
this.selector.number?.max === undefined;
|
||||||
|
|
||||||
|
let sliderStep;
|
||||||
|
|
||||||
|
if (!isBox) {
|
||||||
|
sliderStep = this.selector.number!.step ?? 1;
|
||||||
|
if (sliderStep === "any") {
|
||||||
|
sliderStep = 1;
|
||||||
|
// divide the range of the slider by 100 steps
|
||||||
|
const step =
|
||||||
|
(this.selector.number!.max! - this.selector.number!.min!) / 100;
|
||||||
|
// biggest step size is 1, round the step size to a division of 1
|
||||||
|
while (sliderStep > step) {
|
||||||
|
sliderStep /= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="input">
|
<div class="input">
|
||||||
${!isBox
|
${!isBox
|
||||||
@ -52,12 +68,10 @@ export class HaNumberSelector extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
<ha-slider
|
<ha-slider
|
||||||
labeled
|
labeled
|
||||||
.min=${this.selector.number?.min}
|
.min=${this.selector.number!.min}
|
||||||
.max=${this.selector.number?.max}
|
.max=${this.selector.number!.max}
|
||||||
.value=${this.value ?? ""}
|
.value=${this.value ?? ""}
|
||||||
.step=${this.selector.number?.step === "any"
|
.step=${sliderStep}
|
||||||
? undefined
|
|
||||||
: this.selector.number?.step ?? 1}
|
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
@change=${this._handleSliderChange}
|
@change=${this._handleSliderChange}
|
||||||
|
@ -70,15 +70,15 @@ const SELECTOR_SCHEMAS = {
|
|||||||
number: [
|
number: [
|
||||||
{
|
{
|
||||||
name: "min",
|
name: "min",
|
||||||
selector: { number: { mode: "box" } },
|
selector: { number: { mode: "box", step: "any" } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "max",
|
name: "max",
|
||||||
selector: { number: { mode: "box" } },
|
selector: { number: { mode: "box", step: "any" } },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "step",
|
name: "step",
|
||||||
selector: { number: { mode: "box" } },
|
selector: { number: { mode: "box", step: "any" } },
|
||||||
},
|
},
|
||||||
] as const,
|
] as const,
|
||||||
object: [] as const,
|
object: [] as const,
|
||||||
|
@ -522,6 +522,14 @@ export class HaServiceControl extends LitElement {
|
|||||||
defaultValue = field.selector.constant?.value;
|
defaultValue = field.selector.constant?.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
defaultValue == null &&
|
||||||
|
field?.selector &&
|
||||||
|
"boolean" in field.selector
|
||||||
|
) {
|
||||||
|
defaultValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (defaultValue != null) {
|
if (defaultValue != null) {
|
||||||
data = {
|
data = {
|
||||||
...this._value?.data,
|
...this._value?.data,
|
||||||
@ -597,9 +605,9 @@ export class HaServiceControl extends LitElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
target = {
|
target = {
|
||||||
entity_id: targetEntities,
|
...(targetEntities.length ? { entity_id: targetEntities } : {}),
|
||||||
device_id: targetDevices,
|
...(targetDevices.length ? { device_id: targetDevices } : {}),
|
||||||
area_id: targetAreas,
|
...(targetAreas.length ? { area_id: targetAreas } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,12 @@ export class HaSettingsRow extends LitElement {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.body {
|
.body {
|
||||||
padding: 8px 16px 8px 0;
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-inline-start: 0;
|
||||||
|
padding-right: 16x;
|
||||||
|
padding-inline-end: 16px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: var(--layout-vertical_-_display);
|
display: var(--layout-vertical_-_display);
|
||||||
flex-direction: var(--layout-vertical_-_flex-direction);
|
flex-direction: var(--layout-vertical_-_flex-direction);
|
||||||
|
@ -11,6 +11,7 @@ export class HaSlider extends MdSlider {
|
|||||||
:host {
|
:host {
|
||||||
--md-sys-color-primary: var(--primary-color);
|
--md-sys-color-primary: var(--primary-color);
|
||||||
--md-sys-color-outline: var(--outline-color);
|
--md-sys-color-outline: var(--outline-color);
|
||||||
|
--md-sys-color-on-surface: var(--primary-text-color);
|
||||||
--md-slider-handle-width: 14px;
|
--md-slider-handle-width: 14px;
|
||||||
--md-slider-handle-height: 14px;
|
--md-slider-handle-height: 14px;
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
|
@ -280,6 +280,8 @@ export abstract class TopAppBarBaseBase extends BaseElement {
|
|||||||
}
|
}
|
||||||
#title {
|
#title {
|
||||||
border-right: 1px solid rgba(255, 255, 255, 0.12);
|
border-right: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
border-inline-end: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
border-inline-start: initial;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
flex: 0 0 var(--sidepane-width, 250px);
|
flex: 0 0 var(--sidepane-width, 250px);
|
||||||
width: var(--sidepane-width, 250px);
|
width: var(--sidepane-width, 250px);
|
||||||
@ -290,6 +292,8 @@ export abstract class TopAppBarBaseBase extends BaseElement {
|
|||||||
}
|
}
|
||||||
.pane {
|
.pane {
|
||||||
border-right: 1px solid var(--divider-color);
|
border-right: 1px solid var(--divider-color);
|
||||||
|
border-inline-end: 1px solid var(--divider-color);
|
||||||
|
border-inline-start: initial;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 0 0 var(--sidepane-width, 250px);
|
flex: 0 0 var(--sidepane-width, 250px);
|
||||||
|
102
src/components/ha-valve-controls.ts
Normal file
102
src/components/ha-valve-controls.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { mdiStop, mdiValveClosed, mdiValveOpen } from "@mdi/js";
|
||||||
|
import { CSSResultGroup, LitElement, html, css, nothing } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import { supportsFeature } from "../common/entity/supports-feature";
|
||||||
|
import {
|
||||||
|
ValveEntity,
|
||||||
|
ValveEntityFeature,
|
||||||
|
canClose,
|
||||||
|
canOpen,
|
||||||
|
canStop,
|
||||||
|
} from "../data/valve";
|
||||||
|
import type { HomeAssistant } from "../types";
|
||||||
|
import "./ha-icon-button";
|
||||||
|
|
||||||
|
@customElement("ha-valve-controls")
|
||||||
|
class HaValveControls extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public stateObj!: ValveEntity;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this.stateObj) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="state">
|
||||||
|
<ha-icon-button
|
||||||
|
class=${classMap({
|
||||||
|
hidden: !supportsFeature(this.stateObj, ValveEntityFeature.OPEN),
|
||||||
|
})}
|
||||||
|
.label=${this.hass.localize("ui.card.valve.open_valve")}
|
||||||
|
@click=${this._onOpenTap}
|
||||||
|
.disabled=${!canOpen(this.stateObj)}
|
||||||
|
.path=${mdiValveOpen}
|
||||||
|
>
|
||||||
|
</ha-icon-button>
|
||||||
|
<ha-icon-button
|
||||||
|
class=${classMap({
|
||||||
|
hidden: !supportsFeature(this.stateObj, ValveEntityFeature.STOP),
|
||||||
|
})}
|
||||||
|
.label=${this.hass.localize("ui.card.valve.stop_valve")}
|
||||||
|
@click=${this._onStopTap}
|
||||||
|
.disabled=${!canStop(this.stateObj)}
|
||||||
|
.path=${mdiStop}
|
||||||
|
></ha-icon-button>
|
||||||
|
<ha-icon-button
|
||||||
|
class=${classMap({
|
||||||
|
hidden: !supportsFeature(this.stateObj, ValveEntityFeature.CLOSE),
|
||||||
|
})}
|
||||||
|
.label=${this.hass.localize("ui.card.valve.close_valve")}
|
||||||
|
@click=${this._onCloseTap}
|
||||||
|
.disabled=${!canClose(this.stateObj)}
|
||||||
|
.path=${mdiValveClosed}
|
||||||
|
>
|
||||||
|
</ha-icon-button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onOpenTap(ev): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.hass.callService("valve", "open_valve", {
|
||||||
|
entity_id: this.stateObj.entity_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onCloseTap(ev): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.hass.callService("valve", "close_valve", {
|
||||||
|
entity_id: this.stateObj.entity_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onStopTap(ev): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.hass.callService("valve", "stop_valve", {
|
||||||
|
entity_id: this.stateObj.entity_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.state {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.hidden {
|
||||||
|
visibility: hidden !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-valve-controls": HaValveControls;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,12 @@
|
|||||||
import { dump } from "js-yaml";
|
import { dump } from "js-yaml";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
nothing,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
||||||
@ -18,6 +25,12 @@ import { traceTabStyles } from "./trace-tab-styles";
|
|||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import type { NodeInfo } from "./hat-script-graph";
|
import type { NodeInfo } from "./hat-script-graph";
|
||||||
|
|
||||||
|
const TRACE_PATH_TABS = [
|
||||||
|
"step_config",
|
||||||
|
"changed_variables",
|
||||||
|
"logbook",
|
||||||
|
] as const;
|
||||||
|
|
||||||
@customElement("ha-trace-path-details")
|
@customElement("ha-trace-path-details")
|
||||||
export class HaTracePathDetails extends LitElement {
|
export class HaTracePathDetails extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -34,7 +47,7 @@ export class HaTracePathDetails extends LitElement {
|
|||||||
|
|
||||||
@property() public trackedNodes!: Record<string, any>;
|
@property() public trackedNodes!: Record<string, any>;
|
||||||
|
|
||||||
@state() private _view: "config" | "changed_variables" | "logbook" = "config";
|
@state() private _view: (typeof TRACE_PATH_TABS)[number] = "step_config";
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
@ -43,23 +56,21 @@ export class HaTracePathDetails extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tabs top">
|
<div class="tabs top">
|
||||||
${[
|
${TRACE_PATH_TABS.map(
|
||||||
["config", "Step Config"],
|
(view) => html`
|
||||||
["changed_variables", "Changed Variables"],
|
|
||||||
["logbook", "Related logbook entries"],
|
|
||||||
].map(
|
|
||||||
([view, label]) => html`
|
|
||||||
<button
|
<button
|
||||||
.view=${view}
|
.view=${view}
|
||||||
class=${classMap({ active: this._view === view })}
|
class=${classMap({ active: this._view === view })}
|
||||||
@click=${this._showTab}
|
@click=${this._showTab}
|
||||||
>
|
>
|
||||||
${label}
|
${this.hass!.localize(
|
||||||
|
`ui.panel.config.automation.trace.tabs.${view}`
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
${this._view === "config"
|
${this._view === "step_config"
|
||||||
? this._renderSelectedConfig()
|
? this._renderSelectedConfig()
|
||||||
: this._view === "changed_variables"
|
: this._view === "changed_variables"
|
||||||
? this._renderChangedVars()
|
? this._renderChangedVars()
|
||||||
@ -71,7 +82,9 @@ export class HaTracePathDetails extends LitElement {
|
|||||||
const paths = this.trace.trace;
|
const paths = this.trace.trace;
|
||||||
|
|
||||||
if (!this.selected?.path) {
|
if (!this.selected?.path) {
|
||||||
return "Select a node on the left for more information.";
|
return this.hass!.localize(
|
||||||
|
"ui.panel.config.automation.trace.path.choose"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK: default choice node is not part of paths. We filter them out here by checking parent.
|
// HACK: default choice node is not part of paths. We filter them out here by checking parent.
|
||||||
@ -82,12 +95,16 @@ export class HaTracePathDetails extends LitElement {
|
|||||||
] as ChooseActionTraceStep[];
|
] as ChooseActionTraceStep[];
|
||||||
|
|
||||||
if (parentTraceInfo && parentTraceInfo[0]?.result?.choice === "default") {
|
if (parentTraceInfo && parentTraceInfo[0]?.result?.choice === "default") {
|
||||||
return "The default action was executed because no options matched.";
|
return this.hass!.localize(
|
||||||
|
"ui.panel.config.automation.trace.path.default_action_executed"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(this.selected.path in paths)) {
|
if (!(this.selected.path in paths)) {
|
||||||
return "This node was not executed and so no further trace information is available.";
|
return this.hass!.localize(
|
||||||
|
"ui.panel.config.automation.trace.path.no_further_execution"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts: TemplateResult[][] = [];
|
const parts: TemplateResult[][] = [];
|
||||||
@ -115,29 +132,53 @@ export class HaTracePathDetails extends LitElement {
|
|||||||
trace as any;
|
trace as any;
|
||||||
|
|
||||||
if (result?.enabled === false) {
|
if (result?.enabled === false) {
|
||||||
return html`This node was disabled and skipped during execution so
|
return html`${this.hass!.localize(
|
||||||
no further trace information is available.`;
|
"ui.panel.config.automation.trace.path.disabled_node"
|
||||||
|
)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${curPath === this.selected.path
|
${curPath === this.selected.path
|
||||||
? ""
|
? ""
|
||||||
: html`<h2>${curPath.substr(this.selected.path.length + 1)}</h2>`}
|
: html`<h2>
|
||||||
${data.length === 1 ? "" : html`<h3>Iteration ${idx + 1}</h3>`}
|
${curPath.substring(this.selected.path.length + 1)}
|
||||||
Executed:
|
</h2>`}
|
||||||
${formatDateTimeWithSeconds(
|
${data.length === 1
|
||||||
new Date(timestamp),
|
? nothing
|
||||||
this.hass.locale,
|
: html`<h3>
|
||||||
this.hass.config
|
${this.hass!.localize(
|
||||||
)}<br />
|
"ui.panel.config.automation.trace.path.iteration",
|
||||||
|
{ number: idx + 1 }
|
||||||
|
)}
|
||||||
|
</h3>`}
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.automation.trace.path.executed",
|
||||||
|
{
|
||||||
|
time: formatDateTimeWithSeconds(
|
||||||
|
new Date(timestamp),
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.config
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
<br />
|
||||||
${result
|
${result
|
||||||
? html`Result:
|
? html`${this.hass!.localize(
|
||||||
|
"ui.panel.config.automation.trace.path.result"
|
||||||
|
)}
|
||||||
<pre>${dump(result)}</pre>`
|
<pre>${dump(result)}</pre>`
|
||||||
: error
|
: error
|
||||||
? html`<div class="error">Error: ${error}</div>`
|
? html`<div class="error">
|
||||||
: ""}
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.automation.trace.path.error",
|
||||||
|
{
|
||||||
|
error: error,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
${Object.keys(rest).length === 0
|
${Object.keys(rest).length === 0
|
||||||
? ""
|
? nothing
|
||||||
: html`<pre>${dump(rest)}</pre>`}
|
: html`<pre>${dump(rest)}</pre>`}
|
||||||
`;
|
`;
|
||||||
})
|
})
|
||||||
@ -149,16 +190,18 @@ export class HaTracePathDetails extends LitElement {
|
|||||||
|
|
||||||
private _renderSelectedConfig() {
|
private _renderSelectedConfig() {
|
||||||
if (!this.selected?.path) {
|
if (!this.selected?.path) {
|
||||||
return "";
|
return nothing;
|
||||||
}
|
}
|
||||||
const config = getDataFromPath(this.trace!.config, this.selected.path);
|
const config = getDataFromPath(this.trace!.config, this.selected.path);
|
||||||
return config
|
return config
|
||||||
? html`<ha-code-editor
|
? html`<ha-code-editor
|
||||||
.value=${dump(config).trimRight()}
|
.value=${dump(config).trimEnd()}
|
||||||
readOnly
|
readOnly
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
></ha-code-editor>`
|
></ha-code-editor>`
|
||||||
: "Unable to find config";
|
: this.hass!.localize(
|
||||||
|
"ui.panel.config.automation.trace.path.unable_to_find_config"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderChangedVars() {
|
private _renderChangedVars() {
|
||||||
@ -169,10 +212,19 @@ export class HaTracePathDetails extends LitElement {
|
|||||||
<div class="padded-box">
|
<div class="padded-box">
|
||||||
${data.map(
|
${data.map(
|
||||||
(trace, idx) => html`
|
(trace, idx) => html`
|
||||||
${idx > 0 ? html`<p>Iteration ${idx + 1}</p>` : ""}
|
${idx > 0
|
||||||
|
? html`<p>
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.automation.trace.path.iteration",
|
||||||
|
{ number: idx + 1 }
|
||||||
|
)}
|
||||||
|
</p>`
|
||||||
|
: ""}
|
||||||
${Object.keys(trace.changed_variables || {}).length === 0
|
${Object.keys(trace.changed_variables || {}).length === 0
|
||||||
? "No variables changed"
|
? this.hass!.localize(
|
||||||
: html`<pre>${dump(trace.changed_variables).trimRight()}</pre>`}
|
"ui.panel.config.automation.trace.path.no_variables_changed"
|
||||||
|
)
|
||||||
|
: html`<pre>${dump(trace.changed_variables).trimEnd()}</pre>`}
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -186,7 +238,11 @@ export class HaTracePathDetails extends LitElement {
|
|||||||
const index = trackedPaths.indexOf(this.selected.path);
|
const index = trackedPaths.indexOf(this.selected.path);
|
||||||
|
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
return html`<div class="padded-box">Node not tracked.</div>`;
|
return html`<div class="padded-box">
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.automation.trace.path.node_not_tracked"
|
||||||
|
)}
|
||||||
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let entries: LogbookEntry[];
|
let entries: LogbookEntry[];
|
||||||
@ -234,7 +290,9 @@ export class HaTracePathDetails extends LitElement {
|
|||||||
<hat-logbook-note .domain=${this.trace.domain}></hat-logbook-note>
|
<hat-logbook-note .domain=${this.trace.domain}></hat-logbook-note>
|
||||||
`
|
`
|
||||||
: html`<div class="padded-box">
|
: html`<div class="padded-box">
|
||||||
No Logbook entries found for this step.
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.automation.trace.path.no_logbook_entries"
|
||||||
|
)}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,10 +125,6 @@ export class HatGraphNode extends LitElement {
|
|||||||
:host([notEnabled]:hover) circle {
|
:host([notEnabled]:hover) circle {
|
||||||
--stroke-clr: var(--disabled-hover-clr);
|
--stroke-clr: var(--disabled-hover-clr);
|
||||||
}
|
}
|
||||||
svg {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
circle,
|
circle,
|
||||||
path.connector {
|
path.connector {
|
||||||
stroke: var(--stroke-clr);
|
stroke: var(--stroke-clr);
|
||||||
|
@ -5,6 +5,8 @@ import {
|
|||||||
mdiCallSplit,
|
mdiCallSplit,
|
||||||
mdiCodeBraces,
|
mdiCodeBraces,
|
||||||
mdiDevices,
|
mdiDevices,
|
||||||
|
mdiDotsHorizontal,
|
||||||
|
mdiExcavator,
|
||||||
mdiGestureDoubleTap,
|
mdiGestureDoubleTap,
|
||||||
mdiHandBackRight,
|
mdiHandBackRight,
|
||||||
mdiPalette,
|
mdiPalette,
|
||||||
@ -13,10 +15,12 @@ import {
|
|||||||
mdiRoomService,
|
mdiRoomService,
|
||||||
mdiShuffleDisabled,
|
mdiShuffleDisabled,
|
||||||
mdiTimerOutline,
|
mdiTimerOutline,
|
||||||
|
mdiTools,
|
||||||
mdiTrafficLight,
|
mdiTrafficLight,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
|
import { AutomationElementGroup } from "./automation";
|
||||||
|
|
||||||
export const ACTION_TYPES = {
|
export const ACTION_ICONS = {
|
||||||
condition: mdiAbTesting,
|
condition: mdiAbTesting,
|
||||||
delay: mdiTimerOutline,
|
delay: mdiTimerOutline,
|
||||||
event: mdiGestureDoubleTap,
|
event: mdiGestureDoubleTap,
|
||||||
@ -34,6 +38,43 @@ export const ACTION_TYPES = {
|
|||||||
variables: mdiApplicationVariableOutline,
|
variables: mdiApplicationVariableOutline,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const YAML_ONLY_ACTION_TYPES = new Set<keyof typeof ACTION_TYPES>([
|
export const YAML_ONLY_ACTION_TYPES = new Set<keyof typeof ACTION_ICONS>([
|
||||||
"variables",
|
"variables",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export const ACTION_GROUPS: AutomationElementGroup = {
|
||||||
|
device_id: {},
|
||||||
|
helpers: {
|
||||||
|
icon: mdiTools,
|
||||||
|
members: {},
|
||||||
|
},
|
||||||
|
building_blocks: {
|
||||||
|
icon: mdiExcavator,
|
||||||
|
members: {
|
||||||
|
condition: {},
|
||||||
|
delay: {},
|
||||||
|
wait_template: {},
|
||||||
|
wait_for_trigger: {},
|
||||||
|
repeat: {},
|
||||||
|
choose: {},
|
||||||
|
if: {},
|
||||||
|
stop: {},
|
||||||
|
parallel: {},
|
||||||
|
variables: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
other: {
|
||||||
|
icon: mdiDotsHorizontal,
|
||||||
|
members: {
|
||||||
|
event: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const SERVICE_PREFIX = "__SERVICE__";
|
||||||
|
|
||||||
|
export const isService = (key: string | undefined): boolean | undefined =>
|
||||||
|
key?.startsWith(SERVICE_PREFIX);
|
||||||
|
|
||||||
|
export const getService = (key: string): string =>
|
||||||
|
key.substring(SERVICE_PREFIX.length);
|
||||||
|
@ -128,7 +128,7 @@ export const areaCompare =
|
|||||||
(entries?: HomeAssistant["areas"], order?: string[]) =>
|
(entries?: HomeAssistant["areas"], order?: string[]) =>
|
||||||
(a: string, b: string) => {
|
(a: string, b: string) => {
|
||||||
const indexA = order ? order.indexOf(a) : -1;
|
const indexA = order ? order.indexOf(a) : -1;
|
||||||
const indexB = order ? order.indexOf(b) : 1;
|
const indexB = order ? order.indexOf(b) : -1;
|
||||||
if (indexA === -1 && indexB === -1) {
|
if (indexA === -1 && indexB === -1) {
|
||||||
const nameA = entries?.[a]?.name ?? a;
|
const nameA = entries?.[a]?.name ?? a;
|
||||||
const nameB = entries?.[b]?.name ?? b;
|
const nameB = entries?.[b]?.name ?? b;
|
||||||
|
@ -275,6 +275,10 @@ export interface ShorthandNotCondition extends ShorthandBaseCondition {
|
|||||||
not: Condition[];
|
not: Condition[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AutomationElementGroup {
|
||||||
|
[key: string]: { icon?: string; members?: AutomationElementGroup };
|
||||||
|
}
|
||||||
|
|
||||||
export type Condition =
|
export type Condition =
|
||||||
| StateCondition
|
| StateCondition
|
||||||
| NumericStateCondition
|
| NumericStateCondition
|
||||||
|
@ -766,48 +766,38 @@ const tryDescribeCondition = (
|
|||||||
|
|
||||||
// State Condition
|
// State Condition
|
||||||
if (condition.condition === "state") {
|
if (condition.condition === "state") {
|
||||||
let base = "Confirm";
|
|
||||||
if (!condition.entity_id) {
|
if (!condition.entity_id) {
|
||||||
return `${base} state`;
|
return hass.localize(
|
||||||
|
`${conditionsTranslationBaseKey}.state.description.no_entity`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let attribute = "";
|
||||||
if (condition.attribute) {
|
if (condition.attribute) {
|
||||||
const stateObj = Array.isArray(condition.entity_id)
|
const stateObj = Array.isArray(condition.entity_id)
|
||||||
? hass.states[condition.entity_id[0]]
|
? hass.states[condition.entity_id[0]]
|
||||||
: hass.states[condition.entity_id];
|
: hass.states[condition.entity_id];
|
||||||
base += ` ${computeAttributeNameDisplay(
|
attribute = computeAttributeNameDisplay(
|
||||||
hass.localize,
|
hass.localize,
|
||||||
stateObj,
|
stateObj,
|
||||||
hass.entities,
|
hass.entities,
|
||||||
condition.attribute
|
condition.attribute
|
||||||
)} of`;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const entities: string[] = [];
|
||||||
if (Array.isArray(condition.entity_id)) {
|
if (Array.isArray(condition.entity_id)) {
|
||||||
const entities: string[] = [];
|
|
||||||
for (const entity of condition.entity_id.values()) {
|
for (const entity of condition.entity_id.values()) {
|
||||||
if (hass.states[entity]) {
|
if (hass.states[entity]) {
|
||||||
entities.push(computeStateName(hass.states[entity]) || entity);
|
entities.push(computeStateName(hass.states[entity]) || entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (entities.length !== 0) {
|
|
||||||
const entitiesString =
|
|
||||||
condition.match === "any"
|
|
||||||
? formatListWithOrs(hass.locale, entities)
|
|
||||||
: formatListWithAnds(hass.locale, entities);
|
|
||||||
base += ` ${entitiesString} ${
|
|
||||||
condition.entity_id.length > 1 ? "are" : "is"
|
|
||||||
}`;
|
|
||||||
} else {
|
|
||||||
// no entity_id or empty array
|
|
||||||
base += " an entity";
|
|
||||||
}
|
|
||||||
} else if (condition.entity_id) {
|
} else if (condition.entity_id) {
|
||||||
base += ` ${
|
entities.push(
|
||||||
hass.states[condition.entity_id]
|
hass.states[condition.entity_id]
|
||||||
? computeStateName(hass.states[condition.entity_id])
|
? computeStateName(hass.states[condition.entity_id])
|
||||||
: condition.entity_id
|
: condition.entity_id
|
||||||
} is`;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const states: string[] = [];
|
const states: string[] = [];
|
||||||
@ -845,21 +835,27 @@ const tryDescribeCondition = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (states.length === 0) {
|
let duration = "";
|
||||||
states.push("a state");
|
|
||||||
}
|
|
||||||
|
|
||||||
const statesString = formatListWithOrs(hass.locale, states);
|
|
||||||
base += ` ${statesString}`;
|
|
||||||
|
|
||||||
if (condition.for) {
|
if (condition.for) {
|
||||||
const duration = describeDuration(hass.locale, condition.for);
|
duration = describeDuration(hass.locale, condition.for) || "";
|
||||||
if (duration) {
|
|
||||||
base += ` for ${duration}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return base;
|
return hass.localize(
|
||||||
|
`${conditionsTranslationBaseKey}.state.description.full`,
|
||||||
|
{
|
||||||
|
hasAttribute: attribute !== "" ? "true" : "false",
|
||||||
|
attribute: attribute,
|
||||||
|
numberOfEntities: entities.length,
|
||||||
|
entities:
|
||||||
|
condition.match === "any"
|
||||||
|
? formatListWithOrs(hass.locale, entities)
|
||||||
|
: formatListWithAnds(hass.locale, entities),
|
||||||
|
numberOfStates: states.length,
|
||||||
|
states: formatListWithOrs(hass.locale, states),
|
||||||
|
hasDuration: duration !== "" ? "true" : "false",
|
||||||
|
duration: duration,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Numeric State Condition
|
// Numeric State Condition
|
||||||
|
@ -3,16 +3,21 @@ import {
|
|||||||
mdiClockOutline,
|
mdiClockOutline,
|
||||||
mdiCodeBraces,
|
mdiCodeBraces,
|
||||||
mdiDevices,
|
mdiDevices,
|
||||||
|
mdiDotsHorizontal,
|
||||||
|
mdiExcavator,
|
||||||
mdiGateOr,
|
mdiGateOr,
|
||||||
mdiIdentifier,
|
mdiIdentifier,
|
||||||
|
mdiMapClock,
|
||||||
mdiMapMarkerRadius,
|
mdiMapMarkerRadius,
|
||||||
mdiNotEqualVariant,
|
mdiNotEqualVariant,
|
||||||
mdiNumeric,
|
mdiNumeric,
|
||||||
|
mdiShape,
|
||||||
mdiStateMachine,
|
mdiStateMachine,
|
||||||
mdiWeatherSunny,
|
mdiWeatherSunny,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
|
import { AutomationElementGroup } from "./automation";
|
||||||
|
|
||||||
export const CONDITION_TYPES = {
|
export const CONDITION_ICONS = {
|
||||||
device: mdiDevices,
|
device: mdiDevices,
|
||||||
and: mdiAmpersand,
|
and: mdiAmpersand,
|
||||||
or: mdiGateOr,
|
or: mdiGateOr,
|
||||||
@ -25,3 +30,23 @@ export const CONDITION_TYPES = {
|
|||||||
trigger: mdiIdentifier,
|
trigger: mdiIdentifier,
|
||||||
zone: mdiMapMarkerRadius,
|
zone: mdiMapMarkerRadius,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CONDITION_GROUPS: AutomationElementGroup = {
|
||||||
|
device: {},
|
||||||
|
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
|
||||||
|
time_location: {
|
||||||
|
icon: mdiMapClock,
|
||||||
|
members: { sun: {}, time: {}, zone: {} },
|
||||||
|
},
|
||||||
|
building_blocks: {
|
||||||
|
icon: mdiExcavator,
|
||||||
|
members: { and: {}, or: {}, not: {} },
|
||||||
|
},
|
||||||
|
other: {
|
||||||
|
icon: mdiDotsHorizontal,
|
||||||
|
members: {
|
||||||
|
template: {},
|
||||||
|
trigger: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
@ -65,7 +65,7 @@ export function canOpen(stateObj: CoverEntity) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const assumedState = stateObj.attributes.assumed_state === true;
|
const assumedState = stateObj.attributes.assumed_state === true;
|
||||||
return (!isFullyOpen(stateObj) && !isOpening(stateObj)) || assumedState;
|
return assumedState || (!isFullyOpen(stateObj) && !isOpening(stateObj));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canClose(stateObj: CoverEntity): boolean {
|
export function canClose(stateObj: CoverEntity): boolean {
|
||||||
@ -73,7 +73,7 @@ export function canClose(stateObj: CoverEntity): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const assumedState = stateObj.attributes.assumed_state === true;
|
const assumedState = stateObj.attributes.assumed_state === true;
|
||||||
return (!isFullyClosed(stateObj) && !isClosing(stateObj)) || assumedState;
|
return assumedState || (!isFullyClosed(stateObj) && !isClosing(stateObj));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canStop(stateObj: CoverEntity): boolean {
|
export function canStop(stateObj: CoverEntity): boolean {
|
||||||
@ -85,7 +85,7 @@ export function canOpenTilt(stateObj: CoverEntity): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const assumedState = stateObj.attributes.assumed_state === true;
|
const assumedState = stateObj.attributes.assumed_state === true;
|
||||||
return !isFullyOpenTilt(stateObj) || assumedState;
|
return assumedState || !isFullyOpenTilt(stateObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canCloseTilt(stateObj: CoverEntity): boolean {
|
export function canCloseTilt(stateObj: CoverEntity): boolean {
|
||||||
@ -93,7 +93,7 @@ export function canCloseTilt(stateObj: CoverEntity): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const assumedState = stateObj.attributes.assumed_state === true;
|
const assumedState = stateObj.attributes.assumed_state === true;
|
||||||
return !isFullyClosedTilt(stateObj) || assumedState;
|
return assumedState || !isFullyClosedTilt(stateObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canStopTilt(stateObj: CoverEntity): boolean {
|
export function canStopTilt(stateObj: CoverEntity): boolean {
|
||||||
|
@ -75,6 +75,9 @@ export const DOMAIN_ATTRIBUTES_UNITS = {
|
|||||||
vacuum: {
|
vacuum: {
|
||||||
battery_level: "%",
|
battery_level: "%",
|
||||||
},
|
},
|
||||||
|
valve: {
|
||||||
|
current_position: "%",
|
||||||
|
},
|
||||||
sensor: {
|
sensor: {
|
||||||
battery_level: "%",
|
battery_level: "%",
|
||||||
},
|
},
|
||||||
|
@ -16,7 +16,9 @@ export type IntegrationType =
|
|||||||
| "helper"
|
| "helper"
|
||||||
| "hub"
|
| "hub"
|
||||||
| "service"
|
| "service"
|
||||||
| "hardware";
|
| "hardware"
|
||||||
|
| "entity"
|
||||||
|
| "system";
|
||||||
|
|
||||||
export interface IntegrationManifest {
|
export interface IntegrationManifest {
|
||||||
is_built_in: boolean;
|
is_built_in: boolean;
|
||||||
|
@ -90,7 +90,7 @@ export const enum MediaPlayerEntityFeature {
|
|||||||
TURN_ON = 128,
|
TURN_ON = 128,
|
||||||
TURN_OFF = 256,
|
TURN_OFF = 256,
|
||||||
PLAY_MEDIA = 512,
|
PLAY_MEDIA = 512,
|
||||||
VOLUME_BUTTONS = 1024,
|
VOLUME_STEP = 1024,
|
||||||
SELECT_SOURCE = 2048,
|
SELECT_SOURCE = 2048,
|
||||||
STOP = 4096,
|
STOP = 4096,
|
||||||
CLEAR_PLAYLIST = 8192,
|
CLEAR_PLAYLIST = 8192,
|
||||||
|
@ -18,6 +18,8 @@ export interface TodoItem {
|
|||||||
uid: string;
|
uid: string;
|
||||||
summary: string;
|
summary: string;
|
||||||
status: TodoItemStatus;
|
status: TodoItemStatus;
|
||||||
|
description?: string;
|
||||||
|
due?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum TodoListEntityFeature {
|
export const enum TodoListEntityFeature {
|
||||||
@ -25,6 +27,9 @@ export const enum TodoListEntityFeature {
|
|||||||
DELETE_TODO_ITEM = 2,
|
DELETE_TODO_ITEM = 2,
|
||||||
UPDATE_TODO_ITEM = 4,
|
UPDATE_TODO_ITEM = 4,
|
||||||
MOVE_TODO_ITEM = 8,
|
MOVE_TODO_ITEM = 8,
|
||||||
|
SET_DUE_DATE_ON_ITEM = 16,
|
||||||
|
SET_DUE_DATETIME_ON_ITEM = 32,
|
||||||
|
SET_DESCRIPTION_ON_ITEM = 64,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getTodoLists = (hass: HomeAssistant): TodoList[] =>
|
export const getTodoLists = (hass: HomeAssistant): TodoList[] =>
|
||||||
@ -74,20 +79,30 @@ export const updateItem = (
|
|||||||
hass.callService(
|
hass.callService(
|
||||||
"todo",
|
"todo",
|
||||||
"update_item",
|
"update_item",
|
||||||
{ item: item.uid, rename: item.summary, status: item.status },
|
{
|
||||||
|
item: item.uid,
|
||||||
|
rename: item.summary,
|
||||||
|
status: item.status,
|
||||||
|
description: item.description || undefined,
|
||||||
|
due_datetime: item.due?.includes("T") ? item.due : undefined,
|
||||||
|
due_date: item.due?.includes("T") ? undefined : item.due || undefined,
|
||||||
|
},
|
||||||
{ entity_id }
|
{ entity_id }
|
||||||
);
|
);
|
||||||
|
|
||||||
export const createItem = (
|
export const createItem = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entity_id: string,
|
entity_id: string,
|
||||||
summary: string
|
item: Omit<TodoItem, "uid" | "status">
|
||||||
): Promise<ServiceCallResponse> =>
|
): Promise<ServiceCallResponse> =>
|
||||||
hass.callService(
|
hass.callService(
|
||||||
"todo",
|
"todo",
|
||||||
"add_item",
|
"add_item",
|
||||||
{
|
{
|
||||||
item: summary,
|
item: item.summary,
|
||||||
|
description: item.description || undefined,
|
||||||
|
due_datetime: item.due?.includes("T") ? item.due : undefined,
|
||||||
|
due_date: item.due?.includes("T") ? undefined : item.due,
|
||||||
},
|
},
|
||||||
{ entity_id }
|
{ entity_id }
|
||||||
);
|
);
|
||||||
|
@ -4,13 +4,16 @@ import {
|
|||||||
mdiClockOutline,
|
mdiClockOutline,
|
||||||
mdiCodeBraces,
|
mdiCodeBraces,
|
||||||
mdiDevices,
|
mdiDevices,
|
||||||
|
mdiDotsHorizontal,
|
||||||
mdiGestureDoubleTap,
|
mdiGestureDoubleTap,
|
||||||
|
mdiMapClock,
|
||||||
mdiMapMarker,
|
mdiMapMarker,
|
||||||
mdiMapMarkerRadius,
|
mdiMapMarkerRadius,
|
||||||
mdiMessageAlert,
|
mdiMessageAlert,
|
||||||
mdiMicrophoneMessage,
|
mdiMicrophoneMessage,
|
||||||
mdiNfcVariant,
|
mdiNfcVariant,
|
||||||
mdiNumeric,
|
mdiNumeric,
|
||||||
|
mdiShape,
|
||||||
mdiStateMachine,
|
mdiStateMachine,
|
||||||
mdiSwapHorizontal,
|
mdiSwapHorizontal,
|
||||||
mdiWeatherSunny,
|
mdiWeatherSunny,
|
||||||
@ -18,8 +21,9 @@ import {
|
|||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
|
|
||||||
import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
|
import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
|
||||||
|
import { AutomationElementGroup } from "./automation";
|
||||||
|
|
||||||
export const TRIGGER_TYPES = {
|
export const TRIGGER_ICONS = {
|
||||||
calendar: mdiCalendar,
|
calendar: mdiCalendar,
|
||||||
device: mdiDevices,
|
device: mdiDevices,
|
||||||
event: mdiGestureDoubleTap,
|
event: mdiGestureDoubleTap,
|
||||||
@ -38,3 +42,26 @@ export const TRIGGER_TYPES = {
|
|||||||
persistent_notification: mdiMessageAlert,
|
persistent_notification: mdiMessageAlert,
|
||||||
zone: mdiMapMarkerRadius,
|
zone: mdiMapMarkerRadius,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const TRIGGER_GROUPS: AutomationElementGroup = {
|
||||||
|
device: {},
|
||||||
|
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
|
||||||
|
time_location: {
|
||||||
|
icon: mdiMapClock,
|
||||||
|
members: { calendar: {}, sun: {}, time: {}, time_pattern: {}, zone: {} },
|
||||||
|
},
|
||||||
|
other: {
|
||||||
|
icon: mdiDotsHorizontal,
|
||||||
|
members: {
|
||||||
|
event: {},
|
||||||
|
geo_location: {},
|
||||||
|
homeassistant: {},
|
||||||
|
mqtt: {},
|
||||||
|
conversation: {},
|
||||||
|
tag: {},
|
||||||
|
template: {},
|
||||||
|
webhook: {},
|
||||||
|
persistent_notification: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
@ -13,11 +13,13 @@ import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { showToast } from "../util/toast";
|
import { showToast } from "../util/toast";
|
||||||
|
|
||||||
export const UPDATE_SUPPORT_INSTALL = 1;
|
export enum UpdateEntityFeature {
|
||||||
export const UPDATE_SUPPORT_SPECIFIC_VERSION = 2;
|
INSTALL = 1,
|
||||||
export const UPDATE_SUPPORT_PROGRESS = 4;
|
SPECIFIC_VERSION = 2,
|
||||||
export const UPDATE_SUPPORT_BACKUP = 8;
|
PROGRESS = 4,
|
||||||
export const UPDATE_SUPPORT_RELEASE_NOTES = 16;
|
BACKUP = 8,
|
||||||
|
RELEASE_NOTES = 16,
|
||||||
|
}
|
||||||
|
|
||||||
interface UpdateEntityAttributes extends HassEntityAttributeBase {
|
interface UpdateEntityAttributes extends HassEntityAttributeBase {
|
||||||
auto_update: boolean | null;
|
auto_update: boolean | null;
|
||||||
@ -35,7 +37,7 @@ export interface UpdateEntity extends HassEntityBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const updateUsesProgress = (entity: UpdateEntity): boolean =>
|
export const updateUsesProgress = (entity: UpdateEntity): boolean =>
|
||||||
supportsFeature(entity, UPDATE_SUPPORT_PROGRESS) &&
|
supportsFeature(entity, UpdateEntityFeature.PROGRESS) &&
|
||||||
typeof entity.attributes.in_progress === "number";
|
typeof entity.attributes.in_progress === "number";
|
||||||
|
|
||||||
export const updateCanInstall = (
|
export const updateCanInstall = (
|
||||||
@ -44,7 +46,7 @@ export const updateCanInstall = (
|
|||||||
): boolean =>
|
): boolean =>
|
||||||
(entity.state === BINARY_STATE_ON ||
|
(entity.state === BINARY_STATE_ON ||
|
||||||
(showSkipped && Boolean(entity.attributes.skipped_version))) &&
|
(showSkipped && Boolean(entity.attributes.skipped_version))) &&
|
||||||
supportsFeature(entity, UPDATE_SUPPORT_INSTALL);
|
supportsFeature(entity, UpdateEntityFeature.INSTALL);
|
||||||
|
|
||||||
export const updateIsInstalling = (entity: UpdateEntity): boolean =>
|
export const updateIsInstalling = (entity: UpdateEntity): boolean =>
|
||||||
updateUsesProgress(entity) || !!entity.attributes.in_progress;
|
updateUsesProgress(entity) || !!entity.attributes.in_progress;
|
||||||
@ -176,7 +178,7 @@ export const computeUpdateStateDisplay = (
|
|||||||
if (state === "on") {
|
if (state === "on") {
|
||||||
if (updateIsInstalling(stateObj)) {
|
if (updateIsInstalling(stateObj)) {
|
||||||
const supportsProgress =
|
const supportsProgress =
|
||||||
supportsFeature(stateObj, UPDATE_SUPPORT_PROGRESS) &&
|
supportsFeature(stateObj, UpdateEntityFeature.PROGRESS) &&
|
||||||
typeof attributes.in_progress === "number";
|
typeof attributes.in_progress === "number";
|
||||||
if (supportsProgress) {
|
if (supportsProgress) {
|
||||||
return hass.localize("ui.card.update.installing_with_progress", {
|
return hass.localize("ui.card.update.installing_with_progress", {
|
||||||
|
85
src/data/valve.ts
Normal file
85
src/data/valve.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import {
|
||||||
|
HassEntityAttributeBase,
|
||||||
|
HassEntityBase,
|
||||||
|
} from "home-assistant-js-websocket";
|
||||||
|
import { UNAVAILABLE } from "./entity";
|
||||||
|
import { stateActive } from "../common/entity/state_active";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
export const enum ValveEntityFeature {
|
||||||
|
OPEN = 1,
|
||||||
|
CLOSE = 2,
|
||||||
|
SET_POSITION = 4,
|
||||||
|
STOP = 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFullyOpen(stateObj: ValveEntity) {
|
||||||
|
if (stateObj.attributes.current_position !== undefined) {
|
||||||
|
return stateObj.attributes.current_position === 100;
|
||||||
|
}
|
||||||
|
return stateObj.state === "open";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFullyClosed(stateObj: ValveEntity) {
|
||||||
|
if (stateObj.attributes.current_position !== undefined) {
|
||||||
|
return stateObj.attributes.current_position === 0;
|
||||||
|
}
|
||||||
|
return stateObj.state === "closed";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isOpening(stateObj: ValveEntity) {
|
||||||
|
return stateObj.state === "opening";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isClosing(stateObj: ValveEntity) {
|
||||||
|
return stateObj.state === "closing";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canOpen(stateObj: ValveEntity) {
|
||||||
|
if (stateObj.state === UNAVAILABLE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const assumedState = stateObj.attributes.assumed_state === true;
|
||||||
|
return assumedState || (!isFullyOpen(stateObj) && !isOpening(stateObj));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canClose(stateObj: ValveEntity): boolean {
|
||||||
|
if (stateObj.state === UNAVAILABLE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const assumedState = stateObj.attributes.assumed_state === true;
|
||||||
|
return assumedState || (!isFullyClosed(stateObj) && !isClosing(stateObj));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canStop(stateObj: ValveEntity): boolean {
|
||||||
|
return stateObj.state !== UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ValveEntityAttributes extends HassEntityAttributeBase {
|
||||||
|
current_position?: number;
|
||||||
|
position?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ValveEntity extends HassEntityBase {
|
||||||
|
attributes: ValveEntityAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function computeValvePositionStateDisplay(
|
||||||
|
stateObj: ValveEntity,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
position?: number
|
||||||
|
) {
|
||||||
|
const statePosition = stateActive(stateObj)
|
||||||
|
? stateObj.attributes.current_position
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const currentPosition = position ?? statePosition;
|
||||||
|
|
||||||
|
return currentPosition && currentPosition !== 100
|
||||||
|
? hass.formatEntityAttributeValue(
|
||||||
|
stateObj,
|
||||||
|
"current_position",
|
||||||
|
Math.round(currentPosition)
|
||||||
|
)
|
||||||
|
: "";
|
||||||
|
}
|
@ -383,11 +383,13 @@ export const removeMembersFromGroup = (
|
|||||||
export const addGroup = (
|
export const addGroup = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
groupName: string,
|
groupName: string,
|
||||||
|
groupId?: number,
|
||||||
membersToAdd?: ZHAGroupMember[]
|
membersToAdd?: ZHAGroupMember[]
|
||||||
): Promise<ZHAGroup> =>
|
): Promise<ZHAGroup> =>
|
||||||
hass.callWS({
|
hass.callWS({
|
||||||
type: "zha/group/add",
|
type: "zha/group/add",
|
||||||
group_name: groupName,
|
group_name: groupName,
|
||||||
|
group_id: groupId,
|
||||||
members: membersToAdd,
|
members: membersToAdd,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ export const DOMAINS_WITH_NEW_MORE_INFO = [
|
|||||||
"lock",
|
"lock",
|
||||||
"siren",
|
"siren",
|
||||||
"switch",
|
"switch",
|
||||||
|
"valve",
|
||||||
"water_heater",
|
"water_heater",
|
||||||
];
|
];
|
||||||
/** Domains with separate more info dialog. */
|
/** Domains with separate more info dialog. */
|
||||||
@ -61,6 +62,7 @@ export const DOMAINS_WITH_MORE_INFO = [
|
|||||||
"timer",
|
"timer",
|
||||||
"update",
|
"update",
|
||||||
"vacuum",
|
"vacuum",
|
||||||
|
"valve",
|
||||||
"water_heater",
|
"water_heater",
|
||||||
"weather",
|
"weather",
|
||||||
];
|
];
|
||||||
|
@ -42,7 +42,9 @@ class MoreInfoCover extends LitElement {
|
|||||||
protected willUpdate(changedProps: PropertyValues): void {
|
protected willUpdate(changedProps: PropertyValues): void {
|
||||||
super.willUpdate(changedProps);
|
super.willUpdate(changedProps);
|
||||||
if (changedProps.has("stateObj") && this.stateObj) {
|
if (changedProps.has("stateObj") && this.stateObj) {
|
||||||
if (!this._mode) {
|
const entityId = this.stateObj.entity_id;
|
||||||
|
const oldEntityId = changedProps.get("stateObj")?.entity_id;
|
||||||
|
if (!this._mode || entityId !== oldEntityId) {
|
||||||
this._mode =
|
this._mode =
|
||||||
supportsFeature(this.stateObj, CoverEntityFeature.SET_POSITION) ||
|
supportsFeature(this.stateObj, CoverEntityFeature.SET_POSITION) ||
|
||||||
supportsFeature(this.stateObj, CoverEntityFeature.SET_TILT_POSITION)
|
supportsFeature(this.stateObj, CoverEntityFeature.SET_TILT_POSITION)
|
||||||
|
@ -81,7 +81,7 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
${(supportsFeature(stateObj, MediaPlayerEntityFeature.VOLUME_SET) ||
|
${(supportsFeature(stateObj, MediaPlayerEntityFeature.VOLUME_SET) ||
|
||||||
supportsFeature(stateObj, MediaPlayerEntityFeature.VOLUME_BUTTONS)) &&
|
supportsFeature(stateObj, MediaPlayerEntityFeature.VOLUME_STEP)) &&
|
||||||
stateActive(stateObj)
|
stateActive(stateObj)
|
||||||
? html`
|
? html`
|
||||||
<div class="volume">
|
<div class="volume">
|
||||||
@ -104,8 +104,9 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${supportsFeature(
|
${supportsFeature(
|
||||||
stateObj,
|
stateObj,
|
||||||
MediaPlayerEntityFeature.VOLUME_BUTTONS
|
MediaPlayerEntityFeature.VOLUME_SET
|
||||||
)
|
) ||
|
||||||
|
supportsFeature(stateObj, MediaPlayerEntityFeature.VOLUME_STEP)
|
||||||
? html`
|
? html`
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
action="volume_down"
|
action="volume_down"
|
||||||
|
@ -13,13 +13,9 @@ import "../../../components/ha-markdown";
|
|||||||
import { isUnavailableState } from "../../../data/entity";
|
import { isUnavailableState } from "../../../data/entity";
|
||||||
import {
|
import {
|
||||||
UpdateEntity,
|
UpdateEntity,
|
||||||
|
UpdateEntityFeature,
|
||||||
updateIsInstalling,
|
updateIsInstalling,
|
||||||
updateReleaseNotes,
|
updateReleaseNotes,
|
||||||
UPDATE_SUPPORT_BACKUP,
|
|
||||||
UPDATE_SUPPORT_INSTALL,
|
|
||||||
UPDATE_SUPPORT_PROGRESS,
|
|
||||||
UPDATE_SUPPORT_RELEASE_NOTES,
|
|
||||||
UPDATE_SUPPORT_SPECIFIC_VERSION,
|
|
||||||
} from "../../../data/update";
|
} from "../../../data/update";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
@ -49,7 +45,7 @@ class MoreInfoUpdate extends LitElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this.stateObj.attributes.in_progress
|
${this.stateObj.attributes.in_progress
|
||||||
? supportsFeature(this.stateObj, UPDATE_SUPPORT_PROGRESS) &&
|
? supportsFeature(this.stateObj, UpdateEntityFeature.PROGRESS) &&
|
||||||
typeof this.stateObj.attributes.in_progress === "number"
|
typeof this.stateObj.attributes.in_progress === "number"
|
||||||
? html`<mwc-linear-progress
|
? html`<mwc-linear-progress
|
||||||
.progress=${this.stateObj.attributes.in_progress / 100}
|
.progress=${this.stateObj.attributes.in_progress / 100}
|
||||||
@ -101,7 +97,7 @@ class MoreInfoUpdate extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
: ""}
|
: ""}
|
||||||
${supportsFeature(this.stateObj!, UPDATE_SUPPORT_RELEASE_NOTES) &&
|
${supportsFeature(this.stateObj!, UpdateEntityFeature.RELEASE_NOTES) &&
|
||||||
!this._error
|
!this._error
|
||||||
? !this._releaseNotes
|
? !this._releaseNotes
|
||||||
? html`<div class="flex center">
|
? html`<div class="flex center">
|
||||||
@ -117,7 +113,7 @@ class MoreInfoUpdate extends LitElement {
|
|||||||
.content=${this.stateObj.attributes.release_summary}
|
.content=${this.stateObj.attributes.release_summary}
|
||||||
></ha-markdown>`
|
></ha-markdown>`
|
||||||
: ""}
|
: ""}
|
||||||
${supportsFeature(this.stateObj, UPDATE_SUPPORT_BACKUP)
|
${supportsFeature(this.stateObj, UpdateEntityFeature.BACKUP)
|
||||||
? html`<hr />
|
? html`<hr />
|
||||||
<ha-formfield
|
<ha-formfield
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@ -155,7 +151,7 @@ class MoreInfoUpdate extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
`}
|
`}
|
||||||
${supportsFeature(this.stateObj, UPDATE_SUPPORT_INSTALL)
|
${supportsFeature(this.stateObj, UpdateEntityFeature.INSTALL)
|
||||||
? html`
|
? html`
|
||||||
<mwc-button
|
<mwc-button
|
||||||
@click=${this._handleInstall}
|
@click=${this._handleInstall}
|
||||||
@ -174,7 +170,7 @@ class MoreInfoUpdate extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(): void {
|
protected firstUpdated(): void {
|
||||||
if (supportsFeature(this.stateObj!, UPDATE_SUPPORT_RELEASE_NOTES)) {
|
if (supportsFeature(this.stateObj!, UpdateEntityFeature.RELEASE_NOTES)) {
|
||||||
updateReleaseNotes(this.hass, this.stateObj!.entity_id)
|
updateReleaseNotes(this.hass, this.stateObj!.entity_id)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
this._releaseNotes = result;
|
this._releaseNotes = result;
|
||||||
@ -186,7 +182,7 @@ class MoreInfoUpdate extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get _shouldCreateBackup(): boolean | null {
|
get _shouldCreateBackup(): boolean | null {
|
||||||
if (!supportsFeature(this.stateObj!, UPDATE_SUPPORT_BACKUP)) {
|
if (!supportsFeature(this.stateObj!, UpdateEntityFeature.BACKUP)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const checkbox = this.shadowRoot?.querySelector("ha-checkbox");
|
const checkbox = this.shadowRoot?.querySelector("ha-checkbox");
|
||||||
@ -206,7 +202,7 @@ class MoreInfoUpdate extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
supportsFeature(this.stateObj!, UPDATE_SUPPORT_SPECIFIC_VERSION) &&
|
supportsFeature(this.stateObj!, UpdateEntityFeature.SPECIFIC_VERSION) &&
|
||||||
this.stateObj!.attributes.latest_version
|
this.stateObj!.attributes.latest_version
|
||||||
) {
|
) {
|
||||||
installData.version = this.stateObj!.attributes.latest_version;
|
installData.version = this.stateObj!.attributes.latest_version;
|
||||||
|
192
src/dialogs/more-info/controls/more-info-valve.ts
Normal file
192
src/dialogs/more-info/controls/more-info-valve.ts
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
import { mdiMenu, mdiSwapVertical } from "@mdi/js";
|
||||||
|
import {
|
||||||
|
CSSResultGroup,
|
||||||
|
LitElement,
|
||||||
|
PropertyValues,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
|
nothing,
|
||||||
|
} from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
|
import "../../../components/ha-attributes";
|
||||||
|
import "../../../components/ha-icon-button-group";
|
||||||
|
import "../../../components/ha-icon-button-toggle";
|
||||||
|
import {
|
||||||
|
ValveEntity,
|
||||||
|
ValveEntityFeature,
|
||||||
|
computeValvePositionStateDisplay,
|
||||||
|
} from "../../../data/valve";
|
||||||
|
import "../../../state-control/valve/ha-state-control-valve-buttons";
|
||||||
|
import "../../../state-control/valve/ha-state-control-valve-position";
|
||||||
|
import "../../../state-control/valve/ha-state-control-valve-toggle";
|
||||||
|
import type { HomeAssistant } from "../../../types";
|
||||||
|
import "../components/ha-more-info-state-header";
|
||||||
|
import { moreInfoControlStyle } from "../components/more-info-control-style";
|
||||||
|
|
||||||
|
type Mode = "position" | "button";
|
||||||
|
|
||||||
|
@customElement("more-info-valve")
|
||||||
|
class MoreInfoValve extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public stateObj?: ValveEntity;
|
||||||
|
|
||||||
|
@state() private _mode?: Mode;
|
||||||
|
|
||||||
|
private _setMode(ev) {
|
||||||
|
this._mode = ev.currentTarget.mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected willUpdate(changedProps: PropertyValues): void {
|
||||||
|
super.willUpdate(changedProps);
|
||||||
|
if (changedProps.has("stateObj") && this.stateObj) {
|
||||||
|
const entityId = this.stateObj.entity_id;
|
||||||
|
const oldEntityId = changedProps.get("stateObj")?.entity_id;
|
||||||
|
if (!this._mode || entityId !== oldEntityId) {
|
||||||
|
this._mode = supportsFeature(
|
||||||
|
this.stateObj,
|
||||||
|
ValveEntityFeature.SET_POSITION
|
||||||
|
)
|
||||||
|
? "position"
|
||||||
|
: "button";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _stateOverride() {
|
||||||
|
const stateDisplay = this.hass.formatEntityState(this.stateObj!);
|
||||||
|
|
||||||
|
const positionStateDisplay = computeValvePositionStateDisplay(
|
||||||
|
this.stateObj!,
|
||||||
|
this.hass
|
||||||
|
);
|
||||||
|
|
||||||
|
if (positionStateDisplay) {
|
||||||
|
return `${stateDisplay} ⸱ ${positionStateDisplay}`;
|
||||||
|
}
|
||||||
|
return stateDisplay;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this.hass || !this.stateObj) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const supportsPosition = supportsFeature(
|
||||||
|
this.stateObj,
|
||||||
|
ValveEntityFeature.SET_POSITION
|
||||||
|
);
|
||||||
|
|
||||||
|
const supportsOpenClose =
|
||||||
|
supportsFeature(this.stateObj, ValveEntityFeature.OPEN) ||
|
||||||
|
supportsFeature(this.stateObj, ValveEntityFeature.CLOSE) ||
|
||||||
|
supportsFeature(this.stateObj, ValveEntityFeature.STOP);
|
||||||
|
|
||||||
|
const supportsOpenCloseWithoutStop =
|
||||||
|
supportsFeature(this.stateObj, ValveEntityFeature.OPEN) &&
|
||||||
|
supportsFeature(this.stateObj, ValveEntityFeature.CLOSE) &&
|
||||||
|
!supportsFeature(this.stateObj, ValveEntityFeature.STOP);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-more-info-state-header
|
||||||
|
.hass=${this.hass}
|
||||||
|
.stateObj=${this.stateObj}
|
||||||
|
.stateOverride=${this._stateOverride}
|
||||||
|
></ha-more-info-state-header>
|
||||||
|
<div class="controls">
|
||||||
|
<div class="main-control">
|
||||||
|
${
|
||||||
|
this._mode === "position"
|
||||||
|
? html`
|
||||||
|
${supportsPosition
|
||||||
|
? html`
|
||||||
|
<ha-state-control-valve-position
|
||||||
|
.stateObj=${this.stateObj}
|
||||||
|
.hass=${this.hass}
|
||||||
|
></ha-state-control-valve-position>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
`
|
||||||
|
: nothing
|
||||||
|
}
|
||||||
|
${
|
||||||
|
this._mode === "button"
|
||||||
|
? html`
|
||||||
|
${supportsOpenCloseWithoutStop
|
||||||
|
? html`
|
||||||
|
<ha-state-control-valve-toggle
|
||||||
|
.stateObj=${this.stateObj}
|
||||||
|
.hass=${this.hass}
|
||||||
|
></ha-state-control-valve-toggle>
|
||||||
|
`
|
||||||
|
: supportsOpenClose
|
||||||
|
? html`
|
||||||
|
<ha-state-control-valve-buttons
|
||||||
|
.stateObj=${this.stateObj}
|
||||||
|
.hass=${this.hass}
|
||||||
|
></ha-state-control-valve-buttons>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
`
|
||||||
|
: nothing
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
${
|
||||||
|
supportsPosition && supportsOpenClose
|
||||||
|
? html`
|
||||||
|
<ha-icon-button-group>
|
||||||
|
<ha-icon-button-toggle
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
`ui.dialogs.more_info_control.valve.switch_mode.position`
|
||||||
|
)}
|
||||||
|
.selected=${this._mode === "position"}
|
||||||
|
.path=${mdiMenu}
|
||||||
|
.mode=${"position"}
|
||||||
|
@click=${this._setMode}
|
||||||
|
></ha-icon-button-toggle>
|
||||||
|
<ha-icon-button-toggle
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
`ui.dialogs.more_info_control.valve.switch_mode.button`
|
||||||
|
)}
|
||||||
|
.selected=${this._mode === "button"}
|
||||||
|
.path=${mdiSwapVertical}
|
||||||
|
.mode=${"button"}
|
||||||
|
@click=${this._setMode}
|
||||||
|
></ha-icon-button-toggle>
|
||||||
|
</ha-icon-button-group>
|
||||||
|
`
|
||||||
|
: nothing
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ha-attributes
|
||||||
|
.hass=${this.hass}
|
||||||
|
.stateObj=${this.stateObj}
|
||||||
|
extra-filters="current_position,current_tilt_position"
|
||||||
|
></ha-attributes>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
moreInfoControlStyle,
|
||||||
|
css`
|
||||||
|
.main-control {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.main-control > * {
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"more-info-valve": MoreInfoValve;
|
||||||
|
}
|
||||||
|
}
|
@ -35,6 +35,7 @@ const LAZY_LOADED_MORE_INFO_CONTROL = {
|
|||||||
timer: () => import("./controls/more-info-timer"),
|
timer: () => import("./controls/more-info-timer"),
|
||||||
update: () => import("./controls/more-info-update"),
|
update: () => import("./controls/more-info-update"),
|
||||||
vacuum: () => import("./controls/more-info-vacuum"),
|
vacuum: () => import("./controls/more-info-vacuum"),
|
||||||
|
valve: () => import("./controls/more-info-valve"),
|
||||||
water_heater: () => import("./controls/more-info-water_heater"),
|
water_heater: () => import("./controls/more-info-water_heater"),
|
||||||
weather: () => import("./controls/more-info-weather"),
|
weather: () => import("./controls/more-info-weather"),
|
||||||
};
|
};
|
||||||
|
92
src/dialogs/update_backup/dialog-update-backup.ts
Normal file
92
src/dialogs/update_backup/dialog-update-backup.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import "../../components/ha-button";
|
||||||
|
import { createCloseHeading } from "../../components/ha-dialog";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import { UpdateBackupDialogParams } from "./show-update-backup-dialog";
|
||||||
|
|
||||||
|
@customElement("dialog-update-backup")
|
||||||
|
class DialogBox extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _params?: UpdateBackupDialogParams;
|
||||||
|
|
||||||
|
public async showDialog(params: UpdateBackupDialogParams): Promise<void> {
|
||||||
|
this._params = params;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this._params) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-dialog
|
||||||
|
open
|
||||||
|
@closed=${this._cancel}
|
||||||
|
defaultAction="ignore"
|
||||||
|
.heading=${createCloseHeading(
|
||||||
|
this.hass,
|
||||||
|
this.hass.localize("ui.dialogs.update_backup.title")
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<p>${this.hass.localize("ui.dialogs.update_backup.text")}</p>
|
||||||
|
<ha-button @click=${this._no} slot="secondaryAction">
|
||||||
|
${this.hass!.localize("ui.common.no")}
|
||||||
|
</ha-button>
|
||||||
|
<ha-button @click=${this._yes} slot="primaryAction">
|
||||||
|
${this.hass.localize("ui.dialogs.update_backup.create")}
|
||||||
|
</ha-button>
|
||||||
|
</ha-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _no(): void {
|
||||||
|
if (this._params!.submit) {
|
||||||
|
this._params!.submit(false);
|
||||||
|
}
|
||||||
|
this.closeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _yes(): void {
|
||||||
|
if (this._params!.submit) {
|
||||||
|
this._params!.submit(true);
|
||||||
|
}
|
||||||
|
this.closeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _cancel(): void {
|
||||||
|
this._params?.cancel?.();
|
||||||
|
this.closeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialog(): void {
|
||||||
|
this._params = undefined;
|
||||||
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
ha-dialog {
|
||||||
|
/* Place above other dialogs */
|
||||||
|
--dialog-z-index: 104;
|
||||||
|
}
|
||||||
|
@media all and (min-width: 600px) {
|
||||||
|
ha-dialog {
|
||||||
|
--mdc-dialog-min-width: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-update-backup": DialogBox;
|
||||||
|
}
|
||||||
|
}
|
35
src/dialogs/update_backup/show-update-backup-dialog.ts
Normal file
35
src/dialogs/update_backup/show-update-backup-dialog.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
|
||||||
|
export interface UpdateBackupDialogParams {
|
||||||
|
submit?: (response: boolean) => void;
|
||||||
|
cancel?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const showUpdateBackupDialogParams = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: UpdateBackupDialogParams
|
||||||
|
) =>
|
||||||
|
new Promise<boolean | null>((resolve) => {
|
||||||
|
const origCancel = dialogParams.cancel;
|
||||||
|
const origSubmit = dialogParams.submit;
|
||||||
|
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-update-backup",
|
||||||
|
dialogImport: () => import("./dialog-update-backup"),
|
||||||
|
dialogParams: {
|
||||||
|
...dialogParams,
|
||||||
|
cancel: () => {
|
||||||
|
resolve(null);
|
||||||
|
if (origCancel) {
|
||||||
|
origCancel();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submit: (response: boolean) => {
|
||||||
|
resolve(response);
|
||||||
|
if (origSubmit) {
|
||||||
|
origSubmit(response);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
@ -668,7 +668,12 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
ha-button-menu {
|
ha-button-menu {
|
||||||
--mdc-theme-on-primary: var(--text-primary-color);
|
--mdc-theme-on-primary: var(--text-primary-color);
|
||||||
--mdc-theme-primary: var(--primary-color);
|
--mdc-theme-primary: var(--primary-color);
|
||||||
margin: -8px 0 0 -8px;
|
margin-top: -8px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-inline-end: 0;
|
||||||
|
margin-left: -8px;
|
||||||
|
margin-inline-start: -8px;
|
||||||
}
|
}
|
||||||
ha-button-menu ha-button {
|
ha-button-menu ha-button {
|
||||||
--mdc-theme-primary: var(--secondary-text-color);
|
--mdc-theme-primary: var(--secondary-text-color);
|
||||||
@ -689,7 +694,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
height: 28px;
|
height: 28px;
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
margin-inline-start: 4px;
|
margin-inline-start: 4px;
|
||||||
margin-inline-end: 4px;
|
margin-inline-end: initial;
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
ha-list-item {
|
ha-list-item {
|
||||||
@ -698,7 +703,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
ha-list-item ha-svg-icon {
|
ha-list-item ha-svg-icon {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
margin-inline-start: 4px;
|
margin-inline-start: 4px;
|
||||||
margin-inline-end: 4px;
|
margin-inline-end: initial;
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
@ -124,6 +124,12 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
*/
|
*/
|
||||||
@property({ type: String }) public noDataText?: string;
|
@property({ type: String }) public noDataText?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the data table and show an empty message.
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) public empty = false;
|
||||||
|
|
||||||
@property() public route!: Route;
|
@property() public route!: Route;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -198,56 +204,61 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
.mainPage=${this.mainPage}
|
.mainPage=${this.mainPage}
|
||||||
.supervisor=${this.supervisor}
|
.supervisor=${this.supervisor}
|
||||||
>
|
>
|
||||||
${!this.hideFilterMenu
|
${this.empty
|
||||||
? html`
|
? html`<div class="center">
|
||||||
<div slot="toolbar-icon">
|
<slot name="empty">${this.noDataText}</slot>
|
||||||
${this.narrow
|
</div>`
|
||||||
|
: html`${!this.hideFilterMenu
|
||||||
|
? html`
|
||||||
|
<div slot="toolbar-icon">
|
||||||
|
${this.narrow
|
||||||
|
? html`
|
||||||
|
<div class="filter-menu">
|
||||||
|
${this.numHidden || this.activeFilters
|
||||||
|
? html`<span class="badge"
|
||||||
|
>${this.numHidden || "!"}</span
|
||||||
|
>`
|
||||||
|
: ""}
|
||||||
|
<slot name="filter-menu"></slot>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}<slot name="toolbar-icon"></slot>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${this.narrow
|
||||||
|
? html`
|
||||||
|
<div slot="header">
|
||||||
|
<slot name="header">
|
||||||
|
<div class="search-toolbar">${headerToolbar}</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<ha-data-table
|
||||||
|
.hass=${this.hass}
|
||||||
|
.columns=${this.columns}
|
||||||
|
.data=${this.data}
|
||||||
|
.noDataText=${this.noDataText}
|
||||||
|
.filter=${this.filter}
|
||||||
|
.selectable=${this.selectable}
|
||||||
|
.hasFab=${this.hasFab}
|
||||||
|
.id=${this.id}
|
||||||
|
.dir=${computeRTLDirection(this.hass)}
|
||||||
|
.clickable=${this.clickable}
|
||||||
|
.appendRow=${this.appendRow}
|
||||||
|
>
|
||||||
|
${!this.narrow
|
||||||
? html`
|
? html`
|
||||||
<div class="filter-menu">
|
<div slot="header">
|
||||||
${this.numHidden || this.activeFilters
|
<slot name="header">
|
||||||
? html`<span class="badge"
|
<div class="table-header">${headerToolbar}</div>
|
||||||
>${this.numHidden || "!"}</span
|
</slot>
|
||||||
>`
|
|
||||||
: ""}
|
|
||||||
<slot name="filter-menu"></slot>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}<slot name="toolbar-icon"></slot>
|
: html` <div slot="header"></div> `}
|
||||||
</div>
|
</ha-data-table>`}
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${this.narrow
|
|
||||||
? html`
|
|
||||||
<div slot="header">
|
|
||||||
<slot name="header">
|
|
||||||
<div class="search-toolbar">${headerToolbar}</div>
|
|
||||||
</slot>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
<ha-data-table
|
|
||||||
.hass=${this.hass}
|
|
||||||
.columns=${this.columns}
|
|
||||||
.data=${this.data}
|
|
||||||
.filter=${this.filter}
|
|
||||||
.selectable=${this.selectable}
|
|
||||||
.hasFab=${this.hasFab}
|
|
||||||
.id=${this.id}
|
|
||||||
.noDataText=${this.noDataText}
|
|
||||||
.dir=${computeRTLDirection(this.hass)}
|
|
||||||
.clickable=${this.clickable}
|
|
||||||
.appendRow=${this.appendRow}
|
|
||||||
>
|
|
||||||
${!this.narrow
|
|
||||||
? html`
|
|
||||||
<div slot="header">
|
|
||||||
<slot name="header">
|
|
||||||
<div class="table-header">${headerToolbar}</div>
|
|
||||||
</slot>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: html` <div slot="header"></div> `}
|
|
||||||
</ha-data-table>
|
|
||||||
<div slot="fab"><slot name="fab"></slot></div>
|
<div slot="fab"><slot name="fab"></slot></div>
|
||||||
</hass-tabs-subpage>
|
</hass-tabs-subpage>
|
||||||
`;
|
`;
|
||||||
@ -374,6 +385,16 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
.filter-menu {
|
.filter-menu {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
.center {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import { mdiCalendarClock, mdiClose } from "@mdi/js";
|
import { mdiCalendarClock } from "@mdi/js";
|
||||||
import { toDate } from "date-fns-tz";
|
import { toDate } from "date-fns-tz";
|
||||||
import { addDays, isSameDay } from "date-fns/esm";
|
import { addDays, isSameDay } from "date-fns/esm";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { property, state } from "lit/decorators";
|
||||||
import { formatDate } from "../../common/datetime/format_date";
|
import { formatDate } from "../../common/datetime/format_date";
|
||||||
import { formatDateTime } from "../../common/datetime/format_date_time";
|
import { formatDateTime } from "../../common/datetime/format_date_time";
|
||||||
@ -11,6 +11,7 @@ import { fireEvent } from "../../common/dom/fire_event";
|
|||||||
import { isDate } from "../../common/string/is_date";
|
import { isDate } from "../../common/string/is_date";
|
||||||
import "../../components/entity/state-info";
|
import "../../components/entity/state-info";
|
||||||
import "../../components/ha-date-input";
|
import "../../components/ha-date-input";
|
||||||
|
import { createCloseHeading } from "../../components/ha-dialog";
|
||||||
import "../../components/ha-time-input";
|
import "../../components/ha-time-input";
|
||||||
import {
|
import {
|
||||||
CalendarEventMutableParams,
|
CalendarEventMutableParams,
|
||||||
@ -65,15 +66,7 @@ class DialogCalendarEventDetail extends LitElement {
|
|||||||
@closed=${this.closeDialog}
|
@closed=${this.closeDialog}
|
||||||
scrimClickAction
|
scrimClickAction
|
||||||
escapeKeyAction
|
escapeKeyAction
|
||||||
.heading=${html`
|
.heading=${createCloseHeading(this.hass, this._data!.summary)}
|
||||||
<div class="header_title">${this._data!.summary}</div>
|
|
||||||
<ha-icon-button
|
|
||||||
.label=${this.hass.localize("ui.dialogs.generic.close")}
|
|
||||||
.path=${mdiClose}
|
|
||||||
dialogAction="close"
|
|
||||||
class="header_button"
|
|
||||||
></ha-icon-button>
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
${this._error
|
${this._error
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import { mdiClose } from "@mdi/js";
|
|
||||||
import { formatInTimeZone, toDate } from "date-fns-tz";
|
import { formatInTimeZone, toDate } from "date-fns-tz";
|
||||||
import {
|
import {
|
||||||
addDays,
|
addDays,
|
||||||
@ -9,7 +8,7 @@ import {
|
|||||||
startOfHour,
|
startOfHour,
|
||||||
} from "date-fns/esm";
|
} from "date-fns/esm";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
@ -18,23 +17,24 @@ import { supportsFeature } from "../../common/entity/supports-feature";
|
|||||||
import { isDate } from "../../common/string/is_date";
|
import { isDate } from "../../common/string/is_date";
|
||||||
import "../../components/entity/ha-entity-picker";
|
import "../../components/entity/ha-entity-picker";
|
||||||
import "../../components/ha-date-input";
|
import "../../components/ha-date-input";
|
||||||
|
import { createCloseHeading } from "../../components/ha-dialog";
|
||||||
import "../../components/ha-textarea";
|
import "../../components/ha-textarea";
|
||||||
import "../../components/ha-time-input";
|
import "../../components/ha-time-input";
|
||||||
import {
|
import {
|
||||||
CalendarEntityFeature,
|
CalendarEntityFeature,
|
||||||
CalendarEventMutableParams,
|
CalendarEventMutableParams,
|
||||||
|
RecurrenceRange,
|
||||||
createCalendarEvent,
|
createCalendarEvent,
|
||||||
deleteCalendarEvent,
|
deleteCalendarEvent,
|
||||||
RecurrenceRange,
|
|
||||||
updateCalendarEvent,
|
updateCalendarEvent,
|
||||||
} from "../../data/calendar";
|
} from "../../data/calendar";
|
||||||
|
import { TimeZone } from "../../data/translation";
|
||||||
import { haStyleDialog } from "../../resources/styles";
|
import { haStyleDialog } from "../../resources/styles";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../lovelace/components/hui-generic-entity-row";
|
import "../lovelace/components/hui-generic-entity-row";
|
||||||
import "./ha-recurrence-rule-editor";
|
import "./ha-recurrence-rule-editor";
|
||||||
import { showConfirmEventDialog } from "./show-confirm-event-dialog-box";
|
import { showConfirmEventDialog } from "./show-confirm-event-dialog-box";
|
||||||
import { CalendarEventEditDialogParams } from "./show-dialog-calendar-event-editor";
|
import { CalendarEventEditDialogParams } from "./show-dialog-calendar-event-editor";
|
||||||
import { TimeZone } from "../../data/translation";
|
|
||||||
|
|
||||||
const CALENDAR_DOMAINS = ["calendar"];
|
const CALENDAR_DOMAINS = ["calendar"];
|
||||||
|
|
||||||
@ -142,19 +142,12 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
@closed=${this.closeDialog}
|
@closed=${this.closeDialog}
|
||||||
scrimClickAction
|
scrimClickAction
|
||||||
escapeKeyAction
|
escapeKeyAction
|
||||||
.heading=${html`
|
.heading=${createCloseHeading(
|
||||||
<div class="header_title">
|
this.hass,
|
||||||
${isCreate
|
isCreate
|
||||||
? this.hass.localize("ui.components.calendar.event.add")
|
? this.hass.localize("ui.components.calendar.event.add")
|
||||||
: this._summary}
|
: this._summary
|
||||||
</div>
|
)}
|
||||||
<ha-icon-button
|
|
||||||
.label=${this.hass.localize("ui.dialogs.generic.close")}
|
|
||||||
.path=${mdiClose}
|
|
||||||
dialogAction="close"
|
|
||||||
class="header_button"
|
|
||||||
></ha-icon-button>
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
${this._error
|
${this._error
|
||||||
@ -584,9 +577,11 @@ class DialogCalendarEventEditor extends LitElement {
|
|||||||
return [
|
return [
|
||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
ha-dialog {
|
@media all and (min-width: 450px and min-height: 500px) {
|
||||||
--mdc-dialog-min-width: min(600px, 95vw);
|
ha-dialog {
|
||||||
--mdc-dialog-max-width: min(600px, 95vw);
|
--mdc-dialog-min-width: min(600px, 95vw);
|
||||||
|
--mdc-dialog-max-width: min(600px, 95vw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
state-info {
|
state-info {
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
|
@ -37,7 +37,7 @@ import "../../../../components/ha-card";
|
|||||||
import "../../../../components/ha-expansion-panel";
|
import "../../../../components/ha-expansion-panel";
|
||||||
import "../../../../components/ha-icon-button";
|
import "../../../../components/ha-icon-button";
|
||||||
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
||||||
import { ACTION_TYPES, YAML_ONLY_ACTION_TYPES } from "../../../../data/action";
|
import { ACTION_ICONS, YAML_ONLY_ACTION_TYPES } from "../../../../data/action";
|
||||||
import { AutomationClipboard } from "../../../../data/automation";
|
import { AutomationClipboard } from "../../../../data/automation";
|
||||||
import { validateConfig } from "../../../../data/config";
|
import { validateConfig } from "../../../../data/config";
|
||||||
import { fullEntitiesContext } from "../../../../data/context";
|
import { fullEntitiesContext } from "../../../../data/context";
|
||||||
@ -82,9 +82,9 @@ export const getType = (action: Action | undefined) => {
|
|||||||
if (["and", "or", "not"].some((key) => key in action)) {
|
if (["and", "or", "not"].some((key) => key in action)) {
|
||||||
return "condition" as const;
|
return "condition" as const;
|
||||||
}
|
}
|
||||||
return Object.keys(ACTION_TYPES).find(
|
return Object.keys(ACTION_ICONS).find(
|
||||||
(option) => option in action
|
(option) => option in action
|
||||||
) as keyof typeof ACTION_TYPES;
|
) as keyof typeof ACTION_ICONS;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ActionElement extends LitElement {
|
export interface ActionElement extends LitElement {
|
||||||
@ -190,7 +190,7 @@ export default class HaAutomationActionRow extends LitElement {
|
|||||||
<h3 slot="header">
|
<h3 slot="header">
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
class="action-icon"
|
class="action-icon"
|
||||||
.path=${ACTION_TYPES[type!]}
|
.path=${ACTION_ICONS[type!]}
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
${capitalizeFirstLetter(
|
${capitalizeFirstLetter(
|
||||||
describeAction(this.hass, this._entityReg, this.action)
|
describeAction(this.hass, this._entityReg, this.action)
|
||||||
|
@ -1,57 +1,26 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import type { ActionDetail } from "@material/mwc-list";
|
import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js";
|
||||||
import {
|
|
||||||
mdiArrowDown,
|
|
||||||
mdiArrowUp,
|
|
||||||
mdiContentPaste,
|
|
||||||
mdiDrag,
|
|
||||||
mdiPlus,
|
|
||||||
} from "@mdi/js";
|
|
||||||
import deepClone from "deep-clone-simple";
|
import deepClone from "deep-clone-simple";
|
||||||
import {
|
import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
|
||||||
CSSResultGroup,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
css,
|
|
||||||
html,
|
|
||||||
nothing,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { repeat } from "lit/directives/repeat";
|
import { repeat } from "lit/directives/repeat";
|
||||||
import memoizeOne from "memoize-one";
|
|
||||||
import type { SortableEvent } from "sortablejs";
|
import type { SortableEvent } from "sortablejs";
|
||||||
import { storage } from "../../../../common/decorators/storage";
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { stringCompare } from "../../../../common/string/compare";
|
|
||||||
import { LocalizeFunc } from "../../../../common/translations/localize";
|
|
||||||
import "../../../../components/ha-button";
|
import "../../../../components/ha-button";
|
||||||
import "../../../../components/ha-button-menu";
|
|
||||||
import type { HaSelect } from "../../../../components/ha-select";
|
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
import { ACTION_TYPES } from "../../../../data/action";
|
import { getService, isService } from "../../../../data/action";
|
||||||
import { AutomationClipboard } from "../../../../data/automation";
|
import type { AutomationClipboard } from "../../../../data/automation";
|
||||||
import { Action } from "../../../../data/script";
|
import { Action } from "../../../../data/script";
|
||||||
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||||
import type { SortableInstance } from "../../../../resources/sortable";
|
import type { SortableInstance } from "../../../../resources/sortable";
|
||||||
import { Entries, HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
import {
|
||||||
|
PASTE_VALUE,
|
||||||
|
showAddAutomationElementDialog,
|
||||||
|
} from "../show-add-automation-element-dialog";
|
||||||
import type HaAutomationActionRow from "./ha-automation-action-row";
|
import type HaAutomationActionRow from "./ha-automation-action-row";
|
||||||
import { getType } from "./ha-automation-action-row";
|
import { getType } from "./ha-automation-action-row";
|
||||||
import "./types/ha-automation-action-activate_scene";
|
|
||||||
import "./types/ha-automation-action-choose";
|
|
||||||
import "./types/ha-automation-action-condition";
|
|
||||||
import "./types/ha-automation-action-delay";
|
|
||||||
import "./types/ha-automation-action-device_id";
|
|
||||||
import "./types/ha-automation-action-event";
|
|
||||||
import "./types/ha-automation-action-if";
|
|
||||||
import "./types/ha-automation-action-parallel";
|
|
||||||
import "./types/ha-automation-action-play_media";
|
|
||||||
import "./types/ha-automation-action-repeat";
|
|
||||||
import "./types/ha-automation-action-service";
|
|
||||||
import "./types/ha-automation-action-stop";
|
|
||||||
import "./types/ha-automation-action-wait_for_trigger";
|
|
||||||
import "./types/ha-automation-action-wait_template";
|
|
||||||
|
|
||||||
const PASTE_VALUE = "__paste__";
|
|
||||||
|
|
||||||
@customElement("ha-automation-action")
|
@customElement("ha-automation-action")
|
||||||
export default class HaAutomationAction extends LitElement {
|
export default class HaAutomationAction extends LitElement {
|
||||||
@ -150,42 +119,27 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ha-button-menu
|
<div class="buttons">
|
||||||
@action=${this._addAction}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
fixed
|
|
||||||
>
|
|
||||||
<ha-button
|
<ha-button
|
||||||
slot="trigger"
|
|
||||||
outlined
|
outlined
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.actions.add"
|
"ui.panel.config.automation.editor.actions.add"
|
||||||
)}
|
)}
|
||||||
|
@click=${this._addActionDialog}
|
||||||
>
|
>
|
||||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||||
</ha-button>
|
</ha-button>
|
||||||
${this._clipboard?.action
|
<ha-button
|
||||||
? html` <mwc-list-item .value=${PASTE_VALUE} graphic="icon">
|
.disabled=${this.disabled}
|
||||||
${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.actions.paste"
|
"ui.panel.config.automation.editor.actions.add_building_block"
|
||||||
)}
|
)}
|
||||||
(${this.hass.localize(
|
@click=${this._addActionBuildingBlockDialog}
|
||||||
`ui.panel.config.automation.editor.actions.type.${
|
>
|
||||||
getType(this._clipboard.action) || "unknown"
|
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||||
}.label`
|
</ha-button>
|
||||||
)})
|
</div>
|
||||||
<ha-svg-icon slot="graphic" .path=${mdiContentPaste}></ha-svg-icon
|
|
||||||
></mwc-list-item>`
|
|
||||||
: nothing}
|
|
||||||
${this._processedTypes(this.hass.localize).map(
|
|
||||||
([opt, label, icon]) => html`
|
|
||||||
<mwc-list-item .value=${opt} graphic="icon">
|
|
||||||
${label}<ha-svg-icon slot="graphic" .path=${icon}></ha-svg-icon
|
|
||||||
></mwc-list-item>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</ha-button-menu>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,6 +167,43 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _addActionDialog() {
|
||||||
|
showAddAutomationElementDialog(this, {
|
||||||
|
type: "action",
|
||||||
|
add: this._addAction,
|
||||||
|
clipboardItem: getType(this._clipboard?.action),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addActionBuildingBlockDialog() {
|
||||||
|
showAddAutomationElementDialog(this, {
|
||||||
|
type: "action",
|
||||||
|
add: this._addAction,
|
||||||
|
clipboardItem: getType(this._clipboard?.action),
|
||||||
|
group: "building_blocks",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addAction = (action: string) => {
|
||||||
|
let actions: Action[];
|
||||||
|
if (action === PASTE_VALUE) {
|
||||||
|
actions = this.actions.concat(deepClone(this._clipboard!.action));
|
||||||
|
} else if (isService(action)) {
|
||||||
|
actions = this.actions.concat({
|
||||||
|
service: getService(action),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const elClass = customElements.get(
|
||||||
|
`ha-automation-action-${action}`
|
||||||
|
) as CustomElementConstructor & { defaultConfig: Action };
|
||||||
|
actions = this.actions.concat(
|
||||||
|
elClass ? { ...elClass.defaultConfig } : { [action]: {} }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this._focusLastActionOnChange = true;
|
||||||
|
fireEvent(this, "value-changed", { value: actions });
|
||||||
|
};
|
||||||
|
|
||||||
private async _enterReOrderMode(ev: CustomEvent) {
|
private async _enterReOrderMode(ev: CustomEvent) {
|
||||||
if (this.nested) return;
|
if (this.nested) return;
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
@ -258,25 +249,6 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
return this._actionKeys.get(action)!;
|
return this._actionKeys.get(action)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addAction(ev: CustomEvent<ActionDetail>) {
|
|
||||||
const action = (ev.currentTarget as HaSelect).items[ev.detail.index].value;
|
|
||||||
|
|
||||||
let actions: Action[];
|
|
||||||
if (action === PASTE_VALUE) {
|
|
||||||
actions = this.actions.concat(deepClone(this._clipboard!.action));
|
|
||||||
} else {
|
|
||||||
const elClass = customElements.get(
|
|
||||||
`ha-automation-action-${action}`
|
|
||||||
) as CustomElementConstructor & { defaultConfig: Action };
|
|
||||||
|
|
||||||
actions = this.actions.concat(
|
|
||||||
elClass ? { ...elClass.defaultConfig } : { [action]: {} }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this._focusLastActionOnChange = true;
|
|
||||||
fireEvent(this, "value-changed", { value: actions });
|
|
||||||
}
|
|
||||||
|
|
||||||
private _moveUp(ev) {
|
private _moveUp(ev) {
|
||||||
const index = (ev.target as any).index;
|
const index = (ev.target as any).index;
|
||||||
const newIndex = index - 1;
|
const newIndex = index - 1;
|
||||||
@ -328,22 +300,6 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _processedTypes = memoizeOne(
|
|
||||||
(localize: LocalizeFunc): [string, string, string][] =>
|
|
||||||
(Object.entries(ACTION_TYPES) as Entries<typeof ACTION_TYPES>)
|
|
||||||
.map(
|
|
||||||
([action, icon]) =>
|
|
||||||
[
|
|
||||||
action,
|
|
||||||
localize(
|
|
||||||
`ui.panel.config.automation.editor.actions.type.${action}.label`
|
|
||||||
),
|
|
||||||
icon,
|
|
||||||
] as [string, string, string]
|
|
||||||
)
|
|
||||||
.sort((a, b) => stringCompare(a[1], b[1], this.hass.locale.language))
|
|
||||||
);
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
sortableStyles,
|
sortableStyles,
|
||||||
@ -370,6 +326,11 @@ export default class HaAutomationAction extends LitElement {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
}
|
}
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
|||||||
import "../../../../../components/ha-select";
|
import "../../../../../components/ha-select";
|
||||||
import type { HaSelect } from "../../../../../components/ha-select";
|
import type { HaSelect } from "../../../../../components/ha-select";
|
||||||
import type { Condition } from "../../../../../data/automation";
|
import type { Condition } from "../../../../../data/automation";
|
||||||
import { CONDITION_TYPES } from "../../../../../data/condition";
|
import { CONDITION_ICONS } from "../../../../../data/condition";
|
||||||
import { Entries, HomeAssistant } from "../../../../../types";
|
import { Entries, HomeAssistant } from "../../../../../types";
|
||||||
import "../../condition/ha-automation-condition-editor";
|
import "../../condition/ha-automation-condition-editor";
|
||||||
import type { ActionElement } from "../ha-automation-action-row";
|
import type { ActionElement } from "../ha-automation-action-row";
|
||||||
@ -55,7 +55,7 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
|||||||
|
|
||||||
private _processedTypes = memoizeOne(
|
private _processedTypes = memoizeOne(
|
||||||
(localize: LocalizeFunc): [string, string, string][] =>
|
(localize: LocalizeFunc): [string, string, string][] =>
|
||||||
(Object.entries(CONDITION_TYPES) as Entries<typeof CONDITION_TYPES>)
|
(Object.entries(CONDITION_ICONS) as Entries<typeof CONDITION_ICONS>)
|
||||||
.map(
|
.map(
|
||||||
([condition, icon]) =>
|
([condition, icon]) =>
|
||||||
[
|
[
|
||||||
|
579
src/panels/config/automation/add-automation-element-dialog.ts
Normal file
579
src/panels/config/automation/add-automation-element-dialog.ts
Normal file
@ -0,0 +1,579 @@
|
|||||||
|
import "@material/mwc-list/mwc-list";
|
||||||
|
import { mdiClose, mdiContentPaste, mdiPlus } from "@mdi/js";
|
||||||
|
import Fuse, { IFuseOptions } from "fuse.js";
|
||||||
|
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||||
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
|
import { repeat } from "lit/directives/repeat";
|
||||||
|
import { styleMap } from "lit/directives/style-map";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import { domainIcon } from "../../../common/entity/domain_icon";
|
||||||
|
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||||
|
import { stringCompare } from "../../../common/string/compare";
|
||||||
|
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||||
|
import "../../../components/ha-dialog";
|
||||||
|
import type { HaDialog } from "../../../components/ha-dialog";
|
||||||
|
import "../../../components/ha-dialog-header";
|
||||||
|
import "../../../components/ha-icon-button";
|
||||||
|
import "../../../components/ha-icon-button-prev";
|
||||||
|
import "../../../components/ha-icon-next";
|
||||||
|
import "../../../components/ha-list-item";
|
||||||
|
import "../../../components/search-input";
|
||||||
|
import {
|
||||||
|
ACTION_GROUPS,
|
||||||
|
ACTION_ICONS,
|
||||||
|
SERVICE_PREFIX,
|
||||||
|
getService,
|
||||||
|
isService,
|
||||||
|
} from "../../../data/action";
|
||||||
|
import { AutomationElementGroup } from "../../../data/automation";
|
||||||
|
import { CONDITION_GROUPS, CONDITION_ICONS } from "../../../data/condition";
|
||||||
|
import {
|
||||||
|
IntegrationManifest,
|
||||||
|
domainToName,
|
||||||
|
fetchIntegrationManifests,
|
||||||
|
} from "../../../data/integration";
|
||||||
|
import { TRIGGER_GROUPS, TRIGGER_ICONS } from "../../../data/trigger";
|
||||||
|
import { HassDialog } from "../../../dialogs/make-dialog-manager";
|
||||||
|
import { haStyle, haStyleDialog } from "../../../resources/styles";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import {
|
||||||
|
AddAutomationElementDialogParams,
|
||||||
|
PASTE_VALUE,
|
||||||
|
} from "./show-add-automation-element-dialog";
|
||||||
|
|
||||||
|
const TYPES = {
|
||||||
|
trigger: { groups: TRIGGER_GROUPS, icons: TRIGGER_ICONS },
|
||||||
|
condition: {
|
||||||
|
groups: CONDITION_GROUPS,
|
||||||
|
icons: CONDITION_ICONS,
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
groups: ACTION_GROUPS,
|
||||||
|
icons: ACTION_ICONS,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ListItem {
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
group: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DomainManifestLookup {
|
||||||
|
[domain: string]: IntegrationManifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ENTITY_DOMAINS_OTHER = new Set([
|
||||||
|
"date",
|
||||||
|
"datetime",
|
||||||
|
"device_tracker",
|
||||||
|
"text",
|
||||||
|
"time",
|
||||||
|
"tts",
|
||||||
|
"update",
|
||||||
|
"weather",
|
||||||
|
"image_processing",
|
||||||
|
]);
|
||||||
|
|
||||||
|
@customElement("add-automation-element-dialog")
|
||||||
|
class DialogAddAutomationElement extends LitElement implements HassDialog {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _params?: AddAutomationElementDialogParams;
|
||||||
|
|
||||||
|
@state() private _group?: string;
|
||||||
|
|
||||||
|
@state() private _prev?: string;
|
||||||
|
|
||||||
|
@state() private _filter = "";
|
||||||
|
|
||||||
|
@state() private _manifests?: DomainManifestLookup;
|
||||||
|
|
||||||
|
@query("ha-dialog") private _dialog?: HaDialog;
|
||||||
|
|
||||||
|
private _fullScreen = false;
|
||||||
|
|
||||||
|
private _width?: number;
|
||||||
|
|
||||||
|
private _height?: number;
|
||||||
|
|
||||||
|
public showDialog(params): void {
|
||||||
|
this._params = params;
|
||||||
|
this._group = params.group;
|
||||||
|
if (this._params?.type === "action") {
|
||||||
|
this.hass.loadBackendTranslation("services");
|
||||||
|
this._fetchManifests();
|
||||||
|
}
|
||||||
|
this._fullScreen = matchMedia(
|
||||||
|
"all and (max-width: 450px), all and (max-height: 500px)"
|
||||||
|
).matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialog(): void {
|
||||||
|
if (this._params) {
|
||||||
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
|
}
|
||||||
|
this._height = undefined;
|
||||||
|
this._width = undefined;
|
||||||
|
this._params = undefined;
|
||||||
|
this._group = undefined;
|
||||||
|
this._prev = undefined;
|
||||||
|
this._filter = "";
|
||||||
|
this._manifests = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _convertToItem = (
|
||||||
|
key: string,
|
||||||
|
options,
|
||||||
|
type: AddAutomationElementDialogParams["type"],
|
||||||
|
localize: LocalizeFunc
|
||||||
|
): ListItem => ({
|
||||||
|
group: Boolean(options.members),
|
||||||
|
key,
|
||||||
|
name: localize(
|
||||||
|
// @ts-ignore
|
||||||
|
`ui.panel.config.automation.editor.${type}s.${
|
||||||
|
options.members ? "groups" : "type"
|
||||||
|
}.${key}.label`
|
||||||
|
),
|
||||||
|
description: localize(
|
||||||
|
// @ts-ignore
|
||||||
|
`ui.panel.config.automation.editor.${type}s.${
|
||||||
|
options.members ? "groups" : "type"
|
||||||
|
}.${key}.description${options.members ? "" : ".picker"}`
|
||||||
|
),
|
||||||
|
icon: options.icon || TYPES[type].icons[key],
|
||||||
|
});
|
||||||
|
|
||||||
|
private _getFilteredItems = memoizeOne(
|
||||||
|
(
|
||||||
|
type: AddAutomationElementDialogParams["type"],
|
||||||
|
group: string | undefined,
|
||||||
|
filter: string,
|
||||||
|
localize: LocalizeFunc,
|
||||||
|
services: HomeAssistant["services"],
|
||||||
|
manifests?: DomainManifestLookup
|
||||||
|
): ListItem[] => {
|
||||||
|
const groups: AutomationElementGroup = group
|
||||||
|
? isService(group)
|
||||||
|
? {}
|
||||||
|
: TYPES[type].groups[group].members!
|
||||||
|
: TYPES[type].groups;
|
||||||
|
|
||||||
|
const flattenGroups = (grp: AutomationElementGroup) =>
|
||||||
|
Object.entries(grp).map(([key, options]) =>
|
||||||
|
options.members
|
||||||
|
? flattenGroups(options.members)
|
||||||
|
: this._convertToItem(key, options, type, localize)
|
||||||
|
);
|
||||||
|
|
||||||
|
const items = flattenGroups(groups).flat();
|
||||||
|
|
||||||
|
if (type === "action") {
|
||||||
|
items.push(...this._services(localize, services, manifests, group));
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: IFuseOptions<ListItem> = {
|
||||||
|
keys: ["key", "name", "description"],
|
||||||
|
isCaseSensitive: false,
|
||||||
|
minMatchCharLength: Math.min(filter.length, 2),
|
||||||
|
threshold: 0.2,
|
||||||
|
};
|
||||||
|
const fuse = new Fuse(items, options);
|
||||||
|
return fuse.search(filter).map((result) => result.item);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private _getGroupItems = memoizeOne(
|
||||||
|
(
|
||||||
|
type: AddAutomationElementDialogParams["type"],
|
||||||
|
group: string | undefined,
|
||||||
|
localize: LocalizeFunc,
|
||||||
|
services: HomeAssistant["services"],
|
||||||
|
manifests?: DomainManifestLookup
|
||||||
|
): ListItem[] => {
|
||||||
|
if (type === "action" && isService(group)) {
|
||||||
|
const result = this._services(localize, services, manifests, group);
|
||||||
|
if (group === "service_media_player") {
|
||||||
|
result.unshift(this._convertToItem("play_media", {}, type, localize));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const groups: AutomationElementGroup = group
|
||||||
|
? TYPES[type].groups[group].members!
|
||||||
|
: TYPES[type].groups;
|
||||||
|
|
||||||
|
const result = Object.entries(groups).map(([key, options]) =>
|
||||||
|
this._convertToItem(key, options, type, localize)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (type === "action") {
|
||||||
|
if (!this._group) {
|
||||||
|
result.unshift(
|
||||||
|
...this._serviceGroups(localize, services, manifests, undefined)
|
||||||
|
);
|
||||||
|
} else if (this._group === "helpers") {
|
||||||
|
result.unshift(
|
||||||
|
...this._serviceGroups(localize, services, manifests, "helper")
|
||||||
|
);
|
||||||
|
} else if (this._group === "other") {
|
||||||
|
result.unshift(
|
||||||
|
...this._serviceGroups(localize, services, manifests, "other")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.sort((a, b) => {
|
||||||
|
if (a.group && b.group) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (a.group && !b.group) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (!a.group && b.group) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return stringCompare(a.name, b.name, this.hass.locale.language);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private _serviceGroups = memoizeOne(
|
||||||
|
(
|
||||||
|
localize: LocalizeFunc,
|
||||||
|
services: HomeAssistant["services"],
|
||||||
|
manifests: DomainManifestLookup | undefined,
|
||||||
|
type: "helper" | "other" | undefined
|
||||||
|
): ListItem[] => {
|
||||||
|
if (!services || !manifests) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const result: ListItem[] = [];
|
||||||
|
Object.keys(services)
|
||||||
|
.sort()
|
||||||
|
.forEach((domain) => {
|
||||||
|
const manifest = manifests[domain];
|
||||||
|
if (
|
||||||
|
(type === undefined &&
|
||||||
|
manifest?.integration_type === "entity" &&
|
||||||
|
!ENTITY_DOMAINS_OTHER.has(domain)) ||
|
||||||
|
(type === "helper" && manifest?.integration_type === "helper") ||
|
||||||
|
(type === "other" &&
|
||||||
|
(ENTITY_DOMAINS_OTHER.has(domain) ||
|
||||||
|
!["helper", "entity"].includes(
|
||||||
|
manifest?.integration_type || ""
|
||||||
|
)))
|
||||||
|
) {
|
||||||
|
result.push({
|
||||||
|
group: true,
|
||||||
|
icon: domainIcon(domain),
|
||||||
|
key: `${SERVICE_PREFIX}${domain}`,
|
||||||
|
name: domainToName(localize, domain, manifest),
|
||||||
|
description: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private _services = memoizeOne(
|
||||||
|
(
|
||||||
|
localize: LocalizeFunc,
|
||||||
|
services: HomeAssistant["services"],
|
||||||
|
manifests: DomainManifestLookup | undefined,
|
||||||
|
group?: string
|
||||||
|
): ListItem[] => {
|
||||||
|
if (!services) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const result: ListItem[] = [];
|
||||||
|
|
||||||
|
let domain: string | undefined;
|
||||||
|
|
||||||
|
if (isService(group)) {
|
||||||
|
domain = getService(group!);
|
||||||
|
}
|
||||||
|
|
||||||
|
const addDomain = (dmn: string) => {
|
||||||
|
const services_keys = Object.keys(services[dmn]);
|
||||||
|
|
||||||
|
for (const service of services_keys) {
|
||||||
|
result.push({
|
||||||
|
group: false,
|
||||||
|
icon: domainIcon(dmn),
|
||||||
|
key: `${SERVICE_PREFIX}${dmn}.${service}`,
|
||||||
|
name: `${domain ? "" : `${domainToName(localize, dmn)}: `}${
|
||||||
|
this.hass.localize(`component.${dmn}.services.${service}.name`) ||
|
||||||
|
services[dmn][service]?.name ||
|
||||||
|
service
|
||||||
|
}`,
|
||||||
|
description:
|
||||||
|
this.hass.localize(
|
||||||
|
`component.${domain}.services.${service}.description`
|
||||||
|
) || services[dmn][service]?.description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (domain) {
|
||||||
|
addDomain(domain);
|
||||||
|
return result.sort((a, b) =>
|
||||||
|
stringCompare(a.name, b.name, this.hass.locale.language)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group && !["helpers", "other"].includes(group)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(services)
|
||||||
|
.sort()
|
||||||
|
.forEach((dmn) => {
|
||||||
|
const manifest = manifests?.[dmn];
|
||||||
|
if (group === "helpers" && manifest?.integration_type !== "helper") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
group === "other" &&
|
||||||
|
(ENTITY_DOMAINS_OTHER.has(dmn) ||
|
||||||
|
["helper", "entity"].includes(manifest?.integration_type || ""))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addDomain(dmn);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private async _fetchManifests() {
|
||||||
|
const manifests = {};
|
||||||
|
const fetched = await fetchIntegrationManifests(this.hass);
|
||||||
|
for (const manifest of fetched) {
|
||||||
|
manifests[manifest.domain] = manifest;
|
||||||
|
}
|
||||||
|
this._manifests = manifests;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _opened(): void {
|
||||||
|
// Store the width and height so that when we search, box doesn't jump
|
||||||
|
const boundingRect =
|
||||||
|
this.shadowRoot!.querySelector("mwc-list")?.getBoundingClientRect();
|
||||||
|
this._width = boundingRect?.width;
|
||||||
|
this._height = boundingRect?.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this._params) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = this._filter
|
||||||
|
? this._getFilteredItems(
|
||||||
|
this._params.type,
|
||||||
|
this._group,
|
||||||
|
this._filter,
|
||||||
|
this.hass.localize,
|
||||||
|
this.hass.services,
|
||||||
|
this._manifests
|
||||||
|
)
|
||||||
|
: this._getGroupItems(
|
||||||
|
this._params.type,
|
||||||
|
this._group,
|
||||||
|
this.hass.localize,
|
||||||
|
this.hass.services,
|
||||||
|
this._manifests
|
||||||
|
);
|
||||||
|
|
||||||
|
const groupName = isService(this._group)
|
||||||
|
? domainToName(
|
||||||
|
this.hass.localize,
|
||||||
|
getService(this._group!),
|
||||||
|
this._manifests?.[getService(this._group!)]
|
||||||
|
)
|
||||||
|
: this.hass.localize(
|
||||||
|
// @ts-ignore
|
||||||
|
`ui.panel.config.automation.editor.${this._params.type}s.groups.${this._group}.label`
|
||||||
|
);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-dialog
|
||||||
|
open
|
||||||
|
hideActions
|
||||||
|
@opened=${this._opened}
|
||||||
|
@closed=${this.closeDialog}
|
||||||
|
.heading=${true}
|
||||||
|
>
|
||||||
|
<div slot="heading">
|
||||||
|
<ha-dialog-header>
|
||||||
|
<span slot="title"
|
||||||
|
>${this._group
|
||||||
|
? groupName
|
||||||
|
: this.hass.localize(
|
||||||
|
`ui.panel.config.automation.editor.${this._params.type}s.add`
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
${this._group && this._group !== this._params.group
|
||||||
|
? html`<ha-icon-button-prev
|
||||||
|
slot="navigationIcon"
|
||||||
|
@click=${this._back}
|
||||||
|
></ha-icon-button-prev>`
|
||||||
|
: html`<ha-icon-button
|
||||||
|
.path=${mdiClose}
|
||||||
|
slot="navigationIcon"
|
||||||
|
dialogAction="cancel"
|
||||||
|
></ha-icon-button>`}
|
||||||
|
</ha-dialog-header>
|
||||||
|
<search-input
|
||||||
|
dialogInitialFocus=${ifDefined(this._fullScreen ? undefined : "")}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.filter=${this._filter}
|
||||||
|
@value-changed=${this._filterChanged}
|
||||||
|
.label=${groupName
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.search_in",
|
||||||
|
{ group: groupName }
|
||||||
|
)
|
||||||
|
: this.hass.localize(
|
||||||
|
`ui.panel.config.automation.editor.${this._params.type}s.search`
|
||||||
|
)}
|
||||||
|
></search-input>
|
||||||
|
</div>
|
||||||
|
<mwc-list
|
||||||
|
dialogInitialFocus=${ifDefined(this._fullScreen ? "" : undefined)}
|
||||||
|
innerRole="listbox"
|
||||||
|
itemRoles="option"
|
||||||
|
rootTabbable
|
||||||
|
style=${styleMap({
|
||||||
|
width: `${this._width}px`,
|
||||||
|
height: `${this._height}px`,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
${this._params.clipboardItem &&
|
||||||
|
!this._filter &&
|
||||||
|
(!this._group ||
|
||||||
|
items.find((item) => item.key === this._params!.clipboardItem))
|
||||||
|
? html`<ha-list-item
|
||||||
|
twoline
|
||||||
|
class="paste"
|
||||||
|
.value=${PASTE_VALUE}
|
||||||
|
graphic="icon"
|
||||||
|
hasMeta
|
||||||
|
@request-selected=${this._selected}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.automation.editor.${this._params.type}s.paste`
|
||||||
|
)}
|
||||||
|
<span slot="secondary"
|
||||||
|
>${this.hass.localize(
|
||||||
|
// @ts-ignore
|
||||||
|
`ui.panel.config.automation.editor.${this._params.type}s.type.${this._params.clipboardItem}.label`
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="graphic"
|
||||||
|
.path=${mdiContentPaste}
|
||||||
|
></ha-svg-icon
|
||||||
|
><ha-svg-icon slot="meta" .path=${mdiPlus}></ha-svg-icon>
|
||||||
|
</ha-list-item>
|
||||||
|
<li divider role="separator"></li>`
|
||||||
|
: ""}
|
||||||
|
${repeat(
|
||||||
|
items,
|
||||||
|
(item) => item.key,
|
||||||
|
(item) => html`
|
||||||
|
<ha-list-item
|
||||||
|
.twoline=${Boolean(item.description)}
|
||||||
|
.value=${item.key}
|
||||||
|
.group=${item.group}
|
||||||
|
graphic="icon"
|
||||||
|
hasMeta
|
||||||
|
@request-selected=${this._selected}
|
||||||
|
>
|
||||||
|
${item.name}
|
||||||
|
<span slot="secondary">${item.description}</span>
|
||||||
|
<ha-svg-icon slot="graphic" .path=${item.icon}></ha-svg-icon>
|
||||||
|
${item.group
|
||||||
|
? html`<ha-icon-next slot="meta"></ha-icon-next>`
|
||||||
|
: html`<ha-svg-icon
|
||||||
|
slot="meta"
|
||||||
|
.path=${mdiPlus}
|
||||||
|
></ha-svg-icon>`}
|
||||||
|
</ha-list-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</mwc-list>
|
||||||
|
</ha-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _back() {
|
||||||
|
if (this._filter) {
|
||||||
|
this._filter = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this._prev) {
|
||||||
|
this._group = this._prev;
|
||||||
|
this._prev = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._group = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _selected(ev) {
|
||||||
|
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._dialog!.scrollToPos(0, 0);
|
||||||
|
const item = ev.currentTarget;
|
||||||
|
if (item.group) {
|
||||||
|
this._prev = this._group;
|
||||||
|
this._group = item.value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._params!.add(item.value);
|
||||||
|
this.closeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _filterChanged(ev) {
|
||||||
|
this._filter = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
ha-dialog {
|
||||||
|
--dialog-content-padding: 0;
|
||||||
|
--mdc-dialog-max-height: 60vh;
|
||||||
|
}
|
||||||
|
@media all and (min-width: 550px) {
|
||||||
|
ha-dialog {
|
||||||
|
--mdc-dialog-min-width: 500px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ha-icon-next {
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
search-input {
|
||||||
|
display: block;
|
||||||
|
margin: 0 16px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"add-automation-element-dialog": DialogAddAutomationElement;
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,7 @@ import "../../../../components/ha-icon-button";
|
|||||||
import type { AutomationClipboard } from "../../../../data/automation";
|
import type { AutomationClipboard } from "../../../../data/automation";
|
||||||
import { Condition, testCondition } from "../../../../data/automation";
|
import { Condition, testCondition } from "../../../../data/automation";
|
||||||
import { describeCondition } from "../../../../data/automation_i18n";
|
import { describeCondition } from "../../../../data/automation_i18n";
|
||||||
import { CONDITION_TYPES } from "../../../../data/condition";
|
import { CONDITION_ICONS } from "../../../../data/condition";
|
||||||
import { validateConfig } from "../../../../data/config";
|
import { validateConfig } from "../../../../data/config";
|
||||||
import { fullEntitiesContext } from "../../../../data/context";
|
import { fullEntitiesContext } from "../../../../data/context";
|
||||||
import { EntityRegistryEntry } from "../../../../data/entity_registry";
|
import { EntityRegistryEntry } from "../../../../data/entity_registry";
|
||||||
@ -123,7 +123,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
<h3 slot="header">
|
<h3 slot="header">
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
class="condition-icon"
|
class="condition-icon"
|
||||||
.path=${CONDITION_TYPES[this.condition.condition]}
|
.path=${CONDITION_ICONS[this.condition.condition]}
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
${capitalizeFirstLetter(
|
${capitalizeFirstLetter(
|
||||||
describeCondition(this.condition, this.hass, this._entityReg)
|
describeCondition(this.condition, this.hass, this._entityReg)
|
||||||
|
@ -1,25 +1,18 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import type { ActionDetail } from "@material/mwc-list";
|
import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js";
|
||||||
import {
|
|
||||||
mdiArrowDown,
|
|
||||||
mdiArrowUp,
|
|
||||||
mdiContentPaste,
|
|
||||||
mdiDrag,
|
|
||||||
mdiPlus,
|
|
||||||
} from "@mdi/js";
|
|
||||||
import deepClone from "deep-clone-simple";
|
import deepClone from "deep-clone-simple";
|
||||||
import {
|
import {
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
|
||||||
LitElement,
|
LitElement,
|
||||||
nothing,
|
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
|
nothing,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { repeat } from "lit/directives/repeat";
|
import { repeat } from "lit/directives/repeat";
|
||||||
import memoizeOne from "memoize-one";
|
|
||||||
import type { SortableEvent } from "sortablejs";
|
import type { SortableEvent } from "sortablejs";
|
||||||
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import "../../../../components/ha-button";
|
import "../../../../components/ha-button";
|
||||||
import "../../../../components/ha-button-menu";
|
import "../../../../components/ha-button-menu";
|
||||||
@ -28,30 +21,15 @@ import type {
|
|||||||
AutomationClipboard,
|
AutomationClipboard,
|
||||||
Condition,
|
Condition,
|
||||||
} from "../../../../data/automation";
|
} from "../../../../data/automation";
|
||||||
import type { Entries, HomeAssistant } from "../../../../types";
|
|
||||||
import "./ha-automation-condition-row";
|
|
||||||
import type HaAutomationConditionRow from "./ha-automation-condition-row";
|
|
||||||
// Uncommenting these and this element doesn't load
|
|
||||||
// import "./types/ha-automation-condition-not";
|
|
||||||
// import "./types/ha-automation-condition-or";
|
|
||||||
import { storage } from "../../../../common/decorators/storage";
|
|
||||||
import { stringCompare } from "../../../../common/string/compare";
|
|
||||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
|
||||||
import type { HaSelect } from "../../../../components/ha-select";
|
|
||||||
import { CONDITION_TYPES } from "../../../../data/condition";
|
|
||||||
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||||
import type { SortableInstance } from "../../../../resources/sortable";
|
import type { SortableInstance } from "../../../../resources/sortable";
|
||||||
import "./types/ha-automation-condition-and";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import "./types/ha-automation-condition-device";
|
import {
|
||||||
import "./types/ha-automation-condition-numeric_state";
|
PASTE_VALUE,
|
||||||
import "./types/ha-automation-condition-state";
|
showAddAutomationElementDialog,
|
||||||
import "./types/ha-automation-condition-sun";
|
} from "../show-add-automation-element-dialog";
|
||||||
import "./types/ha-automation-condition-template";
|
import "./ha-automation-condition-row";
|
||||||
import "./types/ha-automation-condition-time";
|
import type HaAutomationConditionRow from "./ha-automation-condition-row";
|
||||||
import "./types/ha-automation-condition-trigger";
|
|
||||||
import "./types/ha-automation-condition-zone";
|
|
||||||
|
|
||||||
const PASTE_VALUE = "__paste__";
|
|
||||||
|
|
||||||
@customElement("ha-automation-condition")
|
@customElement("ha-automation-condition")
|
||||||
export default class HaAutomationCondition extends LitElement {
|
export default class HaAutomationCondition extends LitElement {
|
||||||
@ -197,43 +175,69 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ha-button-menu
|
<div class="buttons">
|
||||||
@action=${this._addCondition}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
fixed
|
|
||||||
>
|
|
||||||
<ha-button
|
<ha-button
|
||||||
slot="trigger"
|
|
||||||
outlined
|
outlined
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.conditions.add"
|
"ui.panel.config.automation.editor.conditions.add"
|
||||||
)}
|
)}
|
||||||
|
@click=${this._addConditionDialog}
|
||||||
>
|
>
|
||||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||||
</ha-button>
|
</ha-button>
|
||||||
${this._clipboard?.condition
|
<ha-button
|
||||||
? html` <mwc-list-item .value=${PASTE_VALUE} graphic="icon">
|
.disabled=${this.disabled}
|
||||||
${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.conditions.paste"
|
"ui.panel.config.automation.editor.conditions.add_building_block"
|
||||||
)}
|
)}
|
||||||
(${this.hass.localize(
|
@click=${this._addConditionBuildingBlockDialog}
|
||||||
`ui.panel.config.automation.editor.conditions.type.${this._clipboard.condition.condition}.label`
|
>
|
||||||
)})
|
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||||
<ha-svg-icon slot="graphic" .path=${mdiContentPaste}></ha-svg-icon
|
</ha-button>
|
||||||
></mwc-list-item>`
|
</div>
|
||||||
: nothing}
|
|
||||||
${this._processedTypes(this.hass.localize).map(
|
|
||||||
([opt, label, icon]) => html`
|
|
||||||
<mwc-list-item .value=${opt} graphic="icon">
|
|
||||||
${label}<ha-svg-icon slot="graphic" .path=${icon}></ha-svg-icon
|
|
||||||
></mwc-list-item>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</ha-button-menu>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _addConditionDialog() {
|
||||||
|
showAddAutomationElementDialog(this, {
|
||||||
|
type: "condition",
|
||||||
|
add: this._addCondition,
|
||||||
|
clipboardItem: this._clipboard?.condition?.condition,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addConditionBuildingBlockDialog() {
|
||||||
|
showAddAutomationElementDialog(this, {
|
||||||
|
type: "condition",
|
||||||
|
add: this._addCondition,
|
||||||
|
clipboardItem: this._clipboard?.condition?.condition,
|
||||||
|
group: "building_blocks",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addCondition = (value) => {
|
||||||
|
let conditions: Condition[];
|
||||||
|
if (value === PASTE_VALUE) {
|
||||||
|
conditions = this.conditions.concat(
|
||||||
|
deepClone(this._clipboard!.condition)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const condition = value as Condition["condition"];
|
||||||
|
const elClass = customElements.get(
|
||||||
|
`ha-automation-condition-${condition}`
|
||||||
|
) as CustomElementConstructor & {
|
||||||
|
defaultConfig: Omit<Condition, "condition">;
|
||||||
|
};
|
||||||
|
conditions = this.conditions.concat({
|
||||||
|
condition: condition as any,
|
||||||
|
...elClass.defaultConfig,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._focusLastConditionOnChange = true;
|
||||||
|
fireEvent(this, "value-changed", { value: conditions });
|
||||||
|
};
|
||||||
|
|
||||||
private async _enterReOrderMode(ev: CustomEvent) {
|
private async _enterReOrderMode(ev: CustomEvent) {
|
||||||
if (this.nested) return;
|
if (this.nested) return;
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
@ -282,32 +286,6 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
return this._conditionKeys.get(condition)!;
|
return this._conditionKeys.get(condition)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addCondition(ev: CustomEvent<ActionDetail>) {
|
|
||||||
const value = (ev.currentTarget as HaSelect).items[ev.detail.index].value;
|
|
||||||
|
|
||||||
let conditions: Condition[];
|
|
||||||
if (value === PASTE_VALUE) {
|
|
||||||
conditions = this.conditions.concat(
|
|
||||||
deepClone(this._clipboard!.condition)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const condition = value as Condition["condition"];
|
|
||||||
|
|
||||||
const elClass = customElements.get(
|
|
||||||
`ha-automation-condition-${condition}`
|
|
||||||
) as CustomElementConstructor & {
|
|
||||||
defaultConfig: Omit<Condition, "condition">;
|
|
||||||
};
|
|
||||||
|
|
||||||
conditions = this.conditions.concat({
|
|
||||||
condition: condition as any,
|
|
||||||
...elClass.defaultConfig,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this._focusLastConditionOnChange = true;
|
|
||||||
fireEvent(this, "value-changed", { value: conditions });
|
|
||||||
}
|
|
||||||
|
|
||||||
private _moveUp(ev) {
|
private _moveUp(ev) {
|
||||||
const index = (ev.target as any).index;
|
const index = (ev.target as any).index;
|
||||||
const newIndex = index - 1;
|
const newIndex = index - 1;
|
||||||
@ -361,22 +339,6 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _processedTypes = memoizeOne(
|
|
||||||
(localize: LocalizeFunc): [string, string, string][] =>
|
|
||||||
(Object.entries(CONDITION_TYPES) as Entries<typeof CONDITION_TYPES>)
|
|
||||||
.map(
|
|
||||||
([condition, icon]) =>
|
|
||||||
[
|
|
||||||
condition,
|
|
||||||
localize(
|
|
||||||
`ui.panel.config.automation.editor.conditions.type.${condition}.label`
|
|
||||||
),
|
|
||||||
icon,
|
|
||||||
] as [string, string, string]
|
|
||||||
)
|
|
||||||
.sort((a, b) => stringCompare(a[1], b[1], this.hass.locale.language))
|
|
||||||
);
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
sortableStyles,
|
sortableStyles,
|
||||||
@ -403,6 +365,11 @@ export default class HaAutomationCondition extends LitElement {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
}
|
}
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -486,7 +486,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
value.valid
|
value.valid
|
||||||
? ""
|
? ""
|
||||||
: html`${this.hass.localize(
|
: html`${this.hass.localize(
|
||||||
`ui.panel.config.automation.editor.${key}s.header`
|
`ui.panel.config.automation.editor.${key}s.name`
|
||||||
)}:
|
)}:
|
||||||
${value.error}<br />`
|
${value.error}<br />`
|
||||||
);
|
);
|
||||||
|
@ -7,11 +7,19 @@ import {
|
|||||||
mdiPlay,
|
mdiPlay,
|
||||||
mdiPlayCircleOutline,
|
mdiPlayCircleOutline,
|
||||||
mdiPlus,
|
mdiPlus,
|
||||||
|
mdiRobotHappy,
|
||||||
mdiStopCircleOutline,
|
mdiStopCircleOutline,
|
||||||
mdiTransitConnection,
|
mdiTransitConnection,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||||
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
nothing,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { differenceInDays } from "date-fns/esm";
|
import { differenceInDays } from "date-fns/esm";
|
||||||
@ -295,6 +303,7 @@ class HaAutomationPicker extends LitElement {
|
|||||||
.activeFilters=${this._activeFilters}
|
.activeFilters=${this._activeFilters}
|
||||||
.columns=${this._columns(this.narrow, this.hass.locale)}
|
.columns=${this._columns(this.narrow, this.hass.locale)}
|
||||||
.data=${this._automations(this.automations, this._filteredAutomations)}
|
.data=${this._automations(this.automations, this._filteredAutomations)}
|
||||||
|
.empty=${!this.automations.length}
|
||||||
@row-click=${this._handleRowClicked}
|
@row-click=${this._handleRowClicked}
|
||||||
.noDataText=${this.hass.localize(
|
.noDataText=${this.hass.localize(
|
||||||
"ui.panel.config.automation.picker.no_automations"
|
"ui.panel.config.automation.picker.no_automations"
|
||||||
@ -318,6 +327,35 @@ class HaAutomationPicker extends LitElement {
|
|||||||
@related-changed=${this._relatedFilterChanged}
|
@related-changed=${this._relatedFilterChanged}
|
||||||
>
|
>
|
||||||
</ha-button-related-filter-menu>
|
</ha-button-related-filter-menu>
|
||||||
|
${!this.automations.length
|
||||||
|
? html` <div class="empty" slot="empty">
|
||||||
|
<ha-svg-icon .path=${mdiRobotHappy}></ha-svg-icon>
|
||||||
|
<h1>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.picker.empty_header"
|
||||||
|
)}
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.picker.empty_text_1"
|
||||||
|
)}<br />
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.picker.empty_text_2"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href=${documentationUrl(this.hass, "/docs/automation/editor/")}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<ha-button>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.picker.learn_more"
|
||||||
|
)}
|
||||||
|
</ha-button>
|
||||||
|
</a>
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
<ha-fab
|
<ha-fab
|
||||||
slot="fab"
|
slot="fab"
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@ -475,9 +513,7 @@ class HaAutomationPicker extends LitElement {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
${this.hass.localize(
|
${this.hass.localize("ui.panel.config.common.learn_more")}
|
||||||
"ui.panel.config.automation.picker.learn_more"
|
|
||||||
)}
|
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
`,
|
`,
|
||||||
@ -505,7 +541,16 @@ class HaAutomationPicker extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return haStyle;
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
.empty {
|
||||||
|
--paper-font-headline_-_font-size: 28px;
|
||||||
|
--mdc-icon-size: 80px;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,14 @@ import {
|
|||||||
mdiRayStartArrow,
|
mdiRayStartArrow,
|
||||||
mdiRefresh,
|
mdiRefresh,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
nothing,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { repeat } from "lit/directives/repeat";
|
import { repeat } from "lit/directives/repeat";
|
||||||
@ -41,6 +48,8 @@ import { haStyle } from "../../../resources/styles";
|
|||||||
import { HomeAssistant, Route } from "../../../types";
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||||
|
|
||||||
|
const TABS = ["details", "automation_config", "timeline", "logbook"] as const;
|
||||||
|
|
||||||
@customElement("ha-automation-trace")
|
@customElement("ha-automation-trace")
|
||||||
export class HaAutomationTrace extends LitElement {
|
export class HaAutomationTrace extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -67,12 +76,7 @@ export class HaAutomationTrace extends LitElement {
|
|||||||
|
|
||||||
@state() private _logbookEntries?: LogbookEntry[];
|
@state() private _logbookEntries?: LogbookEntry[];
|
||||||
|
|
||||||
@state() private _view:
|
@state() private _view: (typeof TABS)[number] | "blueprint" = "details";
|
||||||
| "details"
|
|
||||||
| "config"
|
|
||||||
| "timeline"
|
|
||||||
| "logbook"
|
|
||||||
| "blueprint" = "details";
|
|
||||||
|
|
||||||
@query("hat-script-graph") private _graph?: HatScriptGraph;
|
@query("hat-script-graph") private _graph?: HatScriptGraph;
|
||||||
|
|
||||||
@ -213,9 +217,15 @@ export class HaAutomationTrace extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
${this._traces === undefined
|
${this._traces === undefined
|
||||||
? html`<div class="container">Loading…</div>`
|
? html`<div class="container">
|
||||||
|
${this.hass!.localize("ui.common.loading")}
|
||||||
|
</div>`
|
||||||
: this._traces.length === 0
|
: this._traces.length === 0
|
||||||
? html`<div class="container">No traces found</div>`
|
? html`<div class="container">
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.config.automation.trace.no_traces_found"
|
||||||
|
)}
|
||||||
|
</div>`
|
||||||
: this._trace === undefined
|
: this._trace === undefined
|
||||||
? ""
|
? ""
|
||||||
: html`
|
: html`
|
||||||
@ -230,20 +240,17 @@ export class HaAutomationTrace extends LitElement {
|
|||||||
|
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<div class="tabs top">
|
<div class="tabs top">
|
||||||
${[
|
${TABS.map(
|
||||||
["details", "Step Details"],
|
(view) => html`
|
||||||
["timeline", "Trace Timeline"],
|
|
||||||
["logbook", "Related logbook entries"],
|
|
||||||
["config", "Automation Config"],
|
|
||||||
].map(
|
|
||||||
([view, label]) => html`
|
|
||||||
<button
|
<button
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
.view=${view}
|
.view=${view}
|
||||||
class=${classMap({ active: this._view === view })}
|
class=${classMap({ active: this._view === view })}
|
||||||
@click=${this._showTab}
|
@click=${this._showTab}
|
||||||
>
|
>
|
||||||
${label}
|
${this.hass!.localize(
|
||||||
|
`ui.panel.config.automation.trace.tabs.${view}`
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
@ -257,7 +264,9 @@ export class HaAutomationTrace extends LitElement {
|
|||||||
})}
|
})}
|
||||||
@click=${this._showTab}
|
@click=${this._showTab}
|
||||||
>
|
>
|
||||||
Blueprint Config
|
${this.hass!.localize(
|
||||||
|
`ui.panel.config.automation.trace.tabs.blueprint_config`
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
@ -265,7 +274,7 @@ export class HaAutomationTrace extends LitElement {
|
|||||||
${this._selected === undefined ||
|
${this._selected === undefined ||
|
||||||
this._logbookEntries === undefined ||
|
this._logbookEntries === undefined ||
|
||||||
trackedNodes === undefined
|
trackedNodes === undefined
|
||||||
? ""
|
? nothing
|
||||||
: this._view === "details"
|
: this._view === "details"
|
||||||
? html`
|
? html`
|
||||||
<ha-trace-path-details
|
<ha-trace-path-details
|
||||||
@ -278,7 +287,7 @@ export class HaAutomationTrace extends LitElement {
|
|||||||
.renderedNodes=${renderedNodes!}
|
.renderedNodes=${renderedNodes!}
|
||||||
></ha-trace-path-details>
|
></ha-trace-path-details>
|
||||||
`
|
`
|
||||||
: this._view === "config"
|
: this._view === "automation_config"
|
||||||
? html`
|
? html`
|
||||||
<ha-trace-config
|
<ha-trace-config
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import { mdiHelpCircle } from "@mdi/js";
|
import { mdiHelpCircle } from "@mdi/js";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { ensureArray } from "../../../common/array/ensure-array";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
|
import "../../../components/ha-markdown";
|
||||||
import {
|
import {
|
||||||
Condition,
|
Condition,
|
||||||
ManualAutomationConfig,
|
ManualAutomationConfig,
|
||||||
@ -83,6 +85,14 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
${!this.hass.userData?.showAdvanced &&
|
||||||
|
!ensureArray(this.config.trigger)?.length
|
||||||
|
? html`<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.triggers.description"
|
||||||
|
)}
|
||||||
|
</p>`
|
||||||
|
: nothing}
|
||||||
|
|
||||||
<ha-automation-trigger
|
<ha-automation-trigger
|
||||||
role="region"
|
role="region"
|
||||||
@ -98,6 +108,9 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.conditions.header"
|
"ui.panel.config.automation.editor.conditions.header"
|
||||||
)}
|
)}
|
||||||
|
<span class="small"
|
||||||
|
>(${this.hass.localize("ui.common.optional")})</span
|
||||||
|
>
|
||||||
</h2>
|
</h2>
|
||||||
<a
|
<a
|
||||||
href=${documentationUrl(this.hass, "/docs/automation/condition/")}
|
href=${documentationUrl(this.hass, "/docs/automation/condition/")}
|
||||||
@ -112,6 +125,15 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
${!this.hass.userData?.showAdvanced &&
|
||||||
|
!ensureArray(this.config.condition)?.length
|
||||||
|
? html`<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.conditions.description",
|
||||||
|
{ user: this.hass.user?.name }
|
||||||
|
)}
|
||||||
|
</p>`
|
||||||
|
: nothing}
|
||||||
|
|
||||||
<ha-automation-condition
|
<ha-automation-condition
|
||||||
role="region"
|
role="region"
|
||||||
@ -143,6 +165,14 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
${!this.hass.userData?.showAdvanced &&
|
||||||
|
!ensureArray(this.config.action)?.length
|
||||||
|
? html`<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.actions.description"
|
||||||
|
)}
|
||||||
|
</p>`
|
||||||
|
: nothing}
|
||||||
|
|
||||||
<ha-automation-action
|
<ha-automation-action
|
||||||
role="region"
|
role="region"
|
||||||
@ -207,9 +237,11 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
margin-bottom: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
.header {
|
.header {
|
||||||
|
margin-top: 16px;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@ -217,13 +249,18 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
margin-top: -16px;
|
margin-top: -16px;
|
||||||
}
|
}
|
||||||
.header .name {
|
.header .name {
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
.header a {
|
.header a {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
.header .small {
|
||||||
|
font-size: small;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
|
||||||
|
export const PASTE_VALUE = "__paste__";
|
||||||
|
|
||||||
|
export interface AddAutomationElementDialogParams {
|
||||||
|
type: "trigger" | "condition" | "action";
|
||||||
|
add: (key: string) => void;
|
||||||
|
clipboardItem: string | undefined;
|
||||||
|
group?: string;
|
||||||
|
}
|
||||||
|
const loadDialog = () => import("./add-automation-element-dialog");
|
||||||
|
|
||||||
|
export const showAddAutomationElementDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: AddAutomationElementDialogParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "add-automation-element-dialog",
|
||||||
|
dialogImport: loadDialog,
|
||||||
|
dialogParams,
|
||||||
|
});
|
||||||
|
};
|
@ -37,7 +37,7 @@ import { describeTrigger } from "../../../../data/automation_i18n";
|
|||||||
import { validateConfig } from "../../../../data/config";
|
import { validateConfig } from "../../../../data/config";
|
||||||
import { fullEntitiesContext } from "../../../../data/context";
|
import { fullEntitiesContext } from "../../../../data/context";
|
||||||
import { EntityRegistryEntry } from "../../../../data/entity_registry";
|
import { EntityRegistryEntry } from "../../../../data/entity_registry";
|
||||||
import { TRIGGER_TYPES } from "../../../../data/trigger";
|
import { TRIGGER_ICONS } from "../../../../data/trigger";
|
||||||
import {
|
import {
|
||||||
showAlertDialog,
|
showAlertDialog,
|
||||||
showConfirmationDialog,
|
showConfirmationDialog,
|
||||||
@ -150,7 +150,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||||||
<h3 slot="header">
|
<h3 slot="header">
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
class="trigger-icon"
|
class="trigger-icon"
|
||||||
.path=${TRIGGER_TYPES[this.trigger.platform]}
|
.path=${TRIGGER_ICONS[this.trigger.platform]}
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
${describeTrigger(this.trigger, this.hass, this._entityReg)}
|
${describeTrigger(this.trigger, this.hass, this._entityReg)}
|
||||||
</h3>
|
</h3>
|
||||||
|
@ -1,59 +1,25 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import type { ActionDetail } from "@material/mwc-list";
|
import { mdiArrowDown, mdiArrowUp, mdiDrag, mdiPlus } from "@mdi/js";
|
||||||
import {
|
|
||||||
mdiArrowDown,
|
|
||||||
mdiArrowUp,
|
|
||||||
mdiContentPaste,
|
|
||||||
mdiDrag,
|
|
||||||
mdiPlus,
|
|
||||||
} from "@mdi/js";
|
|
||||||
import deepClone from "deep-clone-simple";
|
import deepClone from "deep-clone-simple";
|
||||||
import {
|
import { CSSResultGroup, LitElement, PropertyValues, css, html } from "lit";
|
||||||
CSSResultGroup,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
css,
|
|
||||||
html,
|
|
||||||
nothing,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { repeat } from "lit/directives/repeat";
|
import { repeat } from "lit/directives/repeat";
|
||||||
import memoizeOne from "memoize-one";
|
|
||||||
import type { SortableEvent } from "sortablejs";
|
import type { SortableEvent } from "sortablejs";
|
||||||
import { storage } from "../../../../common/decorators/storage";
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { stringCompare } from "../../../../common/string/compare";
|
|
||||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
|
||||||
import "../../../../components/ha-button";
|
import "../../../../components/ha-button";
|
||||||
import "../../../../components/ha-button-menu";
|
import "../../../../components/ha-button-menu";
|
||||||
import type { HaSelect } from "../../../../components/ha-select";
|
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
import { AutomationClipboard, Trigger } from "../../../../data/automation";
|
import { AutomationClipboard, Trigger } from "../../../../data/automation";
|
||||||
import { TRIGGER_TYPES } from "../../../../data/trigger";
|
|
||||||
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||||
import type { SortableInstance } from "../../../../resources/sortable";
|
import type { SortableInstance } from "../../../../resources/sortable";
|
||||||
import { Entries, HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import "./ha-automation-trigger-row";
|
import "./ha-automation-trigger-row";
|
||||||
import type HaAutomationTriggerRow from "./ha-automation-trigger-row";
|
import type HaAutomationTriggerRow from "./ha-automation-trigger-row";
|
||||||
import "./types/ha-automation-trigger-calendar";
|
import {
|
||||||
import "./types/ha-automation-trigger-conversation";
|
PASTE_VALUE,
|
||||||
import "./types/ha-automation-trigger-device";
|
showAddAutomationElementDialog,
|
||||||
import "./types/ha-automation-trigger-event";
|
} from "../show-add-automation-element-dialog";
|
||||||
import "./types/ha-automation-trigger-geo_location";
|
|
||||||
import "./types/ha-automation-trigger-homeassistant";
|
|
||||||
import "./types/ha-automation-trigger-mqtt";
|
|
||||||
import "./types/ha-automation-trigger-numeric_state";
|
|
||||||
import "./types/ha-automation-trigger-persistent_notification";
|
|
||||||
import "./types/ha-automation-trigger-state";
|
|
||||||
import "./types/ha-automation-trigger-sun";
|
|
||||||
import "./types/ha-automation-trigger-tag";
|
|
||||||
import "./types/ha-automation-trigger-template";
|
|
||||||
import "./types/ha-automation-trigger-time";
|
|
||||||
import "./types/ha-automation-trigger-time_pattern";
|
|
||||||
import "./types/ha-automation-trigger-webhook";
|
|
||||||
import "./types/ha-automation-trigger-zone";
|
|
||||||
|
|
||||||
const PASTE_VALUE = "__paste__";
|
|
||||||
|
|
||||||
@customElement("ha-automation-trigger")
|
@customElement("ha-automation-trigger")
|
||||||
export default class HaAutomationTrigger extends LitElement {
|
export default class HaAutomationTrigger extends LitElement {
|
||||||
@ -147,47 +113,48 @@ export default class HaAutomationTrigger extends LitElement {
|
|||||||
</ha-automation-trigger-row>
|
</ha-automation-trigger-row>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
<ha-button-menu
|
<ha-button
|
||||||
@action=${this._addTrigger}
|
outlined
|
||||||
.disabled=${this.disabled}
|
.label=${this.hass.localize(
|
||||||
fixed
|
"ui.panel.config.automation.editor.triggers.add"
|
||||||
>
|
|
||||||
<ha-button
|
|
||||||
slot="trigger"
|
|
||||||
outlined
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.triggers.add"
|
|
||||||
)}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
>
|
|
||||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
|
||||||
</ha-button>
|
|
||||||
${this._clipboard?.trigger
|
|
||||||
? html` <mwc-list-item .value=${PASTE_VALUE} graphic="icon">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.triggers.paste"
|
|
||||||
)}
|
|
||||||
(${this.hass.localize(
|
|
||||||
`ui.panel.config.automation.editor.triggers.type.${this._clipboard.trigger.platform}.label`
|
|
||||||
)})
|
|
||||||
<ha-svg-icon
|
|
||||||
slot="graphic"
|
|
||||||
.path=${mdiContentPaste}
|
|
||||||
></ha-svg-icon
|
|
||||||
></mwc-list-item>`
|
|
||||||
: nothing}
|
|
||||||
${this._processedTypes(this.hass.localize).map(
|
|
||||||
([opt, label, icon]) => html`
|
|
||||||
<mwc-list-item .value=${opt} graphic="icon">
|
|
||||||
${label}<ha-svg-icon slot="graphic" .path=${icon}></ha-svg-icon
|
|
||||||
></mwc-list-item>
|
|
||||||
`
|
|
||||||
)}
|
)}
|
||||||
</ha-button-menu>
|
.disabled=${this.disabled}
|
||||||
|
@click=${this._addTriggerDialog}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||||
|
</ha-button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _addTriggerDialog() {
|
||||||
|
showAddAutomationElementDialog(this, {
|
||||||
|
type: "trigger",
|
||||||
|
add: this._addTrigger,
|
||||||
|
clipboardItem: this._clipboard?.trigger?.platform,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addTrigger = (value: string) => {
|
||||||
|
let triggers: Trigger[];
|
||||||
|
if (value === PASTE_VALUE) {
|
||||||
|
triggers = this.triggers.concat(deepClone(this._clipboard!.trigger));
|
||||||
|
} else {
|
||||||
|
const platform = value as Trigger["platform"];
|
||||||
|
const elClass = customElements.get(
|
||||||
|
`ha-automation-trigger-${platform}`
|
||||||
|
) as CustomElementConstructor & {
|
||||||
|
defaultConfig: Omit<Trigger, "platform">;
|
||||||
|
};
|
||||||
|
triggers = this.triggers.concat({
|
||||||
|
platform: platform as any,
|
||||||
|
...elClass.defaultConfig,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._focusLastTriggerOnChange = true;
|
||||||
|
fireEvent(this, "value-changed", { value: triggers });
|
||||||
|
};
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
protected updated(changedProps: PropertyValues) {
|
||||||
super.updated(changedProps);
|
super.updated(changedProps);
|
||||||
|
|
||||||
@ -261,30 +228,6 @@ export default class HaAutomationTrigger extends LitElement {
|
|||||||
return this._triggerKeys.get(action)!;
|
return this._triggerKeys.get(action)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addTrigger(ev: CustomEvent<ActionDetail>) {
|
|
||||||
const value = (ev.currentTarget as HaSelect).items[ev.detail.index].value;
|
|
||||||
|
|
||||||
let triggers: Trigger[];
|
|
||||||
if (value === PASTE_VALUE) {
|
|
||||||
triggers = this.triggers.concat(deepClone(this._clipboard!.trigger));
|
|
||||||
} else {
|
|
||||||
const platform = value as Trigger["platform"];
|
|
||||||
|
|
||||||
const elClass = customElements.get(
|
|
||||||
`ha-automation-trigger-${platform}`
|
|
||||||
) as CustomElementConstructor & {
|
|
||||||
defaultConfig: Omit<Trigger, "platform">;
|
|
||||||
};
|
|
||||||
|
|
||||||
triggers = this.triggers.concat({
|
|
||||||
platform: platform as any,
|
|
||||||
...elClass.defaultConfig,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this._focusLastTriggerOnChange = true;
|
|
||||||
fireEvent(this, "value-changed", { value: triggers });
|
|
||||||
}
|
|
||||||
|
|
||||||
private _moveUp(ev) {
|
private _moveUp(ev) {
|
||||||
const index = (ev.target as any).index;
|
const index = (ev.target as any).index;
|
||||||
const newIndex = index - 1;
|
const newIndex = index - 1;
|
||||||
@ -336,22 +279,6 @@ export default class HaAutomationTrigger extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _processedTypes = memoizeOne(
|
|
||||||
(localize: LocalizeFunc): [string, string, string][] =>
|
|
||||||
(Object.entries(TRIGGER_TYPES) as Entries<typeof TRIGGER_TYPES>)
|
|
||||||
.map(
|
|
||||||
([action, icon]) =>
|
|
||||||
[
|
|
||||||
action,
|
|
||||||
localize(
|
|
||||||
`ui.panel.config.automation.editor.triggers.type.${action}.label`
|
|
||||||
),
|
|
||||||
icon,
|
|
||||||
] as [string, string, string]
|
|
||||||
)
|
|
||||||
.sort((a, b) => stringCompare(a[1], b[1], this.hass.locale.language))
|
|
||||||
);
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
sortableStyles,
|
sortableStyles,
|
||||||
|
@ -109,9 +109,11 @@ class HaConfigUpdates extends SubscribeMixin(LitElement) {
|
|||||||
${this.narrow && entity.attributes.in_progress
|
${this.narrow && entity.attributes.in_progress
|
||||||
? html`<ha-circular-progress
|
? html`<ha-circular-progress
|
||||||
indeterminate
|
indeterminate
|
||||||
size="small"
|
|
||||||
slot="graphic"
|
slot="graphic"
|
||||||
class="absolute"
|
class="absolute"
|
||||||
|
.ariaLabel=${this.hass.localize(
|
||||||
|
"ui.panel.config.updates.update_in_progress"
|
||||||
|
)}
|
||||||
></ha-circular-progress>`
|
></ha-circular-progress>`
|
||||||
: ""}
|
: ""}
|
||||||
<span
|
<span
|
||||||
@ -131,6 +133,9 @@ class HaConfigUpdates extends SubscribeMixin(LitElement) {
|
|||||||
indeterminate
|
indeterminate
|
||||||
size="small"
|
size="small"
|
||||||
slot="meta"
|
slot="meta"
|
||||||
|
.ariaLabel=${this.hass.localize(
|
||||||
|
"ui.panel.config.updates.update_in_progress"
|
||||||
|
)}
|
||||||
></ha-circular-progress>`
|
></ha-circular-progress>`
|
||||||
: html`<ha-icon-next slot="meta"></ha-icon-next>`
|
: html`<ha-icon-next slot="meta"></ha-icon-next>`
|
||||||
: ""}
|
: ""}
|
||||||
@ -191,6 +196,8 @@ class HaConfigUpdates extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
ha-circular-progress.absolute {
|
ha-circular-progress.absolute {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
}
|
}
|
||||||
state-badge.updating {
|
state-badge.updating {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
@ -363,8 +363,8 @@ export class HaConfigDeviceDashboard extends LitElement {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
type: "numeric",
|
type: "numeric",
|
||||||
width: narrow ? "95px" : "15%",
|
width: narrow ? "105px" : "15%",
|
||||||
maxWidth: "95px",
|
maxWidth: "105px",
|
||||||
valueColumn: "battery_level",
|
valueColumn: "battery_level",
|
||||||
template: (device) => {
|
template: (device) => {
|
||||||
const batteryEntityPair = device.battery_entity;
|
const batteryEntityPair = device.battery_entity;
|
||||||
|
@ -121,7 +121,7 @@ const OVERRIDE_DEVICE_CLASSES = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const SWITCH_AS_DOMAINS = ["cover", "fan", "light", "lock", "siren"];
|
const SWITCH_AS_DOMAINS = ["cover", "fan", "light", "lock", "siren", "valve"];
|
||||||
|
|
||||||
const PRECISIONS = [0, 1, 2, 3, 4, 5, 6];
|
const PRECISIONS = [0, 1, 2, 3, 4, 5, 6];
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ export interface EntityRow extends StateEntity {
|
|||||||
entity?: HassEntity;
|
entity?: HassEntity;
|
||||||
unavailable: boolean;
|
unavailable: boolean;
|
||||||
restored: boolean;
|
restored: boolean;
|
||||||
status: string;
|
status: string | undefined;
|
||||||
area?: string;
|
area?: string;
|
||||||
localized_platform: string;
|
localized_platform: string;
|
||||||
}
|
}
|
||||||
@ -429,7 +429,13 @@ export class HaConfigEntities extends LitElement {
|
|||||||
? localize("ui.panel.config.entities.picker.status.unavailable")
|
? localize("ui.panel.config.entities.picker.status.unavailable")
|
||||||
: entry.disabled_by
|
: entry.disabled_by
|
||||||
? localize("ui.panel.config.entities.picker.status.disabled")
|
? localize("ui.panel.config.entities.picker.status.disabled")
|
||||||
: localize("ui.panel.config.entities.picker.status.ok"),
|
: entry.hidden_by
|
||||||
|
? localize("ui.panel.config.entities.picker.status.hidden")
|
||||||
|
: entry.readonly
|
||||||
|
? localize(
|
||||||
|
"ui.panel.config.entities.picker.status.readonly"
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,21 +3,22 @@ import "@material/mwc-list/mwc-list";
|
|||||||
import Fuse, { IFuseOptions } from "fuse.js";
|
import Fuse, { IFuseOptions } from "fuse.js";
|
||||||
import { HassConfig } from "home-assistant-js-websocket";
|
import { HassConfig } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
css,
|
|
||||||
html,
|
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
nothing,
|
nothing,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, state } from "lit/decorators";
|
import { customElement, state } from "lit/decorators";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import {
|
import {
|
||||||
protocolIntegrationPicked,
|
|
||||||
PROTOCOL_INTEGRATIONS,
|
PROTOCOL_INTEGRATIONS,
|
||||||
|
protocolIntegrationPicked,
|
||||||
} from "../../../common/integrations/protocolIntegrationPicked";
|
} from "../../../common/integrations/protocolIntegrationPicked";
|
||||||
import { navigate } from "../../../common/navigate";
|
import { navigate } from "../../../common/navigate";
|
||||||
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||||
@ -34,10 +35,10 @@ import {
|
|||||||
import {
|
import {
|
||||||
Brand,
|
Brand,
|
||||||
Brands,
|
Brands,
|
||||||
findIntegration,
|
|
||||||
getIntegrationDescriptions,
|
|
||||||
Integration,
|
Integration,
|
||||||
Integrations,
|
Integrations,
|
||||||
|
findIntegration,
|
||||||
|
getIntegrationDescriptions,
|
||||||
} from "../../../data/integrations";
|
} from "../../../data/integrations";
|
||||||
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
|
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
|
||||||
import {
|
import {
|
||||||
@ -424,8 +425,7 @@ class AddIntegrationDialog extends LitElement {
|
|||||||
private _renderAll(integrations?: IntegrationListItem[]): TemplateResult {
|
private _renderAll(integrations?: IntegrationListItem[]): TemplateResult {
|
||||||
return html`<search-input
|
return html`<search-input
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
autofocus
|
dialogInitialFocus=${ifDefined(this._narrow ? undefined : "")}
|
||||||
dialogInitialFocus
|
|
||||||
.filter=${this._filter}
|
.filter=${this._filter}
|
||||||
@value-changed=${this._filterChanged}
|
@value-changed=${this._filterChanged}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@ -434,7 +434,9 @@ class AddIntegrationDialog extends LitElement {
|
|||||||
@keypress=${this._maybeSubmit}
|
@keypress=${this._maybeSubmit}
|
||||||
></search-input>
|
></search-input>
|
||||||
${integrations
|
${integrations
|
||||||
? html`<mwc-list>
|
? html`<mwc-list
|
||||||
|
dialogInitialFocus=${ifDefined(this._narrow ? "" : undefined)}
|
||||||
|
>
|
||||||
<lit-virtualizer
|
<lit-virtualizer
|
||||||
scroller
|
scroller
|
||||||
class="ha-scrollbar"
|
class="ha-scrollbar"
|
||||||
|
@ -487,7 +487,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
|||||||
<h1 class="card-header">
|
<h1 class="card-header">
|
||||||
${this._manifest?.integration_type
|
${this._manifest?.integration_type
|
||||||
? this.hass.localize(
|
? this.hass.localize(
|
||||||
`ui.panel.config.integrations.integration_page.entries_${this._manifest?.integration_type}`
|
`ui.panel.config.integrations.integration_page.entries_${this._manifest.integration_type}`
|
||||||
)
|
)
|
||||||
: this.hass.localize(
|
: this.hass.localize(
|
||||||
`ui.panel.config.integrations.integration_page.entries`
|
`ui.panel.config.integrations.integration_page.entries`
|
||||||
@ -507,7 +507,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
|||||||
<ha-button @click=${this._addIntegration}>
|
<ha-button @click=${this._addIntegration}>
|
||||||
${this._manifest?.integration_type
|
${this._manifest?.integration_type
|
||||||
? this.hass.localize(
|
? this.hass.localize(
|
||||||
`ui.panel.config.integrations.integration_page.add_${this._manifest?.integration_type}`
|
`ui.panel.config.integrations.integration_page.add_${this._manifest.integration_type}`
|
||||||
)
|
)
|
||||||
: this.hass.localize(
|
: this.hass.localize(
|
||||||
`ui.panel.config.integrations.integration_page.add_entry`
|
`ui.panel.config.integrations.integration_page.add_entry`
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-input/paper-textarea";
|
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@ -20,6 +19,7 @@ import { haStyle } from "../../../../../resources/styles";
|
|||||||
import { HomeAssistant, Route } from "../../../../../types";
|
import { HomeAssistant, Route } from "../../../../../types";
|
||||||
import { zhaTabs } from "./zha-config-dashboard";
|
import { zhaTabs } from "./zha-config-dashboard";
|
||||||
import "./zha-device-pairing-status-card";
|
import "./zha-device-pairing-status-card";
|
||||||
|
import "../../../../../components/ha-textarea";
|
||||||
|
|
||||||
@customElement("zha-add-devices-page")
|
@customElement("zha-add-devices-page")
|
||||||
class ZHAAddDevicesPage extends LitElement {
|
class ZHAAddDevicesPage extends LitElement {
|
||||||
@ -146,13 +146,13 @@ class ZHAAddDevicesPage extends LitElement {
|
|||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
${this._showLogs
|
${this._showLogs
|
||||||
? html`<paper-textarea
|
? html`<ha-textarea
|
||||||
readonly
|
readonly
|
||||||
max-rows="10"
|
|
||||||
class="log"
|
class="log"
|
||||||
value=${this._formattedEvents}
|
autogrow
|
||||||
|
.value=${this._formattedEvents}
|
||||||
>
|
>
|
||||||
</paper-textarea>`
|
</ha-textarea>`
|
||||||
: ""}
|
: ""}
|
||||||
</hass-tabs-subpage>
|
</hass-tabs-subpage>
|
||||||
`;
|
`;
|
||||||
@ -165,13 +165,6 @@ class ZHAAddDevicesPage extends LitElement {
|
|||||||
private _handleMessage(message: any): void {
|
private _handleMessage(message: any): void {
|
||||||
if (message.type === LOG_OUTPUT) {
|
if (message.type === LOG_OUTPUT) {
|
||||||
this._formattedEvents += message.log_entry.message + "\n";
|
this._formattedEvents += message.log_entry.message + "\n";
|
||||||
if (this.shadowRoot) {
|
|
||||||
const paperTextArea = this.shadowRoot.querySelector("paper-textarea");
|
|
||||||
if (paperTextArea) {
|
|
||||||
const textArea = (paperTextArea.inputElement as any).textarea;
|
|
||||||
textArea.scrollTop = textArea.scrollHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (message.type && DEVICE_MESSAGE_TYPES.includes(message.type)) {
|
if (message.type && DEVICE_MESSAGE_TYPES.includes(message.type)) {
|
||||||
this._discoveredDevices[message.device_info.ieee] = message.device_info;
|
this._discoveredDevices[message.device_info.ieee] = message.device_info;
|
||||||
@ -266,6 +259,9 @@ class ZHAAddDevicesPage extends LitElement {
|
|||||||
color: grey;
|
color: grey;
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
}
|
}
|
||||||
|
ha-textarea {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||||
import { customElement, property, state, query } from "lit/decorators";
|
import { customElement, property, state, query } from "lit/decorators";
|
||||||
import type { HASSDomEvent } from "../../../../../common/dom/fire_event";
|
import type { HASSDomEvent } from "../../../../../common/dom/fire_event";
|
||||||
@ -14,8 +12,9 @@ import {
|
|||||||
ZHAGroup,
|
ZHAGroup,
|
||||||
} from "../../../../../data/zha";
|
} from "../../../../../data/zha";
|
||||||
import "../../../../../layouts/hass-subpage";
|
import "../../../../../layouts/hass-subpage";
|
||||||
import type { ValueChangedEvent, HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import "../../../ha-config-section";
|
import "../../../ha-config-section";
|
||||||
|
import "../../../../../components/ha-textfield";
|
||||||
import "./zha-device-endpoint-data-table";
|
import "./zha-device-endpoint-data-table";
|
||||||
import type { ZHADeviceEndpointDataTable } from "./zha-device-endpoint-data-table";
|
import type { ZHADeviceEndpointDataTable } from "./zha-device-endpoint-data-table";
|
||||||
|
|
||||||
@ -31,6 +30,8 @@ export class ZHAAddGroupPage extends LitElement {
|
|||||||
|
|
||||||
@state() private _groupName = "";
|
@state() private _groupName = "";
|
||||||
|
|
||||||
|
@state() private _groupId?: string;
|
||||||
|
|
||||||
@query("zha-device-endpoint-data-table", true)
|
@query("zha-device-endpoint-data-table", true)
|
||||||
private _zhaDevicesDataTable!: ZHADeviceEndpointDataTable;
|
private _zhaDevicesDataTable!: ZHADeviceEndpointDataTable;
|
||||||
|
|
||||||
@ -66,14 +67,23 @@ export class ZHAAddGroupPage extends LitElement {
|
|||||||
"ui.panel.config.zha.groups.create_group_details"
|
"ui.panel.config.zha.groups.create_group_details"
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<paper-input
|
<ha-textfield
|
||||||
type="string"
|
type="string"
|
||||||
.value=${this._groupName}
|
.value=${this._groupName}
|
||||||
@value-changed=${this._handleNameChange}
|
@change=${this._handleNameChange}
|
||||||
placeholder=${this.hass!.localize(
|
.placeholder=${this.hass!.localize(
|
||||||
"ui.panel.config.zha.groups.group_name_placeholder"
|
"ui.panel.config.zha.groups.group_name_placeholder"
|
||||||
)}
|
)}
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
|
|
||||||
|
<ha-textfield
|
||||||
|
type="number"
|
||||||
|
.value=${this._groupId}
|
||||||
|
@change=${this._handleGroupIdChange}
|
||||||
|
.placeholder=${this.hass!.localize(
|
||||||
|
"ui.panel.config.zha.groups.group_id_placeholder"
|
||||||
|
)}
|
||||||
|
></ha-textfield>
|
||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
${this.hass.localize("ui.panel.config.zha.groups.add_members")}
|
${this.hass.localize("ui.panel.config.zha.groups.add_members")}
|
||||||
@ -131,7 +141,15 @@ export class ZHAAddGroupPage extends LitElement {
|
|||||||
const memberParts = member.split("_");
|
const memberParts = member.split("_");
|
||||||
return { ieee: memberParts[0], endpoint_id: memberParts[1] };
|
return { ieee: memberParts[0], endpoint_id: memberParts[1] };
|
||||||
});
|
});
|
||||||
const group: ZHAGroup = await addGroup(this.hass, this._groupName, members);
|
const groupId = this._groupId
|
||||||
|
? parseInt(this._groupId as string, 10)
|
||||||
|
: undefined;
|
||||||
|
const group: ZHAGroup = await addGroup(
|
||||||
|
this.hass,
|
||||||
|
this._groupName,
|
||||||
|
groupId,
|
||||||
|
members
|
||||||
|
);
|
||||||
this._selectedDevicesToAdd = [];
|
this._selectedDevicesToAdd = [];
|
||||||
this._processingAdd = false;
|
this._processingAdd = false;
|
||||||
this._groupName = "";
|
this._groupName = "";
|
||||||
@ -139,9 +157,12 @@ export class ZHAAddGroupPage extends LitElement {
|
|||||||
navigate(`/config/zha/group/${group.group_id}`, { replace: true });
|
navigate(`/config/zha/group/${group.group_id}`, { replace: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleNameChange(ev: ValueChangedEvent<string>) {
|
private _handleGroupIdChange(event) {
|
||||||
const target = ev.currentTarget as PaperInputElement;
|
this._groupId = event.target.value;
|
||||||
this._groupName = target.value || "";
|
}
|
||||||
|
|
||||||
|
private _handleNameChange(event) {
|
||||||
|
this._groupName = event.target.value || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@ -15,6 +14,7 @@ import "../../../../../components/buttons/ha-call-service-button";
|
|||||||
import "../../../../../components/buttons/ha-progress-button";
|
import "../../../../../components/buttons/ha-progress-button";
|
||||||
import "../../../../../components/ha-card";
|
import "../../../../../components/ha-card";
|
||||||
import "../../../../../components/ha-select";
|
import "../../../../../components/ha-select";
|
||||||
|
import "../../../../../components/ha-textfield";
|
||||||
import { forwardHaptic } from "../../../../../data/haptics";
|
import { forwardHaptic } from "../../../../../data/haptics";
|
||||||
import {
|
import {
|
||||||
Attribute,
|
Attribute,
|
||||||
@ -27,11 +27,7 @@ import {
|
|||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import { formatAsPaddedHex } from "./functions";
|
import { formatAsPaddedHex } from "./functions";
|
||||||
import {
|
import { ItemSelectedEvent, SetAttributeServiceData } from "./types";
|
||||||
ChangeEvent,
|
|
||||||
ItemSelectedEvent,
|
|
||||||
SetAttributeServiceData,
|
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
@customElement("zha-cluster-attributes")
|
@customElement("zha-cluster-attributes")
|
||||||
export class ZHAClusterAttributes extends LitElement {
|
export class ZHAClusterAttributes extends LitElement {
|
||||||
@ -101,24 +97,28 @@ export class ZHAClusterAttributes extends LitElement {
|
|||||||
private _renderAttributeInteractions(): TemplateResult {
|
private _renderAttributeInteractions(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<div class="input-text">
|
<div class="input-text">
|
||||||
<paper-input
|
<ha-textfield
|
||||||
label=${this.hass!.localize("ui.panel.config.zha.common.value")}
|
.label=${this.hass!.localize("ui.panel.config.zha.common.value")}
|
||||||
type="string"
|
type="string"
|
||||||
.value=${this._attributeValue}
|
.value=${this._attributeValue}
|
||||||
@value-changed=${this._onAttributeValueChanged}
|
@change=${this._onAttributeValueChanged}
|
||||||
placeholder=${this.hass!.localize("ui.panel.config.zha.common.value")}
|
.placeholder=${this.hass!.localize(
|
||||||
></paper-input>
|
"ui.panel.config.zha.common.value"
|
||||||
|
)}
|
||||||
|
></ha-textfield>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-text">
|
<div class="input-text">
|
||||||
<paper-input
|
<ha-textfield
|
||||||
label=${this.hass!.localize(
|
.label=${this.hass!.localize(
|
||||||
"ui.panel.config.zha.common.manufacturer_code_override"
|
"ui.panel.config.zha.common.manufacturer_code_override"
|
||||||
)}
|
)}
|
||||||
type="number"
|
type="number"
|
||||||
.value=${this._manufacturerCodeOverride}
|
.value=${this._manufacturerCodeOverride}
|
||||||
@value-changed=${this._onManufacturerCodeOverrideChanged}
|
@change=${this._onManufacturerCodeOverrideChanged}
|
||||||
placeholder=${this.hass!.localize("ui.panel.config.zha.common.value")}
|
.placeholder=${this.hass!.localize(
|
||||||
></paper-input>
|
"ui.panel.config.zha.common.value"
|
||||||
|
)}
|
||||||
|
></ha-textfield>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<ha-progress-button
|
<ha-progress-button
|
||||||
@ -197,13 +197,13 @@ export class ZHAClusterAttributes extends LitElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onAttributeValueChanged(value: ChangeEvent): void {
|
private _onAttributeValueChanged(event): void {
|
||||||
this._attributeValue = value.detail!.value;
|
this._attributeValue = event.target!.value;
|
||||||
this._setAttributeServiceData = this._computeSetAttributeServiceData();
|
this._setAttributeServiceData = this._computeSetAttributeServiceData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onManufacturerCodeOverrideChanged(value: ChangeEvent): void {
|
private _onManufacturerCodeOverrideChanged(event): void {
|
||||||
this._manufacturerCodeOverride = value.detail!.value;
|
this._manufacturerCodeOverride = event.target!.value;
|
||||||
this._setAttributeServiceData = this._computeSetAttributeServiceData();
|
this._setAttributeServiceData = this._computeSetAttributeServiceData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +238,8 @@ export class ZHAClusterAttributes extends LitElement {
|
|||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
.menu,
|
||||||
|
ha-textfield {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@ -14,6 +13,7 @@ import "../../../../../components/buttons/ha-call-service-button";
|
|||||||
import "../../../../../components/ha-card";
|
import "../../../../../components/ha-card";
|
||||||
import "../../../../../components/ha-form/ha-form";
|
import "../../../../../components/ha-form/ha-form";
|
||||||
import "../../../../../components/ha-select";
|
import "../../../../../components/ha-select";
|
||||||
|
import "../../../../../components/ha-textfield";
|
||||||
import {
|
import {
|
||||||
Cluster,
|
Cluster,
|
||||||
Command,
|
Command,
|
||||||
@ -23,7 +23,7 @@ import {
|
|||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import { HomeAssistant } from "../../../../../types";
|
||||||
import { formatAsPaddedHex } from "./functions";
|
import { formatAsPaddedHex } from "./functions";
|
||||||
import { ChangeEvent, IssueCommandServiceData } from "./types";
|
import { IssueCommandServiceData } from "./types";
|
||||||
|
|
||||||
export class ZHAClusterCommands extends LitElement {
|
export class ZHAClusterCommands extends LitElement {
|
||||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
@ -88,17 +88,17 @@ export class ZHAClusterCommands extends LitElement {
|
|||||||
${this._selectedCommandId !== undefined
|
${this._selectedCommandId !== undefined
|
||||||
? html`
|
? html`
|
||||||
<div class="input-text">
|
<div class="input-text">
|
||||||
<paper-input
|
<ha-textfield
|
||||||
label=${this.hass!.localize(
|
.label=${this.hass!.localize(
|
||||||
"ui.panel.config.zha.common.manufacturer_code_override"
|
"ui.panel.config.zha.common.manufacturer_code_override"
|
||||||
)}
|
)}
|
||||||
type="number"
|
type="number"
|
||||||
.value=${this._manufacturerCodeOverride}
|
.value=${this._manufacturerCodeOverride}
|
||||||
@value-changed=${this._onManufacturerCodeOverrideChanged}
|
@change=${this._onManufacturerCodeOverrideChanged}
|
||||||
placeholder=${this.hass!.localize(
|
.placeholder=${this.hass!.localize(
|
||||||
"ui.panel.config.zha.common.value"
|
"ui.panel.config.zha.common.value"
|
||||||
)}
|
)}
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
</div>
|
</div>
|
||||||
<div class="command-form">
|
<div class="command-form">
|
||||||
<ha-form
|
<ha-form
|
||||||
@ -180,8 +180,8 @@ export class ZHAClusterCommands extends LitElement {
|
|||||||
this._computeIssueClusterCommandServiceData();
|
this._computeIssueClusterCommandServiceData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onManufacturerCodeOverrideChanged(value: ChangeEvent): void {
|
private _onManufacturerCodeOverrideChanged(event): void {
|
||||||
this._manufacturerCodeOverride = value.detail!.value;
|
this._manufacturerCodeOverride = Number(event.target.value);
|
||||||
this._issueClusterCommandServiceData =
|
this._issueClusterCommandServiceData =
|
||||||
this._computeIssueClusterCommandServiceData();
|
this._computeIssueClusterCommandServiceData();
|
||||||
}
|
}
|
||||||
@ -199,7 +199,8 @@ export class ZHAClusterCommands extends LitElement {
|
|||||||
ha-select {
|
ha-select {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
.menu {
|
.menu,
|
||||||
|
ha-textfield {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
@ -11,6 +10,7 @@ import "../../../../../components/buttons/ha-call-service-button";
|
|||||||
import "../../../../../components/entity/state-badge";
|
import "../../../../../components/entity/state-badge";
|
||||||
import "../../../../../components/ha-area-picker";
|
import "../../../../../components/ha-area-picker";
|
||||||
import "../../../../../components/ha-card";
|
import "../../../../../components/ha-card";
|
||||||
|
import "../../../../../components/ha-textfield";
|
||||||
import { updateDeviceRegistryEntry } from "../../../../../data/device_registry";
|
import { updateDeviceRegistryEntry } from "../../../../../data/device_registry";
|
||||||
import {
|
import {
|
||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
@ -98,14 +98,14 @@ class ZHADeviceCard extends SubscribeMixin(LitElement) {
|
|||||||
: ""
|
: ""
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<paper-input
|
<ha-textfield
|
||||||
type="string"
|
type="string"
|
||||||
@change=${this._rename}
|
@change=${this._rename}
|
||||||
.value=${this.device.user_given_name || this.device.name}
|
.value=${this.device.user_given_name || this.device.name}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.dialogs.zha_device_info.zha_device_card.device_name_placeholder"
|
"ui.dialogs.zha_device_info.zha_device_card.device_name_placeholder"
|
||||||
)}
|
)}
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
<ha-area-picker
|
<ha-area-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.device=${this.device.device_reg_id}
|
.device=${this.device.device_reg_id}
|
||||||
@ -229,6 +229,9 @@ class ZHADeviceCard extends SubscribeMixin(LitElement) {
|
|||||||
ha-card {
|
ha-card {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
ha-textfield {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,6 @@ import "../../../../../layouts/hass-tabs-subpage";
|
|||||||
import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin";
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import type { HomeAssistant, Route } from "../../../../../types";
|
import type { HomeAssistant, Route } from "../../../../../types";
|
||||||
import "../../../ha-config-section";
|
|
||||||
import { showZWaveJSAddNodeDialog } from "./show-dialog-zwave_js-add-node";
|
import { showZWaveJSAddNodeDialog } from "./show-dialog-zwave_js-add-node";
|
||||||
import { showZWaveJSRebuildNetworkRoutesDialog } from "./show-dialog-zwave_js-rebuild-network-routes";
|
import { showZWaveJSRebuildNetworkRoutesDialog } from "./show-dialog-zwave_js-rebuild-network-routes";
|
||||||
import { showZWaveJSRemoveNodeDialog } from "./show-dialog-zwave_js-remove-node";
|
import { showZWaveJSRemoveNodeDialog } from "./show-dialog-zwave_js-remove-node";
|
||||||
@ -128,358 +127,345 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
|||||||
.path=${mdiRefresh}
|
.path=${mdiRefresh}
|
||||||
.label=${this.hass!.localize("ui.common.refresh")}
|
.label=${this.hass!.localize("ui.common.refresh")}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<ha-config-section .narrow=${this.narrow} .isWide=${this.isWide}>
|
${this._network &&
|
||||||
<div slot="header">
|
this._status === "connected" &&
|
||||||
${this.hass.localize("ui.panel.config.zwave_js.dashboard.header")}
|
(this._network?.controller.inclusion_state ===
|
||||||
</div>
|
InclusionState.Including ||
|
||||||
|
this._network?.controller.inclusion_state ===
|
||||||
<div slot="introduction">
|
InclusionState.Excluding)
|
||||||
${this.hass.localize(
|
? html`
|
||||||
"ui.panel.config.zwave_js.dashboard.introduction"
|
<ha-alert alert-type="info">
|
||||||
)}
|
${this.hass.localize(
|
||||||
</div>
|
`ui.panel.config.zwave_js.common.in_progress_inclusion_exclusion`
|
||||||
${this._network &&
|
)}
|
||||||
this._status === "connected" &&
|
<mwc-button
|
||||||
(this._network?.controller.inclusion_state ===
|
slot="action"
|
||||||
InclusionState.Including ||
|
.label=${this.hass.localize(
|
||||||
this._network?.controller.inclusion_state ===
|
`ui.panel.config.zwave_js.common.cancel_inclusion_exclusion`
|
||||||
InclusionState.Excluding)
|
|
||||||
? html`
|
|
||||||
<ha-alert alert-type="info">
|
|
||||||
${this.hass.localize(
|
|
||||||
`ui.panel.config.zwave_js.common.in_progress_inclusion_exclusion`
|
|
||||||
)}
|
)}
|
||||||
<mwc-button
|
@click=${this._network?.controller.inclusion_state ===
|
||||||
slot="action"
|
InclusionState.Including
|
||||||
.label=${this.hass.localize(
|
? this._cancelInclusion
|
||||||
`ui.panel.config.zwave_js.common.cancel_inclusion_exclusion`
|
: this._cancelExclusion}
|
||||||
)}
|
>
|
||||||
@click=${this._network?.controller.inclusion_state ===
|
</mwc-button>
|
||||||
InclusionState.Including
|
</ha-alert>
|
||||||
? this._cancelInclusion
|
`
|
||||||
: this._cancelExclusion}
|
: ""}
|
||||||
>
|
${this._network
|
||||||
</mwc-button>
|
? html`
|
||||||
</ha-alert>
|
<ha-card class="content network-status">
|
||||||
`
|
<div class="card-content">
|
||||||
: ""}
|
<div class="heading">
|
||||||
${this._network
|
<div class="icon">
|
||||||
? html`
|
${this._status === "disconnected"
|
||||||
<ha-card class="content network-status">
|
? html`<ha-circular-progress
|
||||||
<div class="card-content">
|
|
||||||
<div class="heading">
|
|
||||||
<div class="icon">
|
|
||||||
${this._status === "disconnected"
|
|
||||||
? html`<ha-circular-progress
|
|
||||||
indeterminate
|
|
||||||
></ha-circular-progress>`
|
|
||||||
: html`
|
|
||||||
<ha-svg-icon
|
|
||||||
.path=${this._icon}
|
|
||||||
class="network-status-icon ${classMap({
|
|
||||||
[this._status!]: true,
|
|
||||||
})}"
|
|
||||||
slot="item-icon"
|
|
||||||
></ha-svg-icon>
|
|
||||||
`}
|
|
||||||
</div>
|
|
||||||
${this._status !== "disconnected"
|
|
||||||
? html`
|
|
||||||
<div class="details">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.common.network"
|
|
||||||
)}
|
|
||||||
${this.hass.localize(
|
|
||||||
`ui.panel.config.zwave_js.network_status.${this._status}`
|
|
||||||
)}<br />
|
|
||||||
<small>
|
|
||||||
${this.hass.localize(
|
|
||||||
`ui.panel.config.zwave_js.dashboard.devices`,
|
|
||||||
{
|
|
||||||
count:
|
|
||||||
this._network.controller.nodes.length,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
${notReadyDevices > 0
|
|
||||||
? html`(${this.hass.localize(
|
|
||||||
`ui.panel.config.zwave_js.dashboard.not_ready`,
|
|
||||||
{ count: notReadyDevices }
|
|
||||||
)})`
|
|
||||||
: ""}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ``}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-actions">
|
|
||||||
<a
|
|
||||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${this.configEntryId}`}
|
|
||||||
>
|
|
||||||
<mwc-button>
|
|
||||||
${this.hass.localize("ui.panel.config.devices.caption")}
|
|
||||||
</mwc-button>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href=${`/config/entities/dashboard?historyBack=1&config_entry=${this.configEntryId}`}
|
|
||||||
>
|
|
||||||
<mwc-button>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.entities.caption"
|
|
||||||
)}
|
|
||||||
</mwc-button>
|
|
||||||
</a>
|
|
||||||
${this._provisioningEntries?.length
|
|
||||||
? html`<a
|
|
||||||
href=${`provisioned?config_entry=${this.configEntryId}`}
|
|
||||||
><mwc-button>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.provisioned_devices"
|
|
||||||
)}
|
|
||||||
</mwc-button></a
|
|
||||||
>`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
<ha-card header="Diagnostics">
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="row">
|
|
||||||
<span>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.driver_version"
|
|
||||||
)}:
|
|
||||||
</span>
|
|
||||||
<span>${this._network.client.driver_version}</span>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<span>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.server_version"
|
|
||||||
)}:
|
|
||||||
</span>
|
|
||||||
<span>${this._network.client.server_version}</span>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<span>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.home_id"
|
|
||||||
)}:
|
|
||||||
</span>
|
|
||||||
<span>${this._network.controller.home_id}</span>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<span>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.server_url"
|
|
||||||
)}:
|
|
||||||
</span>
|
|
||||||
<span>${this._network.client.ws_server_url}</span>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<ha-expansion-panel
|
|
||||||
.header=${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.title"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<mwc-list noninteractive>
|
|
||||||
<mwc-list-item twoline hasmeta>
|
|
||||||
<span>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_tx.label"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="secondary">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_tx.tooltip"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="meta"
|
|
||||||
>${this._statistics?.messages_tx ?? 0}</span
|
|
||||||
>
|
|
||||||
</mwc-list-item>
|
|
||||||
<mwc-list-item twoline hasmeta>
|
|
||||||
<span>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_rx.label"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="secondary">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_rx.tooltip"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="meta"
|
|
||||||
>${this._statistics?.messages_rx ?? 0}</span
|
|
||||||
>
|
|
||||||
</mwc-list-item>
|
|
||||||
<mwc-list-item twoline hasmeta>
|
|
||||||
<span>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_tx.label"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="secondary">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_tx.tooltip"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="meta"
|
|
||||||
>${this._statistics?.messages_dropped_tx ?? 0}</span
|
|
||||||
>
|
|
||||||
</mwc-list-item>
|
|
||||||
<mwc-list-item twoline hasmeta>
|
|
||||||
<span>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_rx.label"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="secondary">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_rx.tooltip"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="meta"
|
|
||||||
>${this._statistics?.messages_dropped_rx ?? 0}</span
|
|
||||||
>
|
|
||||||
</mwc-list-item>
|
|
||||||
<mwc-list-item twoline hasmeta>
|
|
||||||
<span>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.nak.label"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="secondary">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.nak.tooltip"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="meta">${this._statistics?.nak ?? 0}</span>
|
|
||||||
</mwc-list-item>
|
|
||||||
<mwc-list-item twoline hasmeta>
|
|
||||||
<span>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.can.label"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="secondary">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.can.tooltip"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="meta">${this._statistics?.can ?? 0}</span>
|
|
||||||
</mwc-list-item>
|
|
||||||
<mwc-list-item twoline hasmeta>
|
|
||||||
<span>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.timeout_ack.label"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="secondary">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.timeout_ack.tooltip"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="meta"
|
|
||||||
>${this._statistics?.timeout_ack ?? 0}</span
|
|
||||||
>
|
|
||||||
</mwc-list-item>
|
|
||||||
<mwc-list-item twoline hasmeta>
|
|
||||||
<span>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.timeout_response.label"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="secondary">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.timeout_response.tooltip"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="meta"
|
|
||||||
>${this._statistics?.timeout_response ?? 0}</span
|
|
||||||
>
|
|
||||||
</mwc-list-item>
|
|
||||||
<mwc-list-item twoline hasmeta>
|
|
||||||
<span>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.timeout_callback.label"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="secondary">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.dashboard.statistics.timeout_callback.tooltip"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="meta"
|
|
||||||
>${this._statistics?.timeout_callback ?? 0}</span
|
|
||||||
>
|
|
||||||
</mwc-list-item>
|
|
||||||
</mwc-list>
|
|
||||||
</ha-expansion-panel>
|
|
||||||
</div>
|
|
||||||
<div class="card-actions">
|
|
||||||
<mwc-button
|
|
||||||
@click=${this._removeNodeClicked}
|
|
||||||
.disabled=${this._status !== "connected" ||
|
|
||||||
(this._network?.controller.inclusion_state !==
|
|
||||||
InclusionState.Idle &&
|
|
||||||
this._network?.controller.inclusion_state !==
|
|
||||||
InclusionState.SmartStart)}
|
|
||||||
>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.common.remove_node"
|
|
||||||
)}
|
|
||||||
</mwc-button>
|
|
||||||
<mwc-button
|
|
||||||
@click=${this._rebuildNetworkRoutesClicked}
|
|
||||||
.disabled=${this._status === "disconnected"}
|
|
||||||
>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.common.rebuild_network_routes"
|
|
||||||
)}
|
|
||||||
</mwc-button>
|
|
||||||
<mwc-button @click=${this._openOptionFlow}>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.zwave_js.common.reconfigure_server"
|
|
||||||
)}
|
|
||||||
</mwc-button>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
<ha-card>
|
|
||||||
<div class="card-header">
|
|
||||||
<h1>Third-Party Data Reporting</h1>
|
|
||||||
${this._dataCollectionOptIn !== undefined
|
|
||||||
? html`
|
|
||||||
<ha-switch
|
|
||||||
.checked=${this._dataCollectionOptIn === true}
|
|
||||||
@change=${this._dataCollectionToggled}
|
|
||||||
></ha-switch>
|
|
||||||
`
|
|
||||||
: html`
|
|
||||||
<ha-circular-progress
|
|
||||||
size="small"
|
|
||||||
indeterminate
|
indeterminate
|
||||||
></ha-circular-progress>
|
></ha-circular-progress>`
|
||||||
`}
|
: html`
|
||||||
|
<ha-svg-icon
|
||||||
|
.path=${this._icon}
|
||||||
|
class="network-status-icon ${classMap({
|
||||||
|
[this._status!]: true,
|
||||||
|
})}"
|
||||||
|
slot="item-icon"
|
||||||
|
></ha-svg-icon>
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
${this._status !== "disconnected"
|
||||||
|
? html`
|
||||||
|
<div class="details">
|
||||||
|
Z-Wave
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.common.network"
|
||||||
|
)}
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.zwave_js.network_status.${this._status}`
|
||||||
|
)}<br />
|
||||||
|
<small>
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.zwave_js.dashboard.devices`,
|
||||||
|
{
|
||||||
|
count: this._network.controller.nodes.length,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
${notReadyDevices > 0
|
||||||
|
? html`(${this.hass.localize(
|
||||||
|
`ui.panel.config.zwave_js.dashboard.not_ready`,
|
||||||
|
{ count: notReadyDevices }
|
||||||
|
)})`
|
||||||
|
: ""}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ``}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
</div>
|
||||||
<p>
|
<div class="card-actions">
|
||||||
Enable the reporting of anonymized telemetry and
|
<a
|
||||||
statistics to the <em>Z-Wave JS organization</em>. This
|
href=${`/config/devices/dashboard?historyBack=1&config_entry=${this.configEntryId}`}
|
||||||
data will be used to focus development efforts and improve
|
>
|
||||||
the user experience. Information about the data that is
|
<mwc-button>
|
||||||
collected and how it is used, including an example of the
|
${this.hass.localize("ui.panel.config.devices.caption")}
|
||||||
data collected, can be found in the
|
</mwc-button>
|
||||||
<a
|
</a>
|
||||||
target="_blank"
|
<a
|
||||||
href="https://zwave-js.github.io/node-zwave-js/#/data-collection/data-collection"
|
href=${`/config/entities/dashboard?historyBack=1&config_entry=${this.configEntryId}`}
|
||||||
>Z-Wave JS data collection documentation</a
|
>
|
||||||
>.
|
<mwc-button>
|
||||||
</p>
|
${this.hass.localize("ui.panel.config.entities.caption")}
|
||||||
|
</mwc-button>
|
||||||
|
</a>
|
||||||
|
${this._provisioningEntries?.length
|
||||||
|
? html`<a
|
||||||
|
href=${`provisioned?config_entry=${this.configEntryId}`}
|
||||||
|
><mwc-button>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.provisioned_devices"
|
||||||
|
)}
|
||||||
|
</mwc-button></a
|
||||||
|
>`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
<ha-card header="Diagnostics">
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="row">
|
||||||
|
<span>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.driver_version"
|
||||||
|
)}:
|
||||||
|
</span>
|
||||||
|
<span>${this._network.client.driver_version}</span>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
<div class="row">
|
||||||
`
|
<span>
|
||||||
: ``}
|
${this.hass.localize(
|
||||||
</ha-config-section>
|
"ui.panel.config.zwave_js.dashboard.server_version"
|
||||||
|
)}:
|
||||||
|
</span>
|
||||||
|
<span>${this._network.client.server_version}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.home_id"
|
||||||
|
)}:
|
||||||
|
</span>
|
||||||
|
<span>${this._network.controller.home_id}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.server_url"
|
||||||
|
)}:
|
||||||
|
</span>
|
||||||
|
<span>${this._network.client.ws_server_url}</span>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<ha-expansion-panel
|
||||||
|
.header=${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.title"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<mwc-list noninteractive>
|
||||||
|
<mwc-list-item twoline hasmeta>
|
||||||
|
<span>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.messages_tx.label"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="secondary">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.messages_tx.tooltip"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="meta"
|
||||||
|
>${this._statistics?.messages_tx ?? 0}</span
|
||||||
|
>
|
||||||
|
</mwc-list-item>
|
||||||
|
<mwc-list-item twoline hasmeta>
|
||||||
|
<span>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.messages_rx.label"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="secondary">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.messages_rx.tooltip"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="meta"
|
||||||
|
>${this._statistics?.messages_rx ?? 0}</span
|
||||||
|
>
|
||||||
|
</mwc-list-item>
|
||||||
|
<mwc-list-item twoline hasmeta>
|
||||||
|
<span>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_tx.label"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="secondary">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_tx.tooltip"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="meta"
|
||||||
|
>${this._statistics?.messages_dropped_tx ?? 0}</span
|
||||||
|
>
|
||||||
|
</mwc-list-item>
|
||||||
|
<mwc-list-item twoline hasmeta>
|
||||||
|
<span>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_rx.label"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="secondary">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.messages_dropped_rx.tooltip"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="meta"
|
||||||
|
>${this._statistics?.messages_dropped_rx ?? 0}</span
|
||||||
|
>
|
||||||
|
</mwc-list-item>
|
||||||
|
<mwc-list-item twoline hasmeta>
|
||||||
|
<span>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.nak.label"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="secondary">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.nak.tooltip"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="meta">${this._statistics?.nak ?? 0}</span>
|
||||||
|
</mwc-list-item>
|
||||||
|
<mwc-list-item twoline hasmeta>
|
||||||
|
<span>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.can.label"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="secondary">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.can.tooltip"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="meta">${this._statistics?.can ?? 0}</span>
|
||||||
|
</mwc-list-item>
|
||||||
|
<mwc-list-item twoline hasmeta>
|
||||||
|
<span>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.timeout_ack.label"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="secondary">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.timeout_ack.tooltip"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="meta"
|
||||||
|
>${this._statistics?.timeout_ack ?? 0}</span
|
||||||
|
>
|
||||||
|
</mwc-list-item>
|
||||||
|
<mwc-list-item twoline hasmeta>
|
||||||
|
<span>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.timeout_response.label"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="secondary">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.timeout_response.tooltip"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="meta"
|
||||||
|
>${this._statistics?.timeout_response ?? 0}</span
|
||||||
|
>
|
||||||
|
</mwc-list-item>
|
||||||
|
<mwc-list-item twoline hasmeta>
|
||||||
|
<span>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.timeout_callback.label"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="secondary">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.dashboard.statistics.timeout_callback.tooltip"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span slot="meta"
|
||||||
|
>${this._statistics?.timeout_callback ?? 0}</span
|
||||||
|
>
|
||||||
|
</mwc-list-item>
|
||||||
|
</mwc-list>
|
||||||
|
</ha-expansion-panel>
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<mwc-button
|
||||||
|
@click=${this._removeNodeClicked}
|
||||||
|
.disabled=${this._status !== "connected" ||
|
||||||
|
(this._network?.controller.inclusion_state !==
|
||||||
|
InclusionState.Idle &&
|
||||||
|
this._network?.controller.inclusion_state !==
|
||||||
|
InclusionState.SmartStart)}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.common.remove_node"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
<mwc-button
|
||||||
|
@click=${this._rebuildNetworkRoutesClicked}
|
||||||
|
.disabled=${this._status === "disconnected"}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.common.rebuild_network_routes"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
<mwc-button @click=${this._openOptionFlow}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.zwave_js.common.reconfigure_server"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
<ha-card>
|
||||||
|
<div class="card-header">
|
||||||
|
<h1>Third-Party Data Reporting</h1>
|
||||||
|
${this._dataCollectionOptIn !== undefined
|
||||||
|
? html`
|
||||||
|
<ha-switch
|
||||||
|
.checked=${this._dataCollectionOptIn === true}
|
||||||
|
@change=${this._dataCollectionToggled}
|
||||||
|
></ha-switch>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<ha-circular-progress
|
||||||
|
size="small"
|
||||||
|
indeterminate
|
||||||
|
></ha-circular-progress>
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<p>
|
||||||
|
Enable the reporting of anonymized telemetry and statistics
|
||||||
|
to the <em>Z-Wave JS organization</em>. This data will be
|
||||||
|
used to focus development efforts and improve the user
|
||||||
|
experience. Information about the data that is collected and
|
||||||
|
how it is used, including an example of the data collected,
|
||||||
|
can be found in the
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://zwave-js.github.io/node-zwave-js/#/data-collection/data-collection"
|
||||||
|
>Z-Wave JS data collection documentation</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`
|
||||||
|
: ``}
|
||||||
<ha-fab
|
<ha-fab
|
||||||
slot="fab"
|
slot="fab"
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
|
@ -3,12 +3,20 @@ import {
|
|||||||
mdiDelete,
|
mdiDelete,
|
||||||
mdiHelpCircle,
|
mdiHelpCircle,
|
||||||
mdiInformationOutline,
|
mdiInformationOutline,
|
||||||
|
mdiPalette,
|
||||||
mdiPencilOff,
|
mdiPencilOff,
|
||||||
mdiPlay,
|
mdiPlay,
|
||||||
mdiPlus,
|
mdiPlus,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
nothing,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { differenceInDays } from "date-fns/esm";
|
import { differenceInDays } from "date-fns/esm";
|
||||||
@ -21,6 +29,7 @@ import {
|
|||||||
} from "../../../components/data-table/ha-data-table";
|
} from "../../../components/data-table/ha-data-table";
|
||||||
import "../../../components/ha-button-related-filter-menu";
|
import "../../../components/ha-button-related-filter-menu";
|
||||||
import "../../../components/ha-fab";
|
import "../../../components/ha-fab";
|
||||||
|
import "../../../components/ha-button";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-state-icon";
|
import "../../../components/ha-state-icon";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
@ -214,6 +223,7 @@ class HaSceneDashboard extends LitElement {
|
|||||||
.columns=${this._columns(this.hass.locale, this.narrow)}
|
.columns=${this._columns(this.hass.locale, this.narrow)}
|
||||||
id="entity_id"
|
id="entity_id"
|
||||||
.data=${this._scenes(this.scenes, this._filteredScenes)}
|
.data=${this._scenes(this.scenes, this._filteredScenes)}
|
||||||
|
.empty=${!this.scenes.length}
|
||||||
.activeFilters=${this._activeFilters}
|
.activeFilters=${this._activeFilters}
|
||||||
.noDataText=${this.hass.localize(
|
.noDataText=${this.hass.localize(
|
||||||
"ui.panel.config.scene.picker.no_scenes"
|
"ui.panel.config.scene.picker.no_scenes"
|
||||||
@ -238,6 +248,30 @@ class HaSceneDashboard extends LitElement {
|
|||||||
@related-changed=${this._relatedFilterChanged}
|
@related-changed=${this._relatedFilterChanged}
|
||||||
>
|
>
|
||||||
</ha-button-related-filter-menu>
|
</ha-button-related-filter-menu>
|
||||||
|
${!this.scenes.length
|
||||||
|
? html`<div class="empty" slot="empty">
|
||||||
|
<ha-svg-icon .path=${mdiPalette}></ha-svg-icon>
|
||||||
|
<h1>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.scene.picker.empty_header"
|
||||||
|
)}
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
${this.hass.localize("ui.panel.config.scene.picker.empty_text")}
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href=${documentationUrl(this.hass, "/docs/scene/editor/")}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<ha-button>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.scene.picker.learn_more"
|
||||||
|
)}
|
||||||
|
</ha-button>
|
||||||
|
</a>
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
<a href="/config/scene/edit/new" slot="fab">
|
<a href="/config/scene/edit/new" slot="fab">
|
||||||
<ha-fab
|
<ha-fab
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@ -336,7 +370,7 @@ class HaSceneDashboard extends LitElement {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
${this.hass.localize("ui.panel.config.scene.picker.learn_more")}
|
${this.hass.localize("ui.panel.config.common.learn_more")}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
`,
|
`,
|
||||||
@ -350,6 +384,11 @@ class HaSceneDashboard extends LitElement {
|
|||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
.empty {
|
||||||
|
--paper-font-headline_-_font-size: 28px;
|
||||||
|
--mdc-icon-size: 80px;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -538,7 +538,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
value.valid
|
value.valid
|
||||||
? ""
|
? ""
|
||||||
: html`${this.hass.localize(
|
: html`${this.hass.localize(
|
||||||
`ui.panel.config.automation.editor.${key}s.header`
|
`ui.panel.config.automation.editor.${key}s.name`
|
||||||
)}:
|
)}:
|
||||||
${value.error}<br />`
|
${value.error}<br />`
|
||||||
);
|
);
|
||||||
|
@ -5,10 +5,18 @@ import {
|
|||||||
mdiInformationOutline,
|
mdiInformationOutline,
|
||||||
mdiPlay,
|
mdiPlay,
|
||||||
mdiPlus,
|
mdiPlus,
|
||||||
|
mdiScriptText,
|
||||||
mdiTransitConnection,
|
mdiTransitConnection,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import { differenceInDays } from "date-fns/esm";
|
import { differenceInDays } from "date-fns/esm";
|
||||||
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
|
import {
|
||||||
|
CSSResultGroup,
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
|
nothing,
|
||||||
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@ -241,6 +249,7 @@ class HaScriptPicker extends LitElement {
|
|||||||
.tabs=${configSections.automations}
|
.tabs=${configSections.automations}
|
||||||
.columns=${this._columns(this.narrow, this.hass.locale)}
|
.columns=${this._columns(this.narrow, this.hass.locale)}
|
||||||
.data=${this._scripts(this.scripts, this._filteredScripts)}
|
.data=${this._scripts(this.scripts, this._filteredScripts)}
|
||||||
|
.empty=${!this.scripts.length}
|
||||||
.activeFilters=${this._activeFilters}
|
.activeFilters=${this._activeFilters}
|
||||||
id="entity_id"
|
id="entity_id"
|
||||||
.noDataText=${this.hass.localize(
|
.noDataText=${this.hass.localize(
|
||||||
@ -266,6 +275,32 @@ class HaScriptPicker extends LitElement {
|
|||||||
@related-changed=${this._relatedFilterChanged}
|
@related-changed=${this._relatedFilterChanged}
|
||||||
>
|
>
|
||||||
</ha-button-related-filter-menu>
|
</ha-button-related-filter-menu>
|
||||||
|
${!this.scripts.length
|
||||||
|
? html` <div class="empty" slot="empty">
|
||||||
|
<ha-svg-icon .path=${mdiScriptText}></ha-svg-icon>
|
||||||
|
<h1>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.script.picker.empty_header"
|
||||||
|
)}
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.script.picker.empty_text"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href=${documentationUrl(this.hass, "/docs/script/editor/")}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<ha-button>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.script.picker.learn_more"
|
||||||
|
)}
|
||||||
|
</ha-button>
|
||||||
|
</a>
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
<ha-fab
|
<ha-fab
|
||||||
slot="fab"
|
slot="fab"
|
||||||
?is-wide=${this.isWide}
|
?is-wide=${this.isWide}
|
||||||
@ -385,7 +420,7 @@ class HaScriptPicker extends LitElement {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
${this.hass.localize("ui.panel.config.script.picker.learn_more")}
|
${this.hass.localize("ui.panel.config.common.learn_more")}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
`,
|
`,
|
||||||
@ -471,6 +506,11 @@ class HaScriptPicker extends LitElement {
|
|||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
.empty {
|
||||||
|
--paper-font-headline_-_font-size: 28px;
|
||||||
|
--mdc-icon-size: 80px;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import memoizeOne from "memoize-one";
|
|||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||||
import "../../../components/ha-circular-progress";
|
import "../../../components/ha-circular-progress";
|
||||||
import "../../../components/ha-markdown";
|
|
||||||
import "../../../components/ha-select";
|
import "../../../components/ha-select";
|
||||||
import {
|
import {
|
||||||
extractApiErrorMessage,
|
extractApiErrorMessage,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user