Compare commits

..

3 Commits

Author SHA1 Message Date
Paulus Schoutsen
d011794884 Bumped version to 20220502.0 2022-05-02 15:07:50 -07:00
Bram Kragten
9b782cabe2 Update hat-script-graph.ts 2022-05-02 15:07:15 -07:00
Bram Kragten
a355bedd77 Indicate things are disabled in trace graph 2022-05-02 15:07:15 -07:00
586 changed files with 10334 additions and 24238 deletions

View File

@@ -51,7 +51,7 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
<!--
Provide details about the versions you are using, which helps us reproducing
and finding the issue quicker. Version information is found in the
Home Assistant frontend: Settings -> About.
Home Assistant frontend: Configuration -> Info.
Browser version and operating system is important! Please try to replicate
your issue in a different browser and be sure to include your findings.

View File

@@ -1,4 +1,4 @@
name: Report a bug with the UI / Dashboards
name: Report a bug with the UI, Frontend or Lovelace
description: Report an issue related to the Home Assistant frontend.
labels: bug
body:
@@ -9,7 +9,7 @@ body:
If you have a feature or enhancement request for the frontend, please [start an discussion][fr] instead of creating an issue.
**Please not not report issues for custom cards.**
**Please not not report issues for custom Lovelace cards.**
[fr]: https://github.com/home-assistant/frontend/discussions
[releases]: https://github.com/home-assistant/home-assistant/releases
@@ -64,7 +64,7 @@ body:
label: What version of Home Assistant Core has the issue?
placeholder: core-
description: >
Can be found in: [Settings -> About](https://my.home-assistant.io/redirect/info/).
Can be found in the Configuration panel -> Info.
- type: input
attributes:
label: What was the last working version of Home Assistant Core?

View File

@@ -1,17 +1,17 @@
blank_issues_enabled: false
contact_links:
- name: Request a feature for the UI / Dashboards
- name: Request a feature for the UI, Frontend or Lovelace
url: https://github.com/home-assistant/frontend/discussions/category_choices
about: Request an new feature for the Home Assistant frontend.
- name: Report a bug that is NOT related to the UI / Dashboards
- name: Report a bug that is NOT related to the UI, Frontend or Lovelace
url: https://github.com/home-assistant/core/issues
about: This is the issue tracker for our frontend. Please report other issues in the backend ("core") repository.
about: This is the issue tracker for our frontend. Please report other issues with the backend repository.
- name: Report incorrect or missing information on our website
url: https://github.com/home-assistant/home-assistant.io/issues
about: Our documentation has its own issue tracker. Please report issues with the website there.
- name: I have a question or need support
url: https://www.home-assistant.io/help
about: We use GitHub for tracking bugs. Check our website for resources on getting help.
about: We use GitHub for tracking bugs, check our website for resources on getting help.
- name: I'm unsure where to go
url: https://www.home-assistant.io/join-chat
about: If you are unsure where to go, then joining our chat is recommended; Just ask!

View File

@@ -1,8 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: weekly
time: "06:00"
open-pull-requests-limit: 10

View File

@@ -11,7 +11,7 @@ on:
- master
env:
NODE_VERSION: 16
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=6144
jobs:
@@ -19,9 +19,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
@@ -43,9 +43,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
@@ -62,9 +62,9 @@ jobs:
needs: [lint, test]
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
@@ -81,9 +81,9 @@ jobs:
needs: [lint, test]
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn

View File

@@ -23,7 +23,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
@@ -36,14 +36,14 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -57,4 +57,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v1

View File

@@ -6,7 +6,7 @@ on:
- dev
env:
NODE_VERSION: 16
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=6144
jobs:
@@ -14,9 +14,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn

View File

@@ -9,7 +9,7 @@ jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v3.0.0
- uses: dessant/lock-threads@v2.0.1
with:
github-token: ${{ github.token }}
issue-lock-inactive-days: "30"

View File

@@ -1,73 +0,0 @@
name: Nightly
on:
workflow_dispatch:
schedule:
- cron: "0 1 * * *"
env:
PYTHON_VERSION: "3.10"
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144
permissions:
actions: none
jobs:
nightly:
name: Nightly
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout the repository
uses: actions/checkout@v3
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install
- name: Download translations
run: ./script/translations_download
env:
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
- name: Bump version
run: script/version_bump.js nightly
- name: Build nightly Python wheels
run: |
pip install build
yarn install
script/build_frontend
rm -rf dist home_assistant_frontend.egg-info
python3 -m build
- name: Archive translations
run: tar -czvf translations.tar.gz translations
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist/home_assistant_frontend*.whl
if-no-files-found: error
- name: Upload translations
uses: actions/upload-artifact@v3
with:
name: translations
path: translations.tar.gz
if-no-files-found: error

View File

@@ -6,8 +6,8 @@ on:
- published
env:
PYTHON_VERSION: "3.10"
NODE_VERSION: 16
PYTHON_VERSION: 3.8
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=6144
# Set default workflow permissions
@@ -21,21 +21,21 @@ jobs:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write # Required to upload release assets
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Verify version
uses: home-assistant/actions/helpers/verify-version@master
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v4
uses: actions/setup-python@v2
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
@@ -74,11 +74,33 @@ jobs:
version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' )
echo "home-assistant-frontend==$version" > ./requirements.txt
- name: Build wheels
uses: home-assistant/wheels@2022.06.7
- name: Upload requirements.txt
uses: actions/upload-artifact@v2
with:
abi: cp310
tag: musllinux_1_2
arch: amd64
name: requirements
path: ./requirements.txt
build-wheels:
name: Build wheels for ${{ matrix.arch }}
needs: wheels-init
runs-on: ubuntu-latest
strategy:
matrix:
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
tag:
- "3.9-alpine3.14"
steps:
- name: Download requirements.txt
uses: actions/download-artifact@v2
with:
name: requirements
- name: Build wheels
uses: home-assistant/wheels@master
with:
tag: ${{ matrix.tag }}
arch: ${{ matrix.arch }}
wheels-host: ${{ secrets.WHEELS_HOST }}
wheels-key: ${{ secrets.WHEELS_KEY }}
wheels-user: wheels
requirements: "requirements.txt"

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 90 days stale policy
uses: actions/stale@v5.1.1
uses: actions/stale@v3.0.13
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 90

View File

@@ -8,7 +8,7 @@ on:
- src/translations/en.json
env:
NODE_VERSION: 16
NODE_VERSION: 14
jobs:
upload:
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Upload Translations
run: |

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn run lint-staged --relative --shell "/bin/bash"

2
.nvmrc
View File

@@ -1 +1 @@
16
14

2
.vscode/tasks.json vendored
View File

@@ -181,7 +181,7 @@
{
"label": "Run HA Core for Supervisor in devcontainer",
"type": "shell",
"command": "SUPERVISOR=${input:supervisorHost} SUPERVISOR_TOKEN=${input:supervisorToken} script/core",
"command": "HASSIO=${input:supervisorHost} HASSIO_TOKEN=${input:supervisorToken} script/core",
"isBackground": true,
"group": {
"kind": "build",

View File

@@ -26,8 +26,8 @@ module.exports = {
},
version() {
const version = fs
.readFileSync(path.resolve(paths.polymer_dir, "pyproject.toml"), "utf8")
.match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/);
.readFileSync(path.resolve(paths.polymer_dir, "setup.cfg"), "utf8")
.match(/version\W+=\W(\d{8}\.\d)/);
if (!version) {
throw Error("Version not found");
}

View File

@@ -156,12 +156,3 @@ gulp.task("gen-icons-json", (done) => {
done();
});
gulp.task("gen-dummy-icons-json", (done) => {
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
fs.writeFileSync(path.resolve(OUTPUT_DIR, "iconList.json"), "[]");
done();
});

View File

@@ -9,7 +9,6 @@ require("./compress.js");
require("./rollup.js");
require("./gather-static.js");
require("./translations.js");
require("./gen-icons-json.js");
gulp.task(
"develop-hassio",
@@ -18,7 +17,6 @@ gulp.task(
process.env.NODE_ENV = "development";
},
"clean-hassio",
"gen-dummy-icons-json",
"gen-index-hassio-dev",
"build-supervisor-translations",
"copy-translations-supervisor",
@@ -35,7 +33,6 @@ gulp.task(
process.env.NODE_ENV = "production";
},
"clean-hassio",
"gen-dummy-icons-json",
"build-supervisor-translations",
"copy-translations-supervisor",
"build-locale-data",

View File

@@ -1,30 +1 @@
[
{
"path": "M20,20H7A2,2 0 0,1 5,18V8.94L2.23,5.64C2.09,5.47 2,5.24 2,5A1,1 0 0,1 3,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20M8.5,7A0.5,0.5 0 0,0 8,7.5V8.5A0.5,0.5 0 0,0 8.5,9H18.5A0.5,0.5 0 0,0 19,8.5V7.5A0.5,0.5 0 0,0 18.5,7H8.5M8.5,11A0.5,0.5 0 0,0 8,11.5V12.5A0.5,0.5 0 0,0 8.5,13H18.5A0.5,0.5 0 0,0 19,12.5V11.5A0.5,0.5 0 0,0 18.5,11H8.5M8.5,15A0.5,0.5 0 0,0 8,15.5V16.5A0.5,0.5 0 0,0 8.5,17H13.5A0.5,0.5 0 0,0 14,16.5V15.5A0.5,0.5 0 0,0 13.5,15H8.5Z",
"name": "android-messages"
},
{
"path": "M4,6H2V20A2,2 0 0,0 4,22H18V20H4V6M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M20,12L17.5,10.5L15,12V4H20V12Z",
"name": "book-variant-multiple"
},
{
"path": "M21,14H3V4H21M21,2H3C1.89,2 1,2.89 1,4V16A2,2 0 0,0 3,18H10L8,21V22H16V21L14,18H21A2,2 0 0,0 23,16V4C23,2.89 22.1,2 21,2Z",
"name": "desktop-mac"
},
{
"path": "M21,14V4H3V14H21M21,2A2,2 0 0,1 23,4V16A2,2 0 0,1 21,18H14L16,21V22H8V21L10,18H3C1.89,18 1,17.1 1,16V4C1,2.89 1.89,2 3,2H21M4,5H15V10H4V5M16,5H20V7H16V5M20,8V13H16V8H20M4,11H9V13H4V11M10,11H15V13H10V11Z",
"name": "desktop-mac-dashboard"
},
{
"path": "M22,24L16.75,19L17.38,21H4.5A2.5,2.5 0 0,1 2,18.5V3.5A2.5,2.5 0 0,1 4.5,1H19.5A2.5,2.5 0 0,1 22,3.5V24M12,6.8C9.32,6.8 7.44,7.95 7.44,7.95C8.47,7.03 10.27,6.5 10.27,6.5L10.1,6.33C8.41,6.36 6.88,7.53 6.88,7.53C5.16,11.12 5.27,14.22 5.27,14.22C6.67,16.03 8.75,15.9 8.75,15.9L9.46,15C8.21,14.73 7.42,13.62 7.42,13.62C7.42,13.62 9.3,14.9 12,14.9C14.7,14.9 16.58,13.62 16.58,13.62C16.58,13.62 15.79,14.73 14.54,15L15.25,15.9C15.25,15.9 17.33,16.03 18.73,14.22C18.73,14.22 18.84,11.12 17.12,7.53C17.12,7.53 15.59,6.36 13.9,6.33L13.73,6.5C13.73,6.5 15.53,7.03 16.56,7.95C16.56,7.95 14.68,6.8 12,6.8M9.93,10.59C10.58,10.59 11.11,11.16 11.1,11.86C11.1,12.55 10.58,13.13 9.93,13.13C9.29,13.13 8.77,12.55 8.77,11.86C8.77,11.16 9.28,10.59 9.93,10.59M14.1,10.59C14.75,10.59 15.27,11.16 15.27,11.86C15.27,12.55 14.75,13.13 14.1,13.13C13.46,13.13 12.94,12.55 12.94,11.86C12.94,11.16 13.45,10.59 14.1,10.59Z",
"name": "discord"
},
{
"path": "M8.06,7.78C7.5,7.78 7.17,7.73 7.08,7.64L6.66,13.73C7.19,14.05 7.88,14.3 8.72,14.5C9.56,14.71 10.78,14.77 12.38,14.67C13.97,14.58 15.63,14.23 17.34,13.64L16.55,4.22C15.67,5.09 14.38,5.91 12.66,6.66C11.13,7.31 9.81,7.69 8.72,7.78H8.06M7.97,5.34C7.28,5.94 7,6.34 7.13,6.56C7.22,6.78 7.7,6.84 8.58,6.75C9.67,6.66 10.91,6.31 12.28,5.72C13.22,5.31 14.03,4.88 14.72,4.41C15.41,3.94 15.88,3.55 16.13,3.23C16.38,2.92 16.47,2.7 16.41,2.58C16.34,2.42 16.03,2.34 15.47,2.34C14.34,2.34 12.94,2.7 11.25,3.42C9.81,4.05 8.72,4.69 7.97,5.34M17.34,2.2C17.41,2.33 17.44,2.47 17.44,2.63L18.61,17C18.61,18.73 18,20.09 16.83,21.07C15.64,22.05 14.03,22.55 12,22.55C10,22.55 8.4,22.04 7.2,21C6,20 5.39,18.64 5.39,16.92L6.09,6.47C6.09,6.22 6.2,5.94 6.42,5.63C6.64,5.31 6.84,5.06 7.03,4.88L7.36,4.59C8.33,3.78 9.5,3.08 10.88,2.5C11.81,2.08 12.73,1.77 13.62,1.57C14.5,1.37 15.3,1.3 16,1.38C16.71,1.46 17.16,1.73 17.34,2.2Z",
"name": "google-home"
},
{
"path": "M19.25,19H4.75V3H19.25M14,22H10V21H14M18,0H6A3,3 0 0,0 3,3V21A3,3 0 0,0 6,24H18A3,3 0 0,0 21,21V3A3,3 0 0,0 18,0Z",
"name": "tablet-android"
}
]
[]

View File

@@ -76,7 +76,7 @@ const createWebpackConfig = ({
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
},
plugins: [
!isStatsBuild && new WebpackBar({ fancy: !isProdBuild }),
new WebpackBar({ fancy: !isProdBuild }),
new WebpackManifestPlugin({
// Only include the JS of entrypoints
filter: (file) => file.isInitial && !file.name.endsWith(".map"),

View File

@@ -1,9 +0,0 @@
# These redirects are handled by Netlify
#
# Some custom cards are not prefixing the instance URL when fetching data
# and can end up fetching the data from the Cast domain instead of HA.
# This will make sure that some common ones are replaced with a placeholder.
/api/camera_proxy/* /images/google-nest-hub.png
/api/camera_proxy_stream/* /images/google-nest-hub.png
/api/media_player_proxy/* /images/google-nest-hub.png

View File

@@ -194,7 +194,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
type: "state-icon",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "group.downstairs_lights",
},
service: "homeassistant.toggle",

View File

@@ -59,7 +59,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: {
hidden: true,
radius: 50,
friendly_name: "School",
friendly_name: "Skolan",
icon: "mdi:school",
},
},
@@ -137,7 +137,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
state: "73",
attributes: {
unit_of_measurement: "%",
friendly_name: "Oskar battery",
friendly_name: "oskar batteri",
device_class: "battery",
},
},
@@ -146,7 +146,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
state: "88",
attributes: {
unit_of_measurement: "%",
friendly_name: "Bella battery",
friendly_name: "bella batteri",
device_class: "battery",
},
},
@@ -154,7 +154,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
entity_id: "binary_sensor.unifi_camera",
state: "off",
attributes: {
friendly_name: "Motion sensor camera",
friendly_name: "R\u00f6relsesensor kamera",
icon: "mdi:walk",
},
},
@@ -707,7 +707,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
},
],
cloudiness: 25,
friendly_name: "Weather",
friendly_name: "V\u00e4der",
},
},
"binary_sensor.ubiquiti_switch": {
@@ -731,7 +731,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
round_trip_time_max: "0.626",
round_trip_time_mdev: "",
round_trip_time_min: "0.358",
friendly_name: "Entrance camera",
friendly_name: "Entr\u00e9 kamera",
device_class: "connectivity",
icon: "mdi:cctv",
},
@@ -797,7 +797,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: {
battery_level: 34,
on: true,
friendly_name: "Porch motion sensor",
friendly_name: "altan_motion_sensor",
device_class: "motion",
},
},
@@ -807,7 +807,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: {
battery_level: 88,
on: true,
friendly_name: "Back door sensor",
friendly_name: "Altand\u00f6rren sensor",
device_class: "opening",
icon: "mdi:door",
},
@@ -818,7 +818,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: {
battery_level: 74,
on: true,
friendly_name: "Bathroom motion sensor",
friendly_name: "badrumssensor",
device_class: "motion",
},
},
@@ -829,7 +829,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 47,
on: true,
dark: true,
friendly_name: "Basement motion sensor",
friendly_name: "R\u00f6relsesensor k\u00e4llaren 1",
device_class: "motion",
icon: "mdi:walk",
},
@@ -841,7 +841,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 60,
on: true,
dark: true,
friendly_name: "Laundy room motion sensor",
friendly_name: "R\u00f6relsesensor tv\u00e4ttstugan",
device_class: "motion",
icon: "mdi:walk",
},
@@ -863,7 +863,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: {
battery_level: 60,
on: true,
friendly_name: "Pantry motion sensor",
friendly_name: "R\u00f6relsesensor skafferiet",
device_class: "motion",
icon: "mdi:walk",
},
@@ -875,7 +875,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 60,
on: true,
dark: true,
friendly_name: "Stair motion sensor",
friendly_name: "R\u00f6relsesensor k\u00e4llaren 2",
device_class: "motion",
icon: "mdi:walk",
},
@@ -887,7 +887,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 47,
on: true,
dark: true,
friendly_name: "Bench sensor",
friendly_name: "B\u00e4nksensor",
device_class: "motion",
},
},

View File

@@ -277,7 +277,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
],
show_header_toggle: false,
type: "entities",
title: "Bandwidth",
title: "Bandbredd",
},
// {
// title: "Updater",

View File

@@ -377,7 +377,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC bed",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "script.air_cleaner_quiet",
},
service: "script.turn_on",
@@ -390,7 +390,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC bed",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "script.air_cleaner_auto",
},
service: "script.turn_on",
@@ -403,7 +403,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC bed",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "script.air_cleaner_turbo",
},
service: "script.turn_on",
@@ -416,7 +416,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "script.ac_off",
},
service: "script.turn_on",
@@ -429,7 +429,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "script.ac_on",
},
service: "script.turn_on",
@@ -629,7 +629,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "scene.morning_lights",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "scene.morning_lights",
},
service: "scene.turn_on",
@@ -641,7 +641,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "scene.movie_time",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "scene.movie_time",
},
service: "scene.turn_on",
@@ -702,7 +702,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "light.downstairs_lights",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "light.downstairs_lights",
},
service: "light.toggle",
@@ -714,7 +714,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "light.upstairs_lights",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "light.upstairs_lights",
},
service: "light.toggle",

View File

@@ -1,4 +1,5 @@
// Compat needs to be first import
import "../../src/resources/compatibility";
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
import { navigate } from "../../src/common/navigate";
import {
@@ -6,14 +7,9 @@ import {
provideHass,
} from "../../src/fake_data/provide_hass";
import { HomeAssistantAppEl } from "../../src/layouts/home-assistant";
import "../../src/resources/compatibility";
import { HomeAssistant } from "../../src/types";
import { selectedDemoConfig } from "./configs/demo-configs";
import { mockAuth } from "./stubs/auth";
import { mockConfigEntries } from "./stubs/config_entries";
import { mockEnergy } from "./stubs/energy";
import { energyEntities } from "./stubs/entities";
import { mockEntityRegistry } from "./stubs/entity_registry";
import { mockEvents } from "./stubs/events";
import { mockFrontend } from "./stubs/frontend";
import { mockHistory } from "./stubs/history";
@@ -24,6 +20,9 @@ import { mockShoppingList } from "./stubs/shopping_list";
import { mockSystemLog } from "./stubs/system_log";
import { mockTemplate } from "./stubs/template";
import { mockTranslations } from "./stubs/translations";
import { mockEnergy } from "./stubs/energy";
import { mockConfig } from "./stubs/config";
import { energyEntities } from "./stubs/entities";
class HaDemo extends HomeAssistantAppEl {
protected async _initializeHass() {
@@ -52,38 +51,8 @@ class HaDemo extends HomeAssistantAppEl {
mockMediaPlayer(hass);
mockFrontend(hass);
mockEnergy(hass);
mockConfig(hass);
mockPersistentNotification(hass);
mockConfigEntries(hass);
mockEntityRegistry(hass, [
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.co2_intensity",
id: "sensor.co2_intensity",
name: null,
icon: null,
platform: "co2signal",
hidden_by: null,
entity_category: null,
has_entity_name: false,
},
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.grid_fossil_fuel_percentage",
id: "sensor.co2_intensity",
name: null,
icon: null,
platform: "co2signal",
hidden_by: null,
entity_category: null,
has_entity_name: false,
},
]);
hass.addEntities(energyEntities());

41
demo/src/stubs/config.ts Normal file
View File

@@ -0,0 +1,41 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockConfig = (hass: MockHomeAssistant) => {
hass.mockAPI("config/config_entries/entry", () => [
{
entry_id: "co2signal",
domain: "co2signal",
title: "CO2 Signal",
source: "user",
state: "loaded",
supports_options: false,
supports_unload: true,
pref_disable_new_entities: false,
pref_disable_polling: false,
disabled_by: null,
reason: null,
},
]);
hass.mockWS("config/entity_registry/list", () => [
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.co2_intensity",
name: null,
icon: null,
platform: "co2signal",
},
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.grid_fossil_fuel_percentage",
name: null,
icon: null,
platform: "co2signal",
},
]);
};

View File

@@ -1,20 +0,0 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockConfigEntries = (hass: MockHomeAssistant) => {
hass.mockWS("config_entries/get", () => [
{
entry_id: "co2signal",
domain: "co2signal",
title: "CO2 Signal",
source: "user",
state: "loaded",
supports_options: false,
supports_remove_device: false,
supports_unload: true,
pref_disable_new_entities: false,
pref_disable_polling: false,
disabled_by: null,
reason: null,
},
]);
};

View File

@@ -1,4 +1,4 @@
import { format, startOfToday, startOfTomorrow } from "date-fns/esm";
import { format, startOfToday, startOfTomorrow } from "date-fns";
import { EnergySolarForecasts } from "../../../src/data/energy";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";

View File

@@ -4,6 +4,4 @@ import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockEntityRegistry = (
hass: MockHomeAssistant,
data: EntityRegistryEntry[] = []
) => {
hass.mockWS("config/entity_registry/list", () => data);
};
) => hass.mockWS("config/entity_registry/list", () => data);

View File

@@ -4,7 +4,7 @@ import {
addMonths,
differenceInHours,
endOfDay,
} from "date-fns/esm";
} from "date-fns";
import { HassEntity } from "home-assistant-js-websocket";
import { StatisticValue } from "../../../src/data/history";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
@@ -466,7 +466,6 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
return results;
}
);
mockHass.mockWS("recorder/get_statistics_metadata", () => []);
mockHass.mockWS("history/list_statistic_ids", () => []);
mockHass.mockWS(
"history/statistics_during_period",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -8,7 +8,7 @@ module.exports = [
{
category: "lovelace",
// Label for in the sidebar
header: "Dashboards",
header: "Lovelace",
// Specify order of pages. Any pages in the category folder but not listed here will
// automatically be added after the pages listed here.
pages: ["introduction"],
@@ -34,7 +34,7 @@ module.exports = [
},
{
category: "misc",
header: "Miscellaneous",
header: "Miscelaneous",
},
{
category: "brand",

View File

@@ -119,7 +119,7 @@ export const basicTrace: DemoTrace = {
params: {
domain: "input_boolean",
service: "toggle",
data: {},
service_data: {},
target: {
entity_id: ["input_boolean.toggle_4"],
},
@@ -164,7 +164,7 @@ export const basicTrace: DemoTrace = {
params: {
domain: "input_boolean",
service: "toggle",
data: {},
service_data: {},
target: {
entity_id: ["input_boolean.toggle_2"],
},
@@ -182,7 +182,7 @@ export const basicTrace: DemoTrace = {
params: {
domain: "input_boolean",
service: "toggle",
data: {},
service_data: {},
target: {
entity_id: ["input_boolean.toggle_3"],
},
@@ -200,7 +200,7 @@ export const basicTrace: DemoTrace = {
params: {
domain: "input_boolean",
service: "toggle",
data: {},
service_data: {},
target: {
entity_id: ["input_boolean.toggle_4"],
},
@@ -298,11 +298,11 @@ export const basicTrace: DemoTrace = {
source: "state of input_boolean.toggle_1",
entity_id: "automation.toggle_toggles",
context_id: "6cfcae368e7b3686fad6c59e83ae76c9",
when: 1616647011.240832,
when: "2021-03-25T04:36:51.240832+00:00",
domain: "automation",
},
{
when: 1616647011.249828,
when: "2021-03-25T04:36:51.249828+00:00",
name: "Toggle 4",
state: "on",
entity_id: "input_boolean.toggle_4",
@@ -313,7 +313,7 @@ export const basicTrace: DemoTrace = {
context_name: "Ensure Party mode",
},
{
when: 1616647011.258947,
when: "2021-03-25T04:36:51.258947+00:00",
name: "Toggle 2",
state: "on",
entity_id: "input_boolean.toggle_2",
@@ -324,7 +324,7 @@ export const basicTrace: DemoTrace = {
context_name: "Ensure Party mode",
},
{
when: 1616647011.261806,
when: "2021-03-25T04:36:51.261806+00:00",
name: "Toggle 3",
state: "off",
entity_id: "input_boolean.toggle_3",
@@ -335,7 +335,7 @@ export const basicTrace: DemoTrace = {
context_name: "Ensure Party mode",
},
{
when: 1616647011.265246,
when: "2021-03-25T04:36:51.265246+00:00",
name: "Toggle 4",
state: "off",
entity_id: "input_boolean.toggle_4",

View File

@@ -185,11 +185,11 @@ export const motionLightTrace: DemoTrace = {
"has been triggered by state of binary_sensor.pauluss_macbook_pro_camera_in_use",
source: "state of binary_sensor.pauluss_macbook_pro_camera_in_use",
entity_id: "automation.auto_elgato",
when: 1615702021.768492,
when: "2021-03-14T06:07:01.768492+00:00",
domain: "automation",
},
{
when: 1615702021.872187,
when: "2021-03-14T06:07:01.872187+00:00",
name: "Elgato Key Light Air",
state: "on",
entity_id: "light.elgato_key_light_air",
@@ -200,7 +200,7 @@ export const motionLightTrace: DemoTrace = {
context_name: "Auto Elgato",
},
{
when: 1615702073.284505,
when: "2021-03-14T06:07:53.284505+00:00",
name: "Elgato Key Light Air",
state: "off",
entity_id: "light.elgato_key_light_air",

View File

@@ -5,7 +5,7 @@ import { html, css, LitElement, PropertyValues } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../src/components/ha-icon-button";
import "../../src/managers/notification-manager";
import { HaExpansionPanel } from "../../src/components/ha-expansion-panel";
import "../../src/components/ha-expansion-panel";
import { haStyle } from "../../src/resources/styles";
import { PAGES, SIDEBAR } from "../build/import-pages";
import { dynamicElement } from "../../src/common/dom/dynamic-element-directive";
@@ -174,10 +174,9 @@ class HaGallery extends LitElement {
const menuItem = this.shadowRoot!.querySelector(
`a[href="#${this._page}"]`
)!;
// Make sure section is expanded
if (menuItem.parentElement instanceof HaExpansionPanel) {
menuItem.parentElement.expanded = true;
if (menuItem.parentElement instanceof HTMLDetailsElement) {
menuItem.parentElement.open = true;
}
}

View File

@@ -1,9 +1,7 @@
import { dump } from "js-yaml";
import { html, css, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement, property } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-yaml-editor";
import { Action } from "../../../../src/data/script";
import { describeAction } from "../../../../src/data/script_i18n";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
@@ -90,15 +88,6 @@ const ACTIONS = [
then: [{ delay: "00:00:01" }],
else: [{ delay: "00:00:05" }],
},
{
if: [{ condition: "state" }],
then: [{ delay: "00:00:01" }],
},
{
if: [{ condition: "state" }, { condition: "state" }],
then: [{ delay: "00:00:01" }],
else: [{ delay: "00:00:05" }],
},
{
choose: [
{
@@ -114,38 +103,16 @@ const ACTIONS = [
},
];
const initialAction: Action = {
service: "light.turn_on",
target: {
entity_id: "light.kitchen",
},
};
@customElement("demo-automation-describe-action")
export class DemoAutomationDescribeAction extends LitElement {
@property({ attribute: false }) hass!: HomeAssistant;
@state() _action = initialAction;
protected render(): TemplateResult {
if (!this.hass) {
return html``;
}
return html`
<ha-card header="Actions">
<div class="action">
<span>
${this._action
? describeAction(this.hass, this._action)
: "<invalid YAML>"}
</span>
<ha-yaml-editor
label="Action Config"
.defaultValue=${initialAction}
@value-changed=${this._dataChanged}
></ha-yaml-editor>
</div>
${ACTIONS.map(
(conf) => html`
<div class="action">
@@ -165,11 +132,6 @@ export class DemoAutomationDescribeAction extends LitElement {
hass.addEntities(ENTITIES);
}
private _dataChanged(ev: CustomEvent): void {
ev.stopPropagation();
this._action = ev.detail.isValid ? ev.detail.value : undefined;
}
static get styles() {
return css`
ha-card {
@@ -185,9 +147,6 @@ export class DemoAutomationDescribeAction extends LitElement {
span {
margin-right: 16px;
}
ha-yaml-editor {
width: 50%;
}
`;
}
}

View File

@@ -1,81 +1,31 @@
import { dump } from "js-yaml";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { html, css, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-yaml-editor";
import { Condition } from "../../../../src/data/automation";
import { describeCondition } from "../../../../src/data/automation_i18n";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../../src/types";
const ENTITIES = [
getEntity("light", "kitchen", "on", {
friendly_name: "Kitchen Light",
}),
getEntity("device_tracker", "person", "home", {
friendly_name: "Person",
}),
getEntity("zone", "home", "", {
friendly_name: "Home",
}),
];
const conditions = [
{ condition: "and" },
{ condition: "not" },
{ condition: "or" },
{ condition: "state", entity_id: "light.kitchen", state: "on" },
{
condition: "numeric_state",
entity_id: "light.kitchen",
attribute: "brightness",
below: 80,
above: 20,
},
{ condition: "state" },
{ condition: "numeric_state" },
{ condition: "sun", after: "sunset" },
{ condition: "sun", after: "sunrise", offset: "-01:00" },
{ condition: "zone", entity_id: "device_tracker.person", zone: "zone.home" },
{ condition: "sun", after: "sunrise" },
{ condition: "zone" },
{ condition: "time" },
{ condition: "template" },
];
const initialCondition: Condition = {
condition: "state",
entity_id: "light.kitchen",
state: "on",
};
@customElement("demo-automation-describe-condition")
export class DemoAutomationDescribeCondition extends LitElement {
@property({ attribute: false }) hass!: HomeAssistant;
@state() _condition = initialCondition;
protected render(): TemplateResult {
if (!this.hass) {
return html``;
}
return html`
<ha-card header="Conditions">
<div class="condition">
<span>
${this._condition
? describeCondition(this._condition, this.hass)
: "<invalid YAML>"}
</span>
<ha-yaml-editor
label="Condition Config"
.defaultValue=${initialCondition}
@value-changed=${this._dataChanged}
></ha-yaml-editor>
</div>
${conditions.map(
(conf) => html`
<div class="condition">
<span>${describeCondition(conf as any, this.hass)}</span>
<span>${describeCondition(conf as any)}</span>
<pre>${dump(conf)}</pre>
</div>
`
@@ -84,18 +34,6 @@ export class DemoAutomationDescribeCondition extends LitElement {
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
const hass = provideHass(this);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
private _dataChanged(ev: CustomEvent): void {
ev.stopPropagation();
this._condition = ev.detail.isValid ? ev.detail.value : undefined;
}
static get styles() {
return css`
ha-card {
@@ -111,9 +49,6 @@ export class DemoAutomationDescribeCondition extends LitElement {
span {
margin-right: 16px;
}
ha-yaml-editor {
width: 50%;
}
`;
}
}

View File

@@ -1,92 +1,34 @@
import { dump } from "js-yaml";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { html, css, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-yaml-editor";
import { Trigger } from "../../../../src/data/automation";
import { describeTrigger } from "../../../../src/data/automation_i18n";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../../src/types";
const ENTITIES = [
getEntity("light", "kitchen", "on", {
friendly_name: "Kitchen Light",
}),
getEntity("person", "person", "", {
friendly_name: "Person",
}),
getEntity("zone", "home", "", {
friendly_name: "Home",
}),
];
const triggers = [
{ platform: "state", entity_id: "light.kitchen", from: "off", to: "on" },
{ platform: "state" },
{ platform: "mqtt" },
{
platform: "geo_location",
source: "test_source",
zone: "zone.home",
event: "enter",
},
{ platform: "homeassistant", event: "start" },
{
platform: "numeric_state",
entity_id: "light.kitchen",
attribute: "brightness",
below: 80,
above: 20,
},
{ platform: "sun", event: "sunset" },
{ platform: "geo_location" },
{ platform: "homeassistant" },
{ platform: "numeric_state" },
{ platform: "sun" },
{ platform: "time_pattern" },
{ platform: "webhook" },
{
platform: "zone",
entity_id: "person.person",
zone: "zone.home",
event: "enter",
},
{ platform: "zone" },
{ platform: "tag" },
{ platform: "time", at: "15:32" },
{ platform: "time" },
{ platform: "template" },
{ platform: "event", event_type: "homeassistant_started" },
{ platform: "event" },
];
const initialTrigger: Trigger = {
platform: "state",
entity_id: "light.kitchen",
};
@customElement("demo-automation-describe-trigger")
export class DemoAutomationDescribeTrigger extends LitElement {
@property({ attribute: false }) hass!: HomeAssistant;
@state() _trigger = initialTrigger;
protected render(): TemplateResult {
if (!this.hass) {
return html``;
}
return html`
<ha-card header="Triggers">
<div class="trigger">
<span>
${this._trigger
? describeTrigger(this._trigger, this.hass)
: "<invalid YAML>"}
</span>
<ha-yaml-editor
label="Trigger Config"
.defaultValue=${initialTrigger}
@value-changed=${this._dataChanged}
></ha-yaml-editor>
</div>
${triggers.map(
(conf) => html`
<div class="trigger">
<span>${describeTrigger(conf as any, this.hass)}</span>
<span>${describeTrigger(conf as any)}</span>
<pre>${dump(conf)}</pre>
</div>
`
@@ -95,18 +37,6 @@ export class DemoAutomationDescribeTrigger extends LitElement {
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
const hass = provideHass(this);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
}
private _dataChanged(ev: CustomEvent): void {
ev.stopPropagation();
this._trigger = ev.detail.isValid ? ev.detail.value : undefined;
}
static get styles() {
return css`
ha-card {
@@ -122,9 +52,6 @@ export class DemoAutomationDescribeTrigger extends LitElement {
span {
margin-right: 16px;
}
ha-yaml-editor {
width: 50%;
}
`;
}
}

View File

@@ -1,32 +0,0 @@
---
title: Dialgos
subtitle: Dialogs provide important prompts in a user flow.
---
# Material Design 3
Our dialogs are based on the latest version of Material Design. Specs and guidelines can be found on it's [website](https://m3.material.io/components/dialogs/overview).
# Highlighted guidelines
## Content
* A best practice is to always use a title, even if it is optional by Material guidelines.
* People mainly read the title and a button. Put the most important information in those two.
* Try to avoid user generated content in the title, this could make the title unreadable long.
* If users become unsure, they read the description. Make sure this explains what will happen.
* Strive for minimalism.
## Buttons and X-icon
* Keep the labels short, for example `Save`, `Delete`, `Enable`.
* Dialog with actions must always have a discard button. On desktop a `Cancel` button and X-icon, on mobile only the X-icon.
* Destructive actions should be a red warning button.
* Alert or confirmation dialogs only have buttons and no X-icon.
* Try to avoid three buttons in one dialog. Especially when you leave the dialog task unfinished.
## Example
### Confirmation dialog
> **Delete dashboard?**
>
> Dashboard [dashboard name] will be permanently deleted from Home Assistant.
>
> Cancel / Delete

View File

@@ -3,13 +3,6 @@ title: Alerts
subtitle: An alert displays a short, important message in a way that attracts the user's attention without interrupting the user's task.
---
<style>
ha-alert {
display: block;
margin: 4px 0;
}
</style>
# Alert `<ha-alert>`
The alert offers four severity levels that set a distinctive icon and color.

View File

@@ -1,5 +0,0 @@
---
title: Expansion Panel
---
Expansion panel following all the ARIA guidelines.

View File

@@ -1,157 +0,0 @@
import { mdiPacMan } from "@mdi/js";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-expansion-panel";
import "../../../../src/components/ha-markdown";
import "../../components/demo-black-white-row";
import { LONG_TEXT } from "../../data/text";
const SHORT_TEXT = LONG_TEXT.substring(0, 113);
const SAMPLES: {
template: (slot: string, leftChevron: boolean) => TemplateResult;
}[] = [
{
template(slot, leftChevron) {
return html`
<ha-expansion-panel
slot=${slot}
.leftChevron=${leftChevron}
header="Attr header"
>
${SHORT_TEXT}
</ha-expansion-panel>
`;
},
},
{
template(slot, leftChevron) {
return html`
<ha-expansion-panel
slot=${slot}
.leftChevron=${leftChevron}
header="Attr header"
secondary="Attr secondary"
>
${SHORT_TEXT}
</ha-expansion-panel>
`;
},
},
{
template(slot, leftChevron) {
return html`
<ha-expansion-panel
slot=${slot}
.leftChevron=${leftChevron}
.header=${"Prop header"}
>
${SHORT_TEXT}
</ha-expansion-panel>
`;
},
},
{
template(slot, leftChevron) {
return html`
<ha-expansion-panel
slot=${slot}
.leftChevron=${leftChevron}
.header=${"Prop header"}
.secondary=${"Prop secondary"}
>
${SHORT_TEXT}
</ha-expansion-panel>
`;
},
},
{
template(slot, leftChevron) {
return html`
<ha-expansion-panel
slot=${slot}
.leftChevron=${leftChevron}
.header=${"Prop header"}
>
<span slot="secondary">Slot Secondary</span>
${SHORT_TEXT}
</ha-expansion-panel>
`;
},
},
{
template(slot, leftChevron) {
return html`
<ha-expansion-panel slot=${slot} .leftChevron=${leftChevron}>
<span slot="header">Slot header</span>
${SHORT_TEXT}
</ha-expansion-panel>
`;
},
},
{
template(slot, leftChevron) {
return html`
<ha-expansion-panel slot=${slot} .leftChevron=${leftChevron}>
<span slot="header">Slot header with actions</span>
<ha-icon-button
slot="icons"
label="Some Action"
.path=${mdiPacMan}
></ha-icon-button>
${SHORT_TEXT}
</ha-expansion-panel>
`;
},
},
{
template(slot, leftChevron) {
return html`
<ha-expansion-panel
slot=${slot}
.leftChevron=${leftChevron}
header="Attr Header with actions"
>
<ha-icon-button
slot="icons"
label="Some Action"
.path=${mdiPacMan}
></ha-icon-button>
${SHORT_TEXT}
</ha-expansion-panel>
`;
},
},
];
@customElement("demo-components-ha-expansion-panel")
export class DemoHaExpansionPanel extends LitElement {
protected render(): TemplateResult {
return html`
${SAMPLES.map(
(sample) => html`
<demo-black-white-row>
${["light", "dark"].map((slot) =>
sample.template(slot, slot === "dark")
)}
</demo-black-white-row>
`
)}
`;
}
static get styles() {
return css`
ha-expansion-panel {
margin: -16px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-components-ha-expansion-panel": DemoHaExpansionPanel;
}
}

View File

@@ -3,7 +3,6 @@ import "@material/mwc-button";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
import { mockConfigEntries } from "../../../../demo/src/stubs/config_entries";
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
@@ -21,22 +20,16 @@ const ENTITIES = [
}),
getEntity("media_player", "livingroom", "playing", {
friendly_name: "Livingroom",
media_content_type: "music",
device_class: "tv",
}),
getEntity("media_player", "lounge", "idle", {
friendly_name: "Lounge",
supported_features: 444983,
device_class: "speaker",
}),
getEntity("light", "bedroom", "on", {
friendly_name: "Bedroom",
effect: "colorloop",
effect_list: ["colorloop", "random"],
}),
getEntity("switch", "coffee", "off", {
friendly_name: "Coffee",
device_class: "switch",
}),
];
@@ -148,13 +141,7 @@ const SCHEMAS: {
selector: { attribute: { entity_id: "" } },
context: { filter_entity: "entity" },
},
{
name: "State",
selector: { state: { entity_id: "" } },
context: { filter_entity: "entity", filter_attribute: "Attribute" },
},
{ name: "Device", selector: { device: {} } },
{ name: "Config entry", selector: { config_entry: {} } },
{ name: "Duration", selector: { duration: {} } },
{ name: "area", selector: { area: {} } },
{ name: "target", selector: { target: {} } },
@@ -436,7 +423,6 @@ class DemoHaForm extends LitElement {
hass.addEntities(ENTITIES);
mockEntityRegistry(hass);
mockDeviceRegistry(hass, DEVICES);
mockConfigEntries(hass);
mockAreaRegistry(hass, AREAS);
mockHassioSupervisor(hass);
}

View File

@@ -3,7 +3,6 @@ import "@material/mwc-button";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
import { mockConfigEntries } from "../../../../demo/src/stubs/config_entries";
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
@@ -116,19 +115,11 @@ const SCHEMAS: {
name: "One of each",
input: {
entity: { name: "Entity", selector: { entity: {} } },
state: {
name: "State",
selector: { state: { entity_id: "alarm_control_panel.alarm" } },
},
attribute: {
name: "Attribute",
selector: { attribute: { entity_id: "" } },
},
device: { name: "Device", selector: { device: {} } },
config_entry: {
name: "Integration",
selector: { config_entry: {} },
},
duration: { name: "Duration", selector: { duration: {} } },
addon: { name: "Addon", selector: { addon: {} } },
area: { name: "Area", selector: { area: {} } },
@@ -285,7 +276,6 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
hass.addEntities(ENTITIES);
mockEntityRegistry(hass);
mockDeviceRegistry(hass, DEVICES);
mockConfigEntries(hass);
mockAreaRegistry(hass, AREAS);
mockHassioSupervisor(hass);
hass.mockWS("auth/sign_path", (params) => params);

View File

@@ -31,7 +31,7 @@ const ENTITIES = [
friendly_name: "Office Light",
}),
getEntity("fan", "kitchen", "on", {
friendly_name: "Kitchen Fan",
friendly_name: "Second Office Fan",
}),
getEntity("binary_sensor", "kitchen_door", "on", {
friendly_name: "Office Door",
@@ -102,7 +102,7 @@ class DemoArea extends LitElement {
picture: "/images/office.jpg",
},
{
name: "Kitchen",
name: "Second Office",
area_id: "kitchen",
picture: "/images/kitchen.png",
},

View File

@@ -75,10 +75,6 @@ const ENTITIES = [
timestamp: 1641801600,
friendly_name: "Date and Time",
}),
getEntity("sensor", "humidity", "23.2", {
friendly_name: "Humidity",
unit_of_measurement: "%",
}),
getEntity("input_select", "dropdown", "Soda", {
friendly_name: "Dropdown",
options: ["Soda", "Beer", "Wine"],
@@ -146,7 +142,6 @@ const CONFIGS = [
- light.non_existing
- climate.ecobee
- input_number.number
- sensor.humidity
`,
},
{
@@ -254,7 +249,7 @@ const CONFIGS = [
name: Bed light
action_name: Toggle light
service: light.toggle
data:
service_data:
entity_id: light.bed_light
- type: section
label: Links

View File

@@ -199,7 +199,7 @@ const CONFIGS = [
tap_action:
action: call-service
service: light.turn_on
data:
service_data:
entity_id: light.ceiling_lights
- entity: sun.sun
name: Regular

View File

@@ -1,11 +1,11 @@
---
title: Introduction
---
Dashboards have many different cards. Each card allows the user to tell
Lovelace has many different cards. Each card allows the user to tell
a different story about what is going on in their house. These cards
are very customizable, as no household is the same.
This gallery helps our developers and designers to see all the
different states that each card can be in.
Check [the Dashboards documentation](https://www.home-assistant.io/dashboards/) for instructions on how to get started with Dashboards.
Check [the Lovelace documentation](https://www.home-assistant.io/lovelace) for instructions on how to get started with Lovelace.

View File

@@ -40,7 +40,7 @@ const CONFIGS = [
left: 90%
padding: 0px
service: light.turn_off
data:
service_data:
entity_id: group.all_lights
- type: icon
icon: mdi:cctv
@@ -88,7 +88,7 @@ const CONFIGS = [
left: 90%
padding: 0px
service: light.turn_off
data:
service_data:
entity_id: group.all_lights
- type: icon
icon: mdi:cctv

View File

@@ -191,11 +191,9 @@ const createEntityRegistryEntries = (
hidden_by: null,
entity_category: null,
entity_id: "binary_sensor.updater",
id: "binary_sensor.updater",
name: null,
icon: null,
platform: "updater",
has_entity_name: false,
},
];

View File

@@ -69,7 +69,7 @@ const ENTITIES = [
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_RGB_light", "on", {
friendly_name: "Color Effects Light",
friendly_name: "Color Effets Light",
brightness: 255,
rgb_color: [30, 100, 255],
supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,

View File

@@ -6,8 +6,10 @@ import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate";
import { caseInsensitiveStringCompare } from "../../../src/common/string/compare";
import "../../../src/components/ha-card";
import { HassioAddonRepository } from "../../../src/data/hassio/addon";
import { StoreAddon } from "../../../src/data/supervisor/store";
import {
HassioAddonInfo,
HassioAddonRepository,
} from "../../../src/data/hassio/addon";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { HomeAssistant } from "../../../src/types";
import "../components/hassio-card-content";
@@ -21,16 +23,20 @@ class HassioAddonRepositoryEl extends LitElement {
@property({ attribute: false }) public repo!: HassioAddonRepository;
@property({ attribute: false }) public addons!: StoreAddon[];
@property({ attribute: false }) public addons!: HassioAddonInfo[];
@property() public filter!: string;
private _getAddons = memoizeOne((addons: StoreAddon[], filter?: string) => {
if (filter) {
return filterAndSort(addons, filter);
private _getAddons = memoizeOne(
(addons: HassioAddonInfo[], filter?: string) => {
if (filter) {
return filterAndSort(addons, filter);
}
return addons.sort((a, b) =>
caseInsensitiveStringCompare(a.name, b.name)
);
}
return addons.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name));
});
);
protected render(): TemplateResult {
const repo = this.repo;
@@ -62,7 +68,6 @@ class HassioAddonRepositoryEl extends LitElement {
${addons.map(
(addon) => html`
<ha-card
outlined
.addon=${addon}
class=${addon.available ? "" : "not_available"}
@click=${this._addonTapped}
@@ -81,10 +86,10 @@ class HassioAddonRepositoryEl extends LitElement {
? this.supervisor.localize(
"common.new_version_available"
)
: this.supervisor.localize("addon.state.installed")
: this.supervisor.localize("addon.installed")
: addon.available
? this.supervisor.localize("addon.state.not_installed")
: this.supervisor.localize("addon.state.not_available")}
? this.supervisor.localize("addon.not_installed")
: this.supervisor.localize("addon.not_available")}
.iconClass=${addon.installed
? addon.update_available
? "update"

View File

@@ -14,18 +14,16 @@ import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event";
import { navigate } from "../../../src/common/navigate";
import "../../../src/components/search-input";
import { extractSearchParam } from "../../../src/common/url/search-params";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-icon-button";
import "../../../src/components/search-input";
import {
HassioAddonInfo,
HassioAddonRepository,
reloadHassioAddons,
} from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { StoreAddon } from "../../../src/data/supervisor/store";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-subpage";
import { HomeAssistant, Route } from "../../../src/types";
@@ -61,24 +59,17 @@ class HassioAddonStore extends LitElement {
@state() private _filter?: string;
public async refreshData() {
try {
await reloadHassioAddons(this.hass);
} catch (err) {
showAlertDialog(this, {
text: extractApiErrorMessage(err),
});
} finally {
await this._loadData();
}
await reloadHassioAddons(this.hass);
await this._loadData();
}
protected render(): TemplateResult {
let repos: TemplateResult[] = [];
if (this.supervisor.store.repositories) {
if (this.supervisor.addon.repositories) {
repos = this.addonRepositories(
this.supervisor.store.repositories,
this.supervisor.store.addons,
this.supervisor.addon.repositories,
this.supervisor.addon.addons,
this._filter
);
}
@@ -154,7 +145,7 @@ class HassioAddonStore extends LitElement {
private addonRepositories = memoizeOne(
(
repositories: HassioAddonRepository[],
addons: StoreAddon[],
addons: HassioAddonInfo[],
filter?: string
) =>
repositories.sort(sortRepos).map((repo) => {

View File

@@ -50,7 +50,6 @@ class HassioAddonAudio extends LitElement {
protected render(): TemplateResult {
return html`
<ha-card
outlined
.header=${this.supervisor.localize("addon.configuration.audio.header")}
>
<div class="card-content">

View File

@@ -162,7 +162,7 @@ class HassioAddonConfig extends LitElement {
);
return html`
<h1>${this.addon.name}</h1>
<ha-card outlined>
<ha-card>
<div class="header">
<h2>
${this.supervisor.localize("addon.configuration.options.header")}
@@ -336,7 +336,7 @@ class HassioAddonConfig extends LitElement {
fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.failed_to_reset",
"addon.common.update_available",
"error",
extractApiErrorMessage(err)
);

View File

@@ -58,7 +58,6 @@ class HassioAddonNetwork extends LitElement {
return html`
<ha-card
outlined
.header=${this.supervisor.localize(
"addon.configuration.network.header"
)}

View File

@@ -38,7 +38,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
}
return html`
<div class="content">
<ha-card outlined>
<ha-card>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
@@ -81,7 +81,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
);
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.documentation.get_documentation",
"addon.documentation.get_logs",
"error",
extractApiErrorMessage(err)
);

View File

@@ -12,17 +12,12 @@ import { navigate } from "../../../src/common/navigate";
import { extractSearchParam } from "../../../src/common/url/search-params";
import "../../../src/components/ha-circular-progress";
import {
fetchAddonInfo,
fetchHassioAddonInfo,
fetchHassioAddonsInfo,
HassioAddonDetails,
} from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import {
addStoreRepository,
fetchSupervisorStore,
StoreAddonDetails,
} from "../../../src/data/supervisor/store";
import { setSupervisorOption } from "../../../src/data/hassio/supervisor";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
import "../../../src/layouts/hass-error-screen";
@@ -47,9 +42,7 @@ class HassioAddonDashboard extends LitElement {
@property({ attribute: false }) public route!: Route;
@property({ attribute: false }) public addon?:
| HassioAddonDetails
| StoreAddonDetails;
@property({ attribute: false }) public addon?: HassioAddonDetails;
@property({ type: Boolean }) public narrow!: boolean;
@@ -75,7 +68,7 @@ class HassioAddonDashboard extends LitElement {
></hass-error-screen>`;
}
if (!this.addon || !this.supervisor?.addon) {
if (!this.addon) {
return html`<hass-loading-screen></hass-loading-screen>`;
}
@@ -176,41 +169,44 @@ class HassioAddonDashboard extends LitElement {
if (this.route.path === "") {
const requestedAddon = extractSearchParam("addon");
const requestedAddonRepository = extractSearchParam("repository_url");
if (requestedAddonRepository) {
const storeInfo = await fetchSupervisorStore(this.hass);
if (
requestedAddonRepository &&
!this.supervisor.supervisor.addons_repositories.find(
(repo) => repo === requestedAddonRepository
)
) {
if (
!storeInfo.repositories.find(
(repo) => repo.source === requestedAddonRepository
)
!(await showConfirmationDialog(this, {
title: this.supervisor.localize("my.add_addon_repository_title"),
text: this.supervisor.localize(
"my.add_addon_repository_description",
{ addon: requestedAddon, repository: requestedAddonRepository }
),
confirmText: this.supervisor.localize("common.add"),
dismissText: this.supervisor.localize("common.cancel"),
}))
) {
if (
!(await showConfirmationDialog(this, {
title: this.supervisor.localize("my.add_addon_repository_title"),
text: this.supervisor.localize(
"my.add_addon_repository_description",
{ addon: requestedAddon, repository: requestedAddonRepository }
),
confirmText: this.supervisor.localize("common.add"),
dismissText: this.supervisor.localize("common.cancel"),
}))
) {
this._error = this.supervisor.localize(
"my.error_repository_not_found"
);
return;
}
this._error = this.supervisor.localize(
"my.error_repository_not_found"
);
return;
}
try {
await addStoreRepository(this.hass, requestedAddonRepository);
} catch (err: any) {
this._error = extractApiErrorMessage(err);
}
try {
await setSupervisorOption(this.hass, {
addons_repositories: [
...this.supervisor.supervisor.addons_repositories,
requestedAddonRepository,
],
});
} catch (err: any) {
this._error = extractApiErrorMessage(err);
}
}
if (requestedAddon) {
const store = await fetchSupervisorStore(this.hass);
const validAddon = store.addons.some(
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
const validAddon = addonsInfo.addons.some(
(addon) => addon.slug === requestedAddon
);
if (!validAddon) {
@@ -238,14 +234,12 @@ class HassioAddonDashboard extends LitElement {
if (["uninstall", "install", "update", "start", "stop"].includes(path)) {
fireEvent(this, "supervisor-collection-refresh", {
collection: "addon",
collection: "supervisor",
});
}
if (path === "uninstall") {
window.history.back();
} else if (path === "install") {
this.addon = await fetchHassioAddonInfo(this.hass, this.addon!.slug);
} else {
await this._routeDataChanged();
}
@@ -263,11 +257,8 @@ class HassioAddonDashboard extends LitElement {
return;
}
try {
if (!this.supervisor.addon) {
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
fireEvent(this, "supervisor-update", { addon: addonsInfo });
}
this.addon = await fetchAddonInfo(this.hass, this.supervisor, addon);
const addoninfo = await fetchHassioAddonInfo(this.hass, addon);
this.addon = addoninfo;
} catch (err: any) {
this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`;
this.addon = undefined;

View File

@@ -1,6 +1,5 @@
import { customElement, property } from "lit/decorators";
import { HassioAddonDetails } from "../../../src/data/hassio/addon";
import { StoreAddonDetails } from "../../../src/data/supervisor/store";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import {
HassRouterPage,
@@ -21,9 +20,7 @@ class HassioAddonRouter extends HassRouterPage {
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ attribute: false }) public addon!:
| HassioAddonDetails
| StoreAddonDetails;
@property({ attribute: false }) public addon!: HassioAddonDetails;
protected routerOptions: RouterOptions = {
defaultPage: "info",

View File

@@ -40,7 +40,6 @@ import "../../../../src/components/ha-settings-row";
import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-switch";
import {
AddonCapability,
fetchHassioAddonChangelog,
fetchHassioAddonInfo,
HassioAddonDetails,
@@ -60,10 +59,7 @@ import {
fetchHassioStats,
HassioStats,
} from "../../../../src/data/hassio/common";
import {
StoreAddon,
StoreAddonDetails,
} from "../../../../src/data/supervisor/store";
import { StoreAddon } from "../../../../src/data/supervisor/store";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import {
showAlertDialog,
@@ -104,9 +100,7 @@ class HassioAddonInfo extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public addon!:
| HassioAddonDetails
| StoreAddonDetails;
@property({ attribute: false }) public addon!: HassioAddonDetails;
@property({ attribute: false }) public supervisor!: Supervisor;
@@ -149,7 +143,7 @@ class HassioAddonInfo extends LitElement {
></update-available-card>
`
: ""}
${"protected" in this.addon && !this.addon.protected
${!this.addon.protected
? html`
<ha-alert
alert-type="error"
@@ -172,7 +166,7 @@ class HassioAddonInfo extends LitElement {
`
: ""}
<ha-card outlined>
<ha-card>
<div class="card-content">
<div class="addon-header">
${!this.narrow ? this.addon.name : ""}
@@ -524,7 +518,7 @@ class HassioAddonInfo extends LitElement {
: ""}
</div>
<div>
${this.addon.version && this.addon.state === "started"
${this.addon.state === "started"
? html`<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
${this.supervisor.localize("addon.dashboard.hostname")}
@@ -655,7 +649,7 @@ class HassioAddonInfo extends LitElement {
${this.addon.long_description
? html`
<ha-card outlined>
<ha-card>
<div class="card-content">
<ha-markdown
.content=${this.addon.long_description}
@@ -675,7 +669,7 @@ class HassioAddonInfo extends LitElement {
}
private async _loadData(): Promise<void> {
if ("state" in this.addon && this.addon.state === "started") {
if (this.addon.state === "started") {
this._metrics = await fetchHassioStats(
this.hass,
`addons/${this.addon.slug}`
@@ -702,7 +696,7 @@ class HassioAddonInfo extends LitElement {
}
private _showMoreInfo(ev): void {
const id = ev.currentTarget.id as AddonCapability;
const id = ev.currentTarget.id;
showHassioMarkdownDialog(this, {
title: this.supervisor.localize(`addon.dashboard.capability.${id}.title`),
content:
@@ -723,22 +717,18 @@ class HassioAddonInfo extends LitElement {
}
private get _computeIsRunning(): boolean {
return (this.addon as HassioAddonDetails)?.state === "started";
return this.addon?.state === "started";
}
private get _pathWebui(): string | null {
return (this.addon as HassioAddonDetails).webui!.replace(
"[HOST]",
document.location.hostname
return (
this.addon.webui &&
this.addon.webui.replace("[HOST]", document.location.hostname)
);
}
private get _computeShowWebUI(): boolean | "" | null {
return (
!this.addon.ingress &&
(this.addon as HassioAddonDetails).webui &&
this._computeIsRunning
);
return !this.addon.ingress && this.addon.webui && this._computeIsRunning;
}
private _openIngress(): void {
@@ -764,8 +754,7 @@ class HassioAddonInfo extends LitElement {
private async _startOnBootToggled(): Promise<void> {
this._error = undefined;
const data: HassioAddonSetOptionParams = {
boot:
(this.addon as HassioAddonDetails).boot === "auto" ? "manual" : "auto",
boot: this.addon.boot === "auto" ? "manual" : "auto",
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
@@ -787,7 +776,7 @@ class HassioAddonInfo extends LitElement {
private async _watchdogToggled(): Promise<void> {
this._error = undefined;
const data: HassioAddonSetOptionParams = {
watchdog: !(this.addon as HassioAddonDetails).watchdog,
watchdog: !this.addon.watchdog,
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
@@ -809,7 +798,7 @@ class HassioAddonInfo extends LitElement {
private async _autoUpdateToggled(): Promise<void> {
this._error = undefined;
const data: HassioAddonSetOptionParams = {
auto_update: !(this.addon as HassioAddonDetails).auto_update,
auto_update: !this.addon.auto_update,
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
@@ -831,7 +820,7 @@ class HassioAddonInfo extends LitElement {
private async _protectionToggled(): Promise<void> {
this._error = undefined;
const data: HassioAddonSetSecurityParams = {
protected: !(this.addon as HassioAddonDetails).protected,
protected: !this.addon.protected,
};
try {
await setHassioAddonSecurity(this.hass, this.addon.slug, data);
@@ -853,7 +842,7 @@ class HassioAddonInfo extends LitElement {
private async _panelToggled(): Promise<void> {
this._error = undefined;
const data: HassioAddonSetOptionParams = {
ingress_panel: !(this.addon as HassioAddonDetails).ingress_panel,
ingress_panel: !this.addon.ingress_panel,
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
@@ -881,7 +870,7 @@ class HassioAddonInfo extends LitElement {
showHassioMarkdownDialog(this, {
title: this.supervisor.localize("addon.dashboard.changelog"),
content: extractChangelog(this.addon as HassioAddonDetails, content),
content: extractChangelog(this.addon, content),
});
} catch (err: any) {
showAlertDialog(this, {

View File

@@ -34,7 +34,7 @@ class HassioAddonLogs extends LitElement {
protected render(): TemplateResult {
return html`
<h1>${this.addon.name}</h1>
<ha-card outlined>
<ha-card>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}

View File

@@ -98,8 +98,9 @@ export class HassioBackups extends LitElement {
if (backup.content.addons.length !== 0) {
for (const addon of backup.content.addons) {
content.push(
this.supervisor.addon.addons.find((entry) => entry.slug === addon)
?.name || addon
this.supervisor.supervisor.addons.find(
(entry) => entry.slug === addon
)?.name || addon
);
}
}
@@ -176,7 +177,7 @@ export class HassioBackups extends LitElement {
: supervisorTabs(this.hass)}
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.searchLabel=${this.supervisor.localize("backup.search")}
.searchLabel=${this.supervisor.localize("search")}
.noDataText=${this.supervisor.localize("backup.no_backups")}
.narrow=${this.narrow}
.route=${this.route}
@@ -240,7 +241,7 @@ export class HassioBackups extends LitElement {
: html`
<ha-icon-button
.label=${this.supervisor.localize(
"backup.delete_selected"
"snapshot.delete_selected"
)}
.path=${mdiDelete}
id="delete-btn"

View File

@@ -1,8 +1,8 @@
import Fuse from "fuse.js";
import { StoreAddon } from "../../../src/data/supervisor/store";
import { HassioAddonInfo } from "../../../src/data/hassio/addon";
export function filterAndSort(addons: StoreAddon[], filter: string) {
const options: Fuse.IFuseOptions<StoreAddon> = {
export function filterAndSort(addons: HassioAddonInfo[], filter: string) {
const options: Fuse.IFuseOptions<HassioAddonInfo> = {
keys: ["name", "description", "slug"],
isCaseSensitive: false,
minMatchCharLength: 2,

View File

@@ -17,12 +17,9 @@ import {
} from "../../../src/data/hassio/backup";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { PolymerChangedEvent } from "../../../src/polymer-types";
import { HomeAssistant, TranslationDict } from "../../../src/types";
import { HomeAssistant } from "../../../src/types";
import "./supervisor-formfield-label";
type BackupOrRestoreKey = keyof TranslationDict["supervisor"]["backup"] &
keyof TranslationDict["ui"]["panel"]["page-onboarding"]["restore"];
interface CheckboxItem {
slug: string;
checked: boolean;
@@ -99,7 +96,7 @@ export class SupervisorBackupContent extends LitElement {
: ["ssl", "share", "media", "addons/local"]
);
this.addons = _computeAddons(
this.backup ? this.backup.addons : this.supervisor?.addon.addons
this.backup ? this.backup.addons : this.supervisor?.supervisor.addons
);
this.backupType = this.backup?.type || "full";
this.backupName = this.backup?.name || "";
@@ -111,9 +108,9 @@ export class SupervisorBackupContent extends LitElement {
this._focusTarget?.focus();
}
private _localize = (key: BackupOrRestoreKey) =>
this.supervisor?.localize(`backup.${key}`) ||
this.localize!(`ui.panel.page-onboarding.restore.${key}`);
private _localize = (string: string) =>
this.supervisor?.localize(`backup.${string}`) ||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
protected render(): TemplateResult {
if (!this.onboarding && !this.supervisor) {
@@ -171,24 +168,23 @@ export class SupervisorBackupContent extends LitElement {
: ""}
${this.backupType === "partial"
? html`<div class="partial-picker">
${!this.backup || this.backup.homeassistant
? html`<ha-formfield
.label=${html`<supervisor-formfield-label
label="Home Assistant"
.iconPath=${mdiHomeAssistant}
.version=${this.backup
? this.backup.homeassistant
: this.hass.config.version}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
.checked=${this.homeAssistant}
@change=${this.toggleHomeAssistant}
>
</ha-checkbox>
</ha-formfield>`
: ""}
<ha-formfield
.label=${html`<supervisor-formfield-label
label="Home Assistant"
.iconPath=${mdiHomeAssistant}
.version=${this.backup
? this.backup.homeassistant
: this.hass.config.version}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
.checked=${this.homeAssistant}
@change=${this.toggleHomeAssistant}
>
</ha-checkbox>
</ha-formfield>
${foldersSection?.templates.length
? html`
<ha-formfield

View File

@@ -24,9 +24,9 @@ class HassioAddons extends LitElement {
? html` <h1>${this.supervisor.localize("dashboard.addons")}</h1> `
: ""}
<div class="card-group">
${!this.supervisor.addon.addons.length
${!this.supervisor.supervisor.addons?.length
? html`
<ha-card outlined>
<ha-card>
<div class="card-content">
<button class="link" @click=${this._openStore}>
${this.supervisor.localize("dashboard.no_addons")}
@@ -34,15 +34,11 @@ class HassioAddons extends LitElement {
</div>
</ha-card>
`
: this.supervisor.addon.addons
: this.supervisor.supervisor.addons
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name))
.map(
(addon) => html`
<ha-card
outlined
.addon=${addon}
@click=${this._addonTapped}
>
<ha-card .addon=${addon} @click=${this._addonTapped}>
<div class="card-content">
<hassio-card-content
.hass=${this.hass}

View File

@@ -85,7 +85,7 @@ export class HassioUpdate extends LitElement {
return html``;
}
return html`
<ha-card outlined>
<ha-card>
<div class="card-content">
<div class="icon">
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>

View File

@@ -201,24 +201,26 @@ class HassioBackupDialog
}
if (!this._dialogParams?.onboarding) {
try {
await this.hass!.callApi(
"POST",
this.hass!.callApi(
"POST",
`hassio/${
atLeastVersion(this.hass!.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/partial`,
backupDetails
);
this.closeDialog();
} catch (error: any) {
this._error = error.body.message;
}
`hassio/${
atLeastVersion(this.hass!.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/partial`,
backupDetails
).then(
() => {
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
} else {
fireEvent(this, "restoring");
await fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
method: "POST",
body: JSON.stringify(backupDetails),
});

View File

@@ -4,7 +4,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../src/components/ha-form/types";
import { HaFormSchema } from "../../../../src/components/ha-form/types";
import "../../../../src/components/ha-icon-button";
import "../../../../src/components/ha-settings-row";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
@@ -19,7 +19,7 @@ import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import { RegistriesDialogParams } from "./show-dialog-registries";
const SCHEMA = [
const SCHEMA: HaFormSchema[] = [
{
name: "registry",
required: true,
@@ -35,7 +35,7 @@ const SCHEMA = [
required: true,
selector: { text: { type: "password" } },
},
] as const;
];
@customElement("dialog-hassio-registries")
class HassioRegistriesDialog extends LitElement {
@@ -135,8 +135,8 @@ class HassioRegistriesDialog extends LitElement {
`;
}
private _computeLabel = (schema: SchemaUnion<typeof SCHEMA>) =>
this.supervisor.localize(`dialog.registries.${schema.name}`);
private _computeLabel = (schema: HaFormSchema) =>
this.supervisor.localize(`dialog.registries.${schema.name}`) || schema.name;
private _valueChanged(ev: CustomEvent) {
this._input = ev.detail.value;

View File

@@ -15,18 +15,15 @@ import "../../../../src/components/ha-circular-progress";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-icon-button";
import {
fetchHassioAddonsInfo,
HassioAddonInfo,
HassioAddonRepository,
} from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { setSupervisorOption } from "../../../../src/data/hassio/supervisor";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import { HassioRepositoryDialogParams } from "./show-dialog-repositories";
import {
addStoreRepository,
fetchStoreRepositories,
removeStoreRepository,
} from "../../../../src/data/supervisor/store";
@customElement("dialog-hassio-repositories")
class HassioRepositoriesDialog extends LitElement {
@@ -61,13 +58,7 @@ class HassioRepositoriesDialog extends LitElement {
private _filteredRepositories = memoizeOne((repos: HassioAddonRepository[]) =>
repos
.filter(
(repo) =>
repo.slug !== "core" && // The core add-ons repository
repo.slug !== "local" && // Locally managed add-ons
repo.slug !== "a0d7b954" && // Home Assistant Community Add-ons
repo.slug !== "5c53de3b" // The ESPHome repository
)
.filter((repo) => repo.slug !== "core" && repo.slug !== "local")
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name))
);
@@ -87,7 +78,7 @@ class HassioRepositoriesDialog extends LitElement {
const repositories = this._filteredRepositories(this._repositories);
const usedRepositories = this._filteredUsedRepositories(
repositories,
this._dialogParams.supervisor.addon.addons
this._dialogParams.supervisor.supervisor.addons
);
return html`
<ha-dialog
@@ -224,7 +215,9 @@ class HassioRepositoriesDialog extends LitElement {
private async _loadData(): Promise<void> {
try {
this._repositories = await fetchStoreRepositories(this.hass);
const addonsinfo = await fetchHassioAddonsInfo(this.hass);
this._repositories = addonsinfo.repositories;
fireEvent(this, "supervisor-collection-refresh", { collection: "addon" });
} catch (err: any) {
@@ -238,9 +231,14 @@ class HassioRepositoriesDialog extends LitElement {
return;
}
this._processing = true;
const repositories = this._filteredRepositories(this._repositories!);
const newRepositories = repositories.map((repo) => repo.source);
newRepositories.push(input.value);
try {
await addStoreRepository(this.hass, input.value);
await setSupervisorOption(this.hass, {
addons_repositories: newRepositories,
});
await this._loadData();
input.value = "";
@@ -252,8 +250,19 @@ class HassioRepositoriesDialog extends LitElement {
private async _removeRepository(ev: Event) {
const slug = (ev.currentTarget as any).slug;
const repositories = this._filteredRepositories(this._repositories!);
const repository = repositories.find((repo) => repo.slug === slug);
if (!repository) {
return;
}
const newRepositories = repositories
.map((repo) => repo.source)
.filter((repo) => repo !== repository.source);
try {
await removeStoreRepository(this.hass, slug);
await setSupervisorOption(this.hass, {
addons_repositories: newRepositories,
});
await this._loadData();
} catch (err: any) {
this._error = extractApiErrorMessage(err);

View File

@@ -22,7 +22,6 @@ import {
Supervisor,
SupervisorObject,
supervisorCollection,
SupervisorKeys,
} from "../../src/data/supervisor/supervisor";
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
import { urlSyncMixin } from "../../src/state/url-sync-mixin";
@@ -125,13 +124,9 @@ export class SupervisorBaseElement extends urlSyncMixin(
this.supervisor = {
...this.supervisor,
localize: await computeLocalize<SupervisorKeys>(
this.constructor.prototype,
language,
{
[language]: data,
}
),
localize: await computeLocalize(this.constructor.prototype, language, {
[language]: data,
}),
};
}

View File

@@ -48,7 +48,7 @@ class HassioCoreInfo extends LitElement {
];
return html`
<ha-card header="Core" outlined>
<ha-card header="Core">
<div class="card-content">
<div>
<ha-settings-row>

View File

@@ -66,7 +66,7 @@ class HassioHostInfo extends LitElement {
},
];
return html`
<ha-card header="Host" outlined>
<ha-card header="Host">
<div class="card-content">
<div>
${this.supervisor.host.features.includes("hostname")

View File

@@ -26,7 +26,7 @@ import {
import {
UNHEALTHY_REASON_URL,
UNSUPPORTED_REASON_URL,
} from "../../../src/panels/config/repairs/dialog-system-information";
} from "../../../src/panels/config/system-health/ha-config-system-health";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { bytesToString } from "../../../src/util/bytes-to-string";
@@ -57,7 +57,7 @@ class HassioSupervisorInfo extends LitElement {
},
];
return html`
<ha-card header="Supervisor" outlined>
<ha-card header="Supervisor">
<div class="card-content">
<div>
<ha-settings-row>

View File

@@ -65,7 +65,7 @@ class HassioSupervisorLog extends LitElement {
protected render(): TemplateResult | void {
return html`
<ha-card outlined>
<ha-card>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}

View File

@@ -128,7 +128,6 @@ class UpdateAvailableCard extends LitElement {
return html`
<ha-card
outlined
.header=${this.supervisor.localize("update_available.update_name", {
name: this._name,
})}

View File

@@ -1,11 +1,4 @@
module.exports = {
"*.{js,ts}": [
"prettier --write",
'eslint --ignore-pattern "**/build-scripts/**/*.js" --fix',
],
"!(/translations)*.{json,css,md,html}": "prettier --write",
"translations/*/*.json": (files) =>
'printf "%s\n" "These files should not be modified. Instead, make the necessary modifications in src/translations/en.json. Please see translations/README.md for details." ' +
files.join(" ") +
" >&2 && exit 1",
"*.{js,ts}": 'eslint --ignore-pattern "**/build-scripts/**/*.js" --fix',
"!(/translations)*.{js,ts,json,css,md,html}": "prettier --write",
};

View File

@@ -16,9 +16,6 @@
"lint:lit": "lit-analyzer \"**/src/**/*.ts\" --format markdown --outFile result.md",
"lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types",
"format": "yarn run format:eslint && yarn run format:prettier",
"postinstall": "husky install",
"prepack": "pinst --disable",
"postpack": "pinst --enable",
"test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.js \"test/**/*.ts\""
},
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
@@ -49,7 +46,6 @@
"@fullcalendar/daygrid": "5.9.0",
"@fullcalendar/interaction": "5.9.0",
"@fullcalendar/list": "5.9.0",
"@fullcalendar/timegrid": "5.9.0",
"@lit-labs/motion": "^1.0.2",
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch",
"@material/chips": "14.0.0-canary.261f2db59.0",
@@ -76,8 +72,8 @@
"@material/mwc-textfield": "0.25.3",
"@material/mwc-top-app-bar-fixed": "^0.25.3",
"@material/top-app-bar": "14.0.0-canary.261f2db59.0",
"@mdi/js": "7.0.96",
"@mdi/svg": "7.0.96",
"@mdi/js": "6.6.95",
"@mdi/svg": "6.6.95",
"@polymer/app-layout": "^3.1.0",
"@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1",
@@ -93,8 +89,8 @@
"@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.4.1",
"@thomasloven/round-slider": "0.5.4",
"@vaadin/combo-box": "^23.1.5",
"@vaadin/vaadin-themable-mixin": "^23.1.5",
"@vaadin/combo-box": "^22.0.4",
"@vaadin/vaadin-themable-mixin": "^22.0.4",
"@vibrant/color": "^3.2.1-alpha.1",
"@vibrant/core": "^3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
@@ -110,15 +106,17 @@
"deep-clone-simple": "^1.1.1",
"deep-freeze": "^0.0.1",
"fuse.js": "^6.0.0",
"fuzzysort": "^1.2.1",
"google-timezones-json": "^1.0.2",
"hls.js": "^1.2.1",
"home-assistant-js-websocket": "^8.0.0",
"hls.js": "^1.1.5",
"home-assistant-js-websocket": "^7.0.3",
"idb-keyval": "^5.1.3",
"intl-messageformat": "^9.9.1",
"js-yaml": "^4.1.0",
"leaflet": "^1.7.1",
"leaflet-draw": "^1.0.4",
"lit": "^2.1.2",
"lit-vaadin-helpers": "^0.3.0",
"marked": "^4.0.12",
"memoize-one": "^5.2.1",
"node-vibrant": "3.2.1-alpha.1",
@@ -205,9 +203,9 @@
"gulp-rename": "^2.0.0",
"gulp-zopfli-green": "^3.0.1",
"html-minifier": "^4.0.0",
"husky": "^8.0.1",
"husky": "^1.3.1",
"instant-mocha": "^1.3.1",
"lint-staged": "^13.0.3",
"lint-staged": "^11.1.2",
"lit-analyzer": "^1.2.1",
"lodash.template": "^4.5.0",
"magic-string": "^0.25.7",
@@ -216,7 +214,6 @@
"mocha": "^8.4.0",
"object-hash": "^2.0.3",
"open": "^7.0.4",
"pinst": "^3.0.0",
"prettier": "^2.4.1",
"require-dir": "^1.2.0",
"rollup": "^2.8.2",
@@ -249,6 +246,11 @@
"@lit/reactive-element": "1.2.1"
},
"main": "src/home-assistant.js",
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"prettier": {
"trailingComma": "es5",
"arrowParens": "always"

View File

@@ -1,25 +1,3 @@
[build-system]
requires = ["setuptools~=62.3", "wheel~=0.37.1"]
requires = ["setuptools~=60.5", "wheel~=0.37.1"]
build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20220902.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"
authors = [
{name = "The Home Assistant Authors", email = "hello@home-assistant.io"}
]
requires-python = ">=3.4.0"
[project.urls]
"Homepage" = "https://github.com/home-assistant/frontend"
[tool.setuptools]
platforms = ["any"]
zip-safe = false
include-package-data = true
[tool.setuptools.packages.find]
include = ["hass_frontend*"]

View File

@@ -24,15 +24,10 @@ function auto(version) {
return patch(version);
}
function nightly() {
return `${today()}.dev`;
}
const methods = {
patch,
today,
auto,
nightly,
};
async function main(args) {
@@ -55,18 +50,14 @@ async function main(args) {
return;
}
const setup = fs.readFileSync("pyproject.toml", "utf8");
const version = setup.match(/version\W+=\W"(\d{8}\.\d)"/)[1];
const setup = fs.readFileSync("setup.cfg", "utf8");
const version = setup.match(/\d{8}\.\d+/)[0];
const newVersion = method(version);
console.log("Current version:", version);
console.log("New version:", newVersion);
fs.writeFileSync(
"pyproject.toml",
setup.replace(version, newVersion),
"utf-8"
);
fs.writeFileSync("setup.cfg", setup.replace(version, newVersion), "utf-8");
if (!commit) {
return;

View File

@@ -1,2 +1,26 @@
# Setuptools v62.3 doesn't support editable installs with just 'pyproject.toml' (PEP 660).
# Keep this file until it does!
[metadata]
name = home-assistant-frontend
version = 20220502.0
author = The Home Assistant Authors
author_email = hello@home-assistant.io
license = Apache-2.0
platforms = any
description = The Home Assistant frontend
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/home-assistant/frontend
[options]
packages = find:
zip_safe = False
include_package_data = True
python_requires = >= 3.4.0
[options.packages.find]
include =
hass_frontend*
[mypy]
python_version = 3.4
show_error_codes = True
strict = True

View File

@@ -314,8 +314,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
}
private _computeStepDescription(step: DataEntryFlowStepForm) {
const resourceKey =
`ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.description` as const;
const resourceKey = `ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.description`;
const args: string[] = [];
const placeholders = step.description_placeholders || {};
Object.keys(placeholders).forEach((key) => {

View File

@@ -47,7 +47,7 @@ import {
mdiRobotVacuum,
mdiScriptText,
mdiSineWave,
mdiMicrophoneMessage,
mdiTextToSpeech,
mdiThermometer,
mdiThermostat,
mdiTimerOutline,
@@ -74,9 +74,8 @@ export const FIXED_DOMAIN_ICONS = {
camera: mdiVideo,
climate: mdiThermostat,
configurator: mdiCog,
conversation: mdiMicrophoneMessage,
conversation: mdiTextToSpeech,
counter: mdiCounter,
demo: mdiHomeAssistant,
fan: mdiFan,
google_assistant: mdiGoogleAssistant,
group: mdiGoogleCirclesCommunities,
@@ -98,7 +97,6 @@ export const FIXED_DOMAIN_ICONS = {
proximity: mdiAppleSafari,
remote: mdiRemote,
scene: mdiPalette,
schedule: mdiCalendarClock,
script: mdiScriptText,
select: mdiFormatListBulleted,
sensor: mdiEye,
@@ -126,7 +124,6 @@ export const FIXED_DEVICE_CLASS_ICONS = {
gas: mdiGasCylinder,
humidity: mdiWaterPercent,
illuminance: mdiBrightness5,
moisture: mdiWaterPercent,
monetary: mdiCash,
nitrogen_dioxide: mdiMolecule,
nitrogen_monoxide: mdiMolecule,
@@ -168,6 +165,46 @@ export const DOMAINS_WITH_CARD = [
"water_heater",
];
/** Domains with separate more info dialog. */
export const DOMAINS_WITH_MORE_INFO = [
"alarm_control_panel",
"automation",
"camera",
"climate",
"configurator",
"counter",
"cover",
"fan",
"group",
"humidifier",
"input_datetime",
"light",
"lock",
"media_player",
"person",
"remote",
"script",
"scene",
"sun",
"timer",
"update",
"vacuum",
"water_heater",
"weather",
];
/** Domains that do not show the default more info dialog content (e.g. the attribute section)
* and do not have a separate more info (so not in DOMAINS_WITH_MORE_INFO). */
export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [
"input_number",
"input_select",
"input_text",
"number",
"scene",
"update",
"select",
];
/** Domains that render an input element instead of a text value when displayed in a row.
* Those rows should then not show a cursor pointer when hovered (which would normally
* be the default) unless the element itself enforces it (e.g. a button). Also those elements
@@ -199,6 +236,9 @@ export const DOMAINS_INPUT_ROW = [
"vacuum",
];
/** Domains that should have the history hidden in the more info dialog. */
export const DOMAINS_MORE_INFO_NO_HISTORY = ["camera", "configurator"];
/** States that we consider "off". */
export const STATES_OFF = ["closed", "locked", "off"];

View File

@@ -1,7 +1,7 @@
import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation";
import { polyfillsLoaded } from "../translations/localize";
import { useAmPm } from "./use_am_pm";
import { polyfillsLoaded } from "../translations/localize";
if (__BUILD__ === "latest" && polyfillsLoaded) {
await polyfillsLoaded;
@@ -28,28 +28,6 @@ const formatDateTimeMem = memoizeOne(
)
);
// Aug 9, 8:23 AM
export const formatShortDateTime = (
dateObj: Date,
locale: FrontendLocaleData
) => formatShortDateTimeMem(locale).format(dateObj);
const formatShortDateTimeMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(
locale.language === "en" && !useAmPm(locale)
? "en-u-hc-h23"
: locale.language,
{
month: "short",
day: "numeric",
hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit",
hour12: useAmPm(locale),
}
)
);
// August 9, 2021, 8:23:15 AM
export const formatDateTimeWithSeconds = (
dateObj: Date,

View File

@@ -1,28 +0,0 @@
import { HaDurationData } from "../../components/ha-duration-input";
const leftPad = (num: number) => (num < 10 ? `0${num}` : num);
export const formatDuration = (duration: HaDurationData) => {
const d = duration.days || 0;
const h = duration.hours || 0;
const m = duration.minutes || 0;
const s = duration.seconds || 0;
const ms = duration.milliseconds || 0;
if (d > 0) {
return `${d} days ${h}:${leftPad(m)}:${leftPad(s)}`;
}
if (h > 0) {
return `${h}:${leftPad(m)}:${leftPad(s)}`;
}
if (m > 0) {
return `${m}:${leftPad(s)}`;
}
if (s > 0) {
return `${s} seconds`;
}
if (ms > 0) {
return `${ms} milliseconds`;
}
return null;
};

View File

@@ -1,7 +1,7 @@
import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation";
import { polyfillsLoaded } from "../translations/localize";
import { useAmPm } from "./use_am_pm";
import { polyfillsLoaded } from "../translations/localize";
if (__BUILD__ === "latest" && polyfillsLoaded) {
await polyfillsLoaded;
@@ -64,17 +64,3 @@ const formatTimeWeekdayMem = memoizeOne(
}
)
);
// 21:15
export const formatTime24h = (dateObj: Date) =>
formatTime24hMem().format(dateObj);
const formatTime24hMem = memoizeOne(
() =>
// en-GB to fix Chrome 24:59 to 0:59 https://stackoverflow.com/a/60898146
new Intl.DateTimeFormat("en-GB", {
hour: "numeric",
minute: "2-digit",
hour12: false,
})
);

View File

@@ -76,11 +76,7 @@ class Storage {
public setValue(storageKey: string, value: any): any {
this._storage[storageKey] = value;
try {
if (value === undefined) {
window.localStorage.removeItem(storageKey);
} else {
window.localStorage.setItem(storageKey, JSON.stringify(value));
}
window.localStorage.setItem(storageKey, JSON.stringify(value));
} catch (err: any) {
// Safari in private mode doesn't allow localstorage
}

View File

@@ -1,41 +0,0 @@
const DEFAULT_OWN = true;
// Finds the closest ancestor of an element that has a specific optionally owned property,
// traversing slot and shadow root boundaries until the body element is reached
export const closestWithProperty = (
element: Element | null,
property: string | symbol,
own = DEFAULT_OWN
) => {
if (!element || element === document.body) return null;
element = element.assignedSlot ?? element;
if (element.parentElement) {
element = element.parentElement;
} else {
const root = element.getRootNode();
element = root instanceof ShadowRoot ? root.host : null;
}
if (
own
? Object.prototype.hasOwnProperty.call(element, property)
: element && property in element
)
return element;
return closestWithProperty(element, property, own);
};
// Finds the set of all such ancestors and includes starting element as first in the set
export const ancestorsWithProperty = (
element: Element | null,
property: string | symbol,
own = DEFAULT_OWN
) => {
const ancestors: Set<Element> = new Set();
while (element) {
ancestors.add(element);
element = closestWithProperty(element, property, own);
}
return ancestors;
};

View File

@@ -5,7 +5,8 @@ export type LeafletModuleType = typeof import("leaflet");
export type LeafletDrawModuleType = typeof import("leaflet-draw");
export const setupLeafletMap = async (
mapElement: HTMLElement
mapElement: HTMLElement,
darkMode?: boolean
): Promise<[Map, LeafletModuleType, TileLayer]> => {
if (!mapElement.parentNode) {
throw new Error("Cannot setup Leaflet map on disconnected element");
@@ -22,7 +23,7 @@ export const setupLeafletMap = async (
mapElement.parentNode.appendChild(style);
map.setView([52.3731339, 4.8903147], 13);
const tileLayer = createTileLayer(Leaflet).addTo(map);
const tileLayer = createTileLayer(Leaflet, Boolean(darkMode)).addTo(map);
return [map, Leaflet, tileLayer];
};
@@ -30,19 +31,23 @@ export const setupLeafletMap = async (
export const replaceTileLayer = (
leaflet: LeafletModuleType,
map: Map,
tileLayer: TileLayer
tileLayer: TileLayer,
darkMode: boolean
): TileLayer => {
map.removeLayer(tileLayer);
tileLayer = createTileLayer(leaflet);
tileLayer = createTileLayer(leaflet, darkMode);
tileLayer.addTo(map);
return tileLayer;
};
const createTileLayer = (leaflet: LeafletModuleType): TileLayer =>
const createTileLayer = (
leaflet: LeafletModuleType,
darkMode: boolean
): TileLayer =>
leaflet.tileLayer(
`https://basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}${
leaflet.Browser.retina ? "@2x.png" : ".png"
}`,
`https://{s}.basemaps.cartocdn.com/${
darkMode ? "dark_all" : "light_all"
}/{z}/{x}/{y}${leaflet.Browser.retina ? "@2x.png" : ".png"}`,
{
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>, &copy; <a href="https://carto.com/attributions">CARTO</a>',

View File

@@ -1,11 +1,6 @@
import { HassEntity } from "home-assistant-js-websocket";
import { UNAVAILABLE_STATES } from "../../data/entity";
export const computeActiveState = (stateObj: HassEntity): string => {
if (UNAVAILABLE_STATES.includes(stateObj.state)) {
return stateObj.state;
}
const domain = stateObj.entity_id.split(".")[0];
let state = stateObj.state;

View File

@@ -2,78 +2,67 @@ import { HassEntity } from "home-assistant-js-websocket";
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { FrontendLocaleData } from "../../data/translation";
import {
updateIsInstallingFromAttributes,
updateIsInstalling,
UpdateEntity,
UPDATE_SUPPORT_PROGRESS,
} from "../../data/update";
import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration";
import { formatDate } from "../datetime/format_date";
import { formatDateTime } from "../datetime/format_date_time";
import { formatTime } from "../datetime/format_time";
import { formatNumber, isNumericFromAttributes } from "../number/format_number";
import { blankBeforePercent } from "../translations/blank_before_percent";
import { formatNumber, isNumericState } from "../number/format_number";
import { LocalizeFunc } from "../translations/localize";
import { computeDomain } from "./compute_domain";
import { supportsFeatureFromAttributes } from "./supports-feature";
import { computeStateDomain } from "./compute_state_domain";
import { supportsFeature } from "./supports-feature";
import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration";
export const computeStateDisplay = (
localize: LocalizeFunc,
stateObj: HassEntity,
locale: FrontendLocaleData,
state?: string
): string =>
computeStateDisplayFromEntityAttributes(
localize,
locale,
stateObj.entity_id,
stateObj.attributes,
state !== undefined ? state : stateObj.state
);
export const computeStateDisplayFromEntityAttributes = (
localize: LocalizeFunc,
locale: FrontendLocaleData,
entityId: string,
attributes: any,
state: string
): string => {
if (state === UNKNOWN || state === UNAVAILABLE) {
return localize(`state.default.${state}`);
const compareState = state !== undefined ? state : stateObj.state;
if (compareState === UNKNOWN || compareState === UNAVAILABLE) {
return localize(`state.default.${compareState}`);
}
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
if (isNumericFromAttributes(attributes)) {
if (isNumericState(stateObj)) {
// state is duration
if (
attributes.device_class === "duration" &&
attributes.unit_of_measurement &&
UNIT_TO_SECOND_CONVERT[attributes.unit_of_measurement]
stateObj.attributes.device_class === "duration" &&
stateObj.attributes.unit_of_measurement &&
UNIT_TO_SECOND_CONVERT[stateObj.attributes.unit_of_measurement]
) {
try {
return formatDuration(state, attributes.unit_of_measurement);
return formatDuration(
compareState,
stateObj.attributes.unit_of_measurement
);
} catch (_err) {
// fallback to default
}
}
if (attributes.device_class === "monetary") {
if (stateObj.attributes.device_class === "monetary") {
try {
return formatNumber(state, locale, {
return formatNumber(compareState, locale, {
style: "currency",
currency: attributes.unit_of_measurement,
currency: stateObj.attributes.unit_of_measurement,
minimumFractionDigits: 2,
});
} catch (_err) {
// fallback to default
}
}
const unit = !attributes.unit_of_measurement
? ""
: attributes.unit_of_measurement === "%"
? blankBeforePercent(locale) + "%"
: ` ${attributes.unit_of_measurement}`;
return `${formatNumber(state, locale)}${unit}`;
return `${formatNumber(compareState, locale)}${
stateObj.attributes.unit_of_measurement
? " " + stateObj.attributes.unit_of_measurement
: ""
}`;
}
const domain = computeDomain(entityId);
const domain = computeStateDomain(stateObj);
if (domain === "input_datetime") {
if (state !== undefined) {
@@ -108,32 +97,36 @@ export const computeStateDisplayFromEntityAttributes = (
} else {
// If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format.
let date: Date;
if (attributes.has_date && attributes.has_time) {
if (stateObj.attributes.has_date && stateObj.attributes.has_time) {
date = new Date(
attributes.year,
attributes.month - 1,
attributes.day,
attributes.hour,
attributes.minute
stateObj.attributes.year,
stateObj.attributes.month - 1,
stateObj.attributes.day,
stateObj.attributes.hour,
stateObj.attributes.minute
);
return formatDateTime(date, locale);
}
if (attributes.has_date) {
date = new Date(attributes.year, attributes.month - 1, attributes.day);
if (stateObj.attributes.has_date) {
date = new Date(
stateObj.attributes.year,
stateObj.attributes.month - 1,
stateObj.attributes.day
);
return formatDate(date, locale);
}
if (attributes.has_time) {
if (stateObj.attributes.has_time) {
date = new Date();
date.setHours(attributes.hour, attributes.minute);
date.setHours(stateObj.attributes.hour, stateObj.attributes.minute);
return formatTime(date, locale);
}
return state;
return stateObj.state;
}
}
if (domain === "humidifier") {
if (state === "on" && attributes.humidity) {
return `${attributes.humidity} %`;
if (compareState === "on" && stateObj.attributes.humidity) {
return `${stateObj.attributes.humidity} %`;
}
}
@@ -143,7 +136,7 @@ export const computeStateDisplayFromEntityAttributes = (
domain === "number" ||
domain === "input_number"
) {
return formatNumber(state, locale);
return formatNumber(compareState, locale);
}
// state of button is a timestamp
@@ -151,12 +144,12 @@ export const computeStateDisplayFromEntityAttributes = (
domain === "button" ||
domain === "input_button" ||
domain === "scene" ||
(domain === "sensor" && attributes.device_class === "timestamp")
(domain === "sensor" && stateObj.attributes.device_class === "timestamp")
) {
try {
return formatDateTime(new Date(state), locale);
return formatDateTime(new Date(compareState), locale);
} catch (_err) {
return state;
return compareState;
}
}
@@ -167,28 +160,30 @@ export const computeStateDisplayFromEntityAttributes = (
// When the latest version is skipped, show the latest version
// When update is not available, show "Up-to-date"
// When update is not available and there is no latest_version show "Unavailable"
return state === "on"
? updateIsInstallingFromAttributes(attributes)
? supportsFeatureFromAttributes(attributes, UPDATE_SUPPORT_PROGRESS)
return compareState === "on"
? updateIsInstalling(stateObj as UpdateEntity)
? supportsFeature(stateObj, UPDATE_SUPPORT_PROGRESS)
? localize("ui.card.update.installing_with_progress", {
progress: attributes.in_progress,
progress: stateObj.attributes.in_progress,
})
: localize("ui.card.update.installing")
: attributes.latest_version
: attributes.skipped_version === attributes.latest_version
? attributes.latest_version ?? localize("state.default.unavailable")
: stateObj.attributes.latest_version
: stateObj.attributes.skipped_version ===
stateObj.attributes.latest_version
? stateObj.attributes.latest_version ??
localize("state.default.unavailable")
: localize("ui.card.update.up_to_date");
}
return (
// Return device class translation
(attributes.device_class &&
(stateObj.attributes.device_class &&
localize(
`component.${domain}.state.${attributes.device_class}.${state}`
`component.${domain}.state.${stateObj.attributes.device_class}.${compareState}`
)) ||
// Return default translation
localize(`component.${domain}.state._.${state}`) ||
localize(`component.${domain}.state._.${compareState}`) ||
// We don't know! Return the raw state.
state
compareState
);
};

View File

@@ -1,13 +1,7 @@
import { HassEntity } from "home-assistant-js-websocket";
import { computeObjectId } from "./compute_object_id";
export const computeStateNameFromEntityAttributes = (
entityId: string,
attributes: { [key: string]: any }
): string =>
attributes.friendly_name === undefined
? computeObjectId(entityId).replace(/_/g, " ")
: attributes.friendly_name || "";
export const computeStateName = (stateObj: HassEntity): string =>
computeStateNameFromEntityAttributes(stateObj.entity_id, stateObj.attributes);
stateObj.attributes.friendly_name === undefined
? computeObjectId(stateObj.entity_id).replace(/_/g, " ")
: stateObj.attributes.friendly_name || "";

View File

@@ -8,8 +8,6 @@ import {
mdiCalendar,
mdiCast,
mdiCastConnected,
mdiCastOff,
mdiChartSankey,
mdiCheckCircleOutline,
mdiClock,
mdiCloseCircleOutline,
@@ -26,22 +24,12 @@ import {
mdiPowerPlug,
mdiPowerPlugOff,
mdiRestart,
mdiSpeaker,
mdiSpeakerOff,
mdiSpeakerPause,
mdiSpeakerPlay,
mdiSwapHorizontal,
mdiTelevision,
mdiTelevisionOff,
mdiTelevisionPause,
mdiTelevisionPlay,
mdiToggleSwitchVariant,
mdiToggleSwitchVariantOff,
mdiWeatherNight,
} from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
import { UpdateEntity, updateIsInstalling } from "../../data/update";
import { weatherIcon } from "../../data/weather";
import { updateIsInstalling, UpdateEntity } from "../../data/update";
/**
* Return the icon to be used for a domain.
*
@@ -58,20 +46,6 @@ export const domainIcon = (
stateObj?: HassEntity,
state?: string
): string => {
const icon = domainIconWithoutDefault(domain, stateObj, state);
if (icon) {
return icon;
}
// eslint-disable-next-line
console.warn(`Unable to find icon for domain ${domain}`);
return DEFAULT_DOMAIN_ICON;
};
export const domainIconWithoutDefault = (
domain: string,
stateObj?: HassEntity,
state?: string
): string | undefined => {
const compareState = state !== undefined ? state : stateObj?.state;
switch (domain) {
@@ -113,15 +87,6 @@ export const domainIconWithoutDefault = (
? mdiCheckCircleOutline
: mdiCloseCircleOutline;
case "input_datetime":
if (!stateObj?.attributes.has_date) {
return mdiClock;
}
if (!stateObj.attributes.has_time) {
return mdiCalendar;
}
break;
case "lock":
switch (compareState) {
case "unlocked":
@@ -136,40 +101,7 @@ export const domainIconWithoutDefault = (
}
case "media_player":
switch (stateObj?.attributes.device_class) {
case "speaker":
switch (compareState) {
case "playing":
return mdiSpeakerPlay;
case "paused":
return mdiSpeakerPause;
case "off":
return mdiSpeakerOff;
default:
return mdiSpeaker;
}
case "tv":
switch (compareState) {
case "playing":
return mdiTelevisionPlay;
case "paused":
return mdiTelevisionPause;
case "off":
return mdiTelevisionOff;
default:
return mdiTelevision;
}
default:
switch (compareState) {
case "playing":
case "paused":
return mdiCastConnected;
case "off":
return mdiCastOff;
default:
return mdiCast;
}
}
return compareState === "playing" ? mdiCastConnected : mdiCast;
case "switch":
switch (stateObj?.attributes.device_class) {
@@ -192,31 +124,33 @@ export const domainIconWithoutDefault = (
break;
}
case "input_datetime":
if (!stateObj?.attributes.has_date) {
return mdiClock;
}
if (!stateObj.attributes.has_time) {
return mdiCalendar;
}
break;
case "sun":
return stateObj?.state === "above_horizon"
? FIXED_DOMAIN_ICONS[domain]
: mdiWeatherNight;
case "switch_as_x":
return mdiSwapHorizontal;
case "threshold":
return mdiChartSankey;
case "update":
return compareState === "on"
? updateIsInstalling(stateObj as UpdateEntity)
? mdiPackageDown
: mdiPackageUp
: mdiPackage;
case "weather":
return weatherIcon(stateObj?.state);
}
if (domain in FIXED_DOMAIN_ICONS) {
return FIXED_DOMAIN_ICONS[domain];
}
return undefined;
// eslint-disable-next-line
console.warn(`Unable to find icon for domain ${domain}`);
return DEFAULT_DOMAIN_ICON;
};

View File

@@ -1,277 +0,0 @@
import { HassEntity } from "home-assistant-js-websocket";
import { computeStateDomain } from "./compute_state_domain";
import { UNAVAILABLE_STATES } from "../../data/entity";
const FIXED_DOMAIN_STATES = {
alarm_control_panel: [
"armed_away",
"armed_custom_bypass",
"armed_home",
"armed_night",
"armed_vacation",
"arming",
"disarmed",
"disarming",
"pending",
"triggered",
],
automation: ["on", "off"],
binary_sensor: ["on", "off"],
button: [],
calendar: ["on", "off"],
camera: ["idle", "recording", "streaming"],
cover: ["closed", "closing", "open", "opening"],
device_tracker: ["home", "not_home"],
fan: ["on", "off"],
humidifier: ["on", "off"],
input_boolean: ["on", "off"],
input_button: [],
light: ["on", "off"],
lock: ["jammed", "locked", "locking", "unlocked", "unlocking"],
media_player: ["idle", "off", "paused", "playing", "standby"],
person: ["home", "not_home"],
remote: ["on", "off"],
scene: [],
schedule: ["on", "off"],
script: ["on", "off"],
siren: ["on", "off"],
sun: ["above_horizon", "below_horizon"],
switch: ["on", "off"],
update: ["on", "off"],
vacuum: ["cleaning", "docked", "error", "idle", "paused", "returning"],
weather: [
"clear-night",
"cloudy",
"exceptional",
"fog",
"hail",
"lightning-rainy",
"lightning",
"partlycloudy",
"pouring",
"rainy",
"snowy-rainy",
"snowy",
"sunny",
"windy-variant",
"windy",
],
};
const FIXED_DOMAIN_ATTRIBUTE_STATES = {
alarm_control_panel: {
code_format: ["number", "text"],
},
binary_sensor: {
device_class: [
"battery",
"battery_charging",
"co",
"cold",
"connectivity",
"door",
"garage_door",
"gas",
"heat",
"light",
"lock",
"moisture",
"motion",
"moving",
"occupancy",
"opening",
"plug",
"power",
"presence",
"problem",
"running",
"safety",
"smoke",
"sound",
"tamper",
"update",
"vibration",
"window",
],
},
button: {
device_class: ["restart", "update"],
},
camera: {
frontend_stream_type: ["hls", "web_rtc"],
},
climate: {
hvac_action: ["off", "idle", "heating", "cooling", "drying", "fan"],
},
cover: {
device_class: [
"awning",
"blind",
"curtain",
"damper",
"door",
"garage",
"gate",
"shade",
"shutter",
"window",
],
},
humidifier: {
device_class: ["humidifier", "dehumidifier"],
},
media_player: {
device_class: ["tv", "speaker", "receiver"],
media_content_type: [
"app",
"channel",
"episode",
"game",
"image",
"movie",
"music",
"playlist",
"tvshow",
"url",
"video",
],
},
number: {
device_class: ["temperature"],
},
sensor: {
device_class: [
"apparent_power",
"aqi",
"battery",
"carbon_dioxide",
"carbon_monoxide",
"current",
"date",
"duration",
"energy",
"frequency",
"gas",
"humidity",
"illuminance",
"monetary",
"nitrogen_dioxide",
"nitrogen_monoxide",
"nitrous_oxide",
"ozone",
"pm1",
"pm10",
"pm25",
"power_factor",
"power",
"pressure",
"reactive_power",
"signal_strength",
"sulphur_dioxide",
"temperature",
"timestamp",
"volatile_organic_compounds",
"voltage",
],
state_class: ["measurement", "total", "total_increasing"],
},
switch: {
device_class: ["outlet", "switch"],
},
update: {
device_class: ["firmware"],
},
water_heater: {
away_mode: ["on", "off"],
},
};
export const getStates = (
state: HassEntity,
attribute: string | undefined = undefined
): string[] => {
const domain = computeStateDomain(state);
const result: string[] = [];
if (!attribute && domain in FIXED_DOMAIN_STATES) {
result.push(...FIXED_DOMAIN_STATES[domain]);
} else if (
attribute &&
domain in FIXED_DOMAIN_ATTRIBUTE_STATES &&
attribute in FIXED_DOMAIN_ATTRIBUTE_STATES[domain]
) {
result.push(...FIXED_DOMAIN_ATTRIBUTE_STATES[domain][attribute]);
}
// Dynamic values based on the entities
switch (domain) {
case "climate":
if (!attribute) {
result.push(...state.attributes.hvac_modes);
} else if (attribute === "fan_mode") {
result.push(...state.attributes.fan_modes);
} else if (attribute === "preset_mode") {
result.push(...state.attributes.preset_modes);
} else if (attribute === "swing_mode") {
result.push(...state.attributes.swing_modes);
}
break;
case "device_tracker":
case "person":
if (!attribute) {
result.push("home", "not_home");
}
break;
case "fan":
if (attribute === "preset_mode") {
result.push(...state.attributes.preset_modes);
}
break;
case "humidifier":
if (attribute === "mode") {
result.push(...state.attributes.available_modes);
}
break;
case "input_select":
case "select":
if (!attribute) {
result.push(...state.attributes.options);
}
break;
case "light":
if (attribute === "effect") {
result.push(...state.attributes.effect_list);
} else if (attribute === "color_mode") {
result.push(...state.attributes.color_modes);
}
break;
case "media_player":
if (attribute === "sound_mode") {
result.push(...state.attributes.sound_mode_list);
} else if (attribute === "source") {
result.push(...state.attributes.source_list);
}
break;
case "remote":
if (attribute === "current_activity") {
result.push(...state.attributes.activity_list);
}
break;
case "vacuum":
if (attribute === "fan_speed") {
result.push(...state.attributes.fan_speed_list);
}
break;
case "water_heater":
if (!attribute || attribute === "operation_mode") {
result.push(...state.attributes.operation_list);
}
break;
}
if (!attribute) {
// All entities can have unavailable states
result.push(...UNAVAILABLE_STATES);
}
return [...new Set(result)];
};

View File

@@ -3,13 +3,6 @@ import { HassEntity } from "home-assistant-js-websocket";
export const supportsFeature = (
stateObj: HassEntity,
feature: number
): boolean => supportsFeatureFromAttributes(stateObj.attributes, feature);
export const supportsFeatureFromAttributes = (
attributes: {
[key: string]: any;
},
feature: number
): boolean =>
// eslint-disable-next-line no-bitwise
(attributes.supported_features! & feature) !== 0;
(stateObj.attributes.supported_features! & feature) !== 0;

Some files were not shown because too many files have changed in this diff Show More