Compare commits

..

1 Commits

Author SHA1 Message Date
Zack Barett
effb9e29db Revert "20220629.0 (#13030)"
This reverts commit 56eacf5733.
2022-06-29 11:22:32 -05:00
459 changed files with 6323 additions and 14618 deletions

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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ jobs:
lock: lock:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: dessant/lock-threads@v3.0.0 - uses: dessant/lock-threads@v2.0.1
with: with:
github-token: ${{ github.token }} github-token: ${{ github.token }}
issue-lock-inactive-days: "30" 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 - published
env: env:
PYTHON_VERSION: "3.10" PYTHON_VERSION: 3.8
NODE_VERSION: 16 NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=6144 NODE_OPTIONS: --max_old_space_size=6144
# Set default workflow permissions # Set default workflow permissions
@@ -21,21 +21,21 @@ jobs:
name: Release name: Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write # Required to upload release assets contents: write # Required to upload release assets
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v3 uses: actions/checkout@v2
- name: Verify version - name: Verify version
uses: home-assistant/actions/helpers/verify-version@master uses: home-assistant/actions/helpers/verify-version@master
- name: Set up Python ${{ env.PYTHON_VERSION }} - name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- name: Set up Node ${{ env.NODE_VERSION }} - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3 uses: actions/setup-node@v2
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
@@ -74,11 +74,33 @@ jobs:
version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' ) version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' )
echo "home-assistant-frontend==$version" > ./requirements.txt echo "home-assistant-frontend==$version" > ./requirements.txt
- name: Build wheels - name: Upload requirements.txt
uses: home-assistant/wheels@2022.06.7 uses: actions/upload-artifact@v2
with: with:
abi: cp310 name: requirements
tag: musllinux_1_2 path: ./requirements.txt
arch: amd64
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-key: ${{ secrets.WHEELS_KEY }}
wheels-user: wheels
requirements: "requirements.txt" requirements: "requirements.txt"

View File

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

View File

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

View File

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

View File

@@ -156,12 +156,3 @@ gulp.task("gen-icons-json", (done) => {
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("./rollup.js");
require("./gather-static.js"); require("./gather-static.js");
require("./translations.js"); require("./translations.js");
require("./gen-icons-json.js");
gulp.task( gulp.task(
"develop-hassio", "develop-hassio",
@@ -18,7 +17,6 @@ gulp.task(
process.env.NODE_ENV = "development"; process.env.NODE_ENV = "development";
}, },
"clean-hassio", "clean-hassio",
"gen-dummy-icons-json",
"gen-index-hassio-dev", "gen-index-hassio-dev",
"build-supervisor-translations", "build-supervisor-translations",
"copy-translations-supervisor", "copy-translations-supervisor",
@@ -35,7 +33,6 @@ gulp.task(
process.env.NODE_ENV = "production"; process.env.NODE_ENV = "production";
}, },
"clean-hassio", "clean-hassio",
"gen-dummy-icons-json",
"build-supervisor-translations", "build-supervisor-translations",
"copy-translations-supervisor", "copy-translations-supervisor",
"build-locale-data", "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", chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
}, },
plugins: [ plugins: [
!isStatsBuild && new WebpackBar({ fancy: !isProdBuild }), new WebpackBar({ fancy: !isProdBuild }),
new WebpackManifestPlugin({ new WebpackManifestPlugin({
// Only include the JS of entrypoints // Only include the JS of entrypoints
filter: (file) => file.isInitial && !file.name.endsWith(".map"), filter: (file) => file.isInitial && !file.name.endsWith(".map"),

View File

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

View File

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

View File

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

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

View File

@@ -466,7 +466,6 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
return results; return results;
} }
); );
mockHass.mockWS("recorder/get_statistics_metadata", () => []);
mockHass.mockWS("history/list_statistic_ids", () => []); mockHass.mockWS("history/list_statistic_ids", () => []);
mockHass.mockWS( mockHass.mockWS(
"history/statistics_during_period", "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", category: "lovelace",
// Label for in the sidebar // Label for in the sidebar
header: "Dashboards", header: "Lovelace",
// Specify order of pages. Any pages in the category folder but not listed here will // Specify order of pages. Any pages in the category folder but not listed here will
// automatically be added after the pages listed here. // automatically be added after the pages listed here.
pages: ["introduction"], pages: ["introduction"],
@@ -34,7 +34,7 @@ module.exports = [
}, },
{ {
category: "misc", category: "misc",
header: "Miscellaneous", header: "Miscelaneous",
}, },
{ {
category: "brand", category: "brand",

View File

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

View File

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

View File

@@ -1,81 +1,31 @@
import { dump } from "js-yaml"; import { dump } from "js-yaml";
import { css, html, LitElement, TemplateResult } from "lit"; import { html, css, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement } from "lit/decorators";
import "../../../../src/components/ha-card"; 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 { 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 = [ const conditions = [
{ condition: "and" }, { condition: "and" },
{ condition: "not" }, { condition: "not" },
{ condition: "or" }, { condition: "or" },
{ condition: "state", entity_id: "light.kitchen", state: "on" }, { condition: "state" },
{ { condition: "numeric_state" },
condition: "numeric_state",
entity_id: "light.kitchen",
attribute: "brightness",
below: 80,
above: 20,
},
{ condition: "sun", after: "sunset" }, { condition: "sun", after: "sunset" },
{ condition: "sun", after: "sunrise", offset: "-01:00" }, { condition: "sun", after: "sunrise" },
{ condition: "zone", entity_id: "device_tracker.person", zone: "zone.home" }, { condition: "zone" },
{ condition: "time" }, { condition: "time" },
{ condition: "template" }, { condition: "template" },
]; ];
const initialCondition: Condition = {
condition: "state",
entity_id: "light.kitchen",
state: "on",
};
@customElement("demo-automation-describe-condition") @customElement("demo-automation-describe-condition")
export class DemoAutomationDescribeCondition extends LitElement { export class DemoAutomationDescribeCondition extends LitElement {
@property({ attribute: false }) hass!: HomeAssistant;
@state() _condition = initialCondition;
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.hass) {
return html``;
}
return html` return html`
<ha-card header="Conditions"> <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( ${conditions.map(
(conf) => html` (conf) => html`
<div class="condition"> <div class="condition">
<span>${describeCondition(conf as any, this.hass)}</span> <span>${describeCondition(conf as any)}</span>
<pre>${dump(conf)}</pre> <pre>${dump(conf)}</pre>
</div> </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() { static get styles() {
return css` return css`
ha-card { ha-card {
@@ -111,9 +49,6 @@ export class DemoAutomationDescribeCondition extends LitElement {
span { span {
margin-right: 16px; margin-right: 16px;
} }
ha-yaml-editor {
width: 50%;
}
`; `;
} }
} }

View File

@@ -1,92 +1,34 @@
import { dump } from "js-yaml"; import { dump } from "js-yaml";
import { css, html, LitElement, TemplateResult } from "lit"; import { html, css, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement } from "lit/decorators";
import "../../../../src/components/ha-card"; 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 { 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 = [ const triggers = [
{ platform: "state", entity_id: "light.kitchen", from: "off", to: "on" }, { platform: "state" },
{ platform: "mqtt" }, { platform: "mqtt" },
{ { platform: "geo_location" },
platform: "geo_location", { platform: "homeassistant" },
source: "test_source", { platform: "numeric_state" },
zone: "zone.home", { platform: "sun" },
event: "enter",
},
{ platform: "homeassistant", event: "start" },
{
platform: "numeric_state",
entity_id: "light.kitchen",
attribute: "brightness",
below: 80,
above: 20,
},
{ platform: "sun", event: "sunset" },
{ platform: "time_pattern" }, { platform: "time_pattern" },
{ platform: "webhook" }, { platform: "webhook" },
{ { platform: "zone" },
platform: "zone",
entity_id: "person.person",
zone: "zone.home",
event: "enter",
},
{ platform: "tag" }, { platform: "tag" },
{ platform: "time", at: "15:32" }, { platform: "time" },
{ platform: "template" }, { platform: "template" },
{ platform: "event", event_type: "homeassistant_started" }, { platform: "event" },
]; ];
const initialTrigger: Trigger = {
platform: "state",
entity_id: "light.kitchen",
};
@customElement("demo-automation-describe-trigger") @customElement("demo-automation-describe-trigger")
export class DemoAutomationDescribeTrigger extends LitElement { export class DemoAutomationDescribeTrigger extends LitElement {
@property({ attribute: false }) hass!: HomeAssistant;
@state() _trigger = initialTrigger;
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.hass) {
return html``;
}
return html` return html`
<ha-card header="Triggers"> <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( ${triggers.map(
(conf) => html` (conf) => html`
<div class="trigger"> <div class="trigger">
<span>${describeTrigger(conf as any, this.hass)}</span> <span>${describeTrigger(conf as any)}</span>
<pre>${dump(conf)}</pre> <pre>${dump(conf)}</pre>
</div> </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() { static get styles() {
return css` return css`
ha-card { ha-card {
@@ -122,9 +52,6 @@ export class DemoAutomationDescribeTrigger extends LitElement {
span { span {
margin-right: 16px; 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 Desing 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. 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>` # Alert `<ha-alert>`
The alert offers four severity levels that set a distinctive icon and color. 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 { html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators"; import { customElement, state } from "lit/decorators";
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry"; 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 { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry"; import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor"; import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
@@ -21,22 +20,16 @@ const ENTITIES = [
}), }),
getEntity("media_player", "livingroom", "playing", { getEntity("media_player", "livingroom", "playing", {
friendly_name: "Livingroom", friendly_name: "Livingroom",
media_content_type: "music",
device_class: "tv",
}), }),
getEntity("media_player", "lounge", "idle", { getEntity("media_player", "lounge", "idle", {
friendly_name: "Lounge", friendly_name: "Lounge",
supported_features: 444983, supported_features: 444983,
device_class: "speaker",
}), }),
getEntity("light", "bedroom", "on", { getEntity("light", "bedroom", "on", {
friendly_name: "Bedroom", friendly_name: "Bedroom",
effect: "colorloop",
effect_list: ["colorloop", "random"],
}), }),
getEntity("switch", "coffee", "off", { getEntity("switch", "coffee", "off", {
friendly_name: "Coffee", friendly_name: "Coffee",
device_class: "switch",
}), }),
]; ];
@@ -148,13 +141,7 @@ const SCHEMAS: {
selector: { attribute: { entity_id: "" } }, selector: { attribute: { entity_id: "" } },
context: { filter_entity: "entity" }, context: { filter_entity: "entity" },
}, },
{
name: "State",
selector: { state: { entity_id: "" } },
context: { filter_entity: "entity", filter_attribute: "Attribute" },
},
{ name: "Device", selector: { device: {} } }, { name: "Device", selector: { device: {} } },
{ name: "Config entry", selector: { config_entry: {} } },
{ name: "Duration", selector: { duration: {} } }, { name: "Duration", selector: { duration: {} } },
{ name: "area", selector: { area: {} } }, { name: "area", selector: { area: {} } },
{ name: "target", selector: { target: {} } }, { name: "target", selector: { target: {} } },
@@ -436,7 +423,6 @@ class DemoHaForm extends LitElement {
hass.addEntities(ENTITIES); hass.addEntities(ENTITIES);
mockEntityRegistry(hass); mockEntityRegistry(hass);
mockDeviceRegistry(hass, DEVICES); mockDeviceRegistry(hass, DEVICES);
mockConfigEntries(hass);
mockAreaRegistry(hass, AREAS); mockAreaRegistry(hass, AREAS);
mockHassioSupervisor(hass); mockHassioSupervisor(hass);
} }

View File

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

View File

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

View File

@@ -75,10 +75,6 @@ const ENTITIES = [
timestamp: 1641801600, timestamp: 1641801600,
friendly_name: "Date and Time", friendly_name: "Date and Time",
}), }),
getEntity("sensor", "humidity", "23.2", {
friendly_name: "Humidity",
unit_of_measurement: "%",
}),
getEntity("input_select", "dropdown", "Soda", { getEntity("input_select", "dropdown", "Soda", {
friendly_name: "Dropdown", friendly_name: "Dropdown",
options: ["Soda", "Beer", "Wine"], options: ["Soda", "Beer", "Wine"],
@@ -146,7 +142,6 @@ const CONFIGS = [
- light.non_existing - light.non_existing
- climate.ecobee - climate.ecobee
- input_number.number - input_number.number
- sensor.humidity
`, `,
}, },
{ {

View File

@@ -1,11 +1,11 @@
--- ---
title: Introduction 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 a different story about what is going on in their house. These cards
are very customizable, as no household is the same. are very customizable, as no household is the same.
This gallery helps our developers and designers to see all the This gallery helps our developers and designers to see all the
different states that each card can be in. 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

@@ -194,7 +194,6 @@ const createEntityRegistryEntries = (
name: null, name: null,
icon: null, icon: null,
platform: "updater", platform: "updater",
has_entity_name: false,
}, },
]; ];

View File

@@ -69,7 +69,7 @@ const ENTITIES = [
effect_list: ["random", "colorloop"], effect_list: ["random", "colorloop"],
}), }),
getEntity("light", "color_RGB_light", "on", { getEntity("light", "color_RGB_light", "on", {
friendly_name: "Color Effects Light", friendly_name: "Color Effets Light",
brightness: 255, brightness: 255,
rgb_color: [30, 100, 255], rgb_color: [30, 100, 255],
supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION, 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 { navigate } from "../../../src/common/navigate";
import { caseInsensitiveStringCompare } from "../../../src/common/string/compare"; import { caseInsensitiveStringCompare } from "../../../src/common/string/compare";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { HassioAddonRepository } from "../../../src/data/hassio/addon"; import {
import { StoreAddon } from "../../../src/data/supervisor/store"; HassioAddonInfo,
HassioAddonRepository,
} from "../../../src/data/hassio/addon";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
import "../components/hassio-card-content"; import "../components/hassio-card-content";
@@ -21,16 +23,20 @@ class HassioAddonRepositoryEl extends LitElement {
@property({ attribute: false }) public repo!: HassioAddonRepository; @property({ attribute: false }) public repo!: HassioAddonRepository;
@property({ attribute: false }) public addons!: StoreAddon[]; @property({ attribute: false }) public addons!: HassioAddonInfo[];
@property() public filter!: string; @property() public filter!: string;
private _getAddons = memoizeOne((addons: StoreAddon[], filter?: string) => { private _getAddons = memoizeOne(
if (filter) { (addons: HassioAddonInfo[], filter?: string) => {
return filterAndSort(addons, filter); 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 { protected render(): TemplateResult {
const repo = this.repo; const repo = this.repo;
@@ -81,10 +87,10 @@ class HassioAddonRepositoryEl extends LitElement {
? this.supervisor.localize( ? this.supervisor.localize(
"common.new_version_available" "common.new_version_available"
) )
: this.supervisor.localize("addon.state.installed") : this.supervisor.localize("addon.installed")
: addon.available : addon.available
? this.supervisor.localize("addon.state.not_installed") ? this.supervisor.localize("addon.not_installed")
: this.supervisor.localize("addon.state.not_available")} : this.supervisor.localize("addon.not_available")}
.iconClass=${addon.installed .iconClass=${addon.installed
? addon.update_available ? addon.update_available
? "update" ? "update"

View File

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

View File

@@ -336,7 +336,7 @@ class HassioAddonConfig extends LitElement {
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) { } catch (err: any) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.failed_to_reset", "addon.common.update_available",
"error", "error",
extractApiErrorMessage(err) extractApiErrorMessage(err)
); );

View File

@@ -81,7 +81,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
); );
} catch (err: any) { } catch (err: any) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.documentation.get_documentation", "addon.documentation.get_logs",
"error", "error",
extractApiErrorMessage(err) extractApiErrorMessage(err)
); );

View File

@@ -12,17 +12,15 @@ import { navigate } from "../../../src/common/navigate";
import { extractSearchParam } from "../../../src/common/url/search-params"; import { extractSearchParam } from "../../../src/common/url/search-params";
import "../../../src/components/ha-circular-progress"; import "../../../src/components/ha-circular-progress";
import { import {
fetchAddonInfo,
fetchHassioAddonInfo, fetchHassioAddonInfo,
fetchHassioAddonsInfo, fetchHassioAddonsInfo,
HassioAddonDetails, HassioAddonDetails,
} from "../../../src/data/hassio/addon"; } from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { import {
addStoreRepository, fetchHassioSupervisorInfo,
fetchSupervisorStore, setSupervisorOption,
StoreAddonDetails, } from "../../../src/data/hassio/supervisor";
} from "../../../src/data/supervisor/store";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box"; import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
import "../../../src/layouts/hass-error-screen"; import "../../../src/layouts/hass-error-screen";
@@ -47,9 +45,7 @@ class HassioAddonDashboard extends LitElement {
@property({ attribute: false }) public route!: Route; @property({ attribute: false }) public route!: Route;
@property({ attribute: false }) public addon?: @property({ attribute: false }) public addon?: HassioAddonDetails;
| HassioAddonDetails
| StoreAddonDetails;
@property({ type: Boolean }) public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
@@ -75,7 +71,7 @@ class HassioAddonDashboard extends LitElement {
></hass-error-screen>`; ></hass-error-screen>`;
} }
if (!this.addon || !this.supervisor?.addon) { if (!this.addon) {
return html`<hass-loading-screen></hass-loading-screen>`; return html`<hass-loading-screen></hass-loading-screen>`;
} }
@@ -177,10 +173,10 @@ class HassioAddonDashboard extends LitElement {
const requestedAddon = extractSearchParam("addon"); const requestedAddon = extractSearchParam("addon");
const requestedAddonRepository = extractSearchParam("repository_url"); const requestedAddonRepository = extractSearchParam("repository_url");
if (requestedAddonRepository) { if (requestedAddonRepository) {
const storeInfo = await fetchSupervisorStore(this.hass); const supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
if ( if (
!storeInfo.repositories.find( !supervisorInfo.addons_repositories.find(
(repo) => repo.source === requestedAddonRepository (repo) => repo === requestedAddonRepository
) )
) { ) {
if ( if (
@@ -201,7 +197,12 @@ class HassioAddonDashboard extends LitElement {
} }
try { try {
await addStoreRepository(this.hass, requestedAddonRepository); await setSupervisorOption(this.hass, {
addons_repositories: [
...supervisorInfo.addons_repositories,
requestedAddonRepository,
],
});
} catch (err: any) { } catch (err: any) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
} }
@@ -209,8 +210,8 @@ class HassioAddonDashboard extends LitElement {
} }
if (requestedAddon) { if (requestedAddon) {
const store = await fetchSupervisorStore(this.hass); const addonsInfo = await fetchHassioAddonsInfo(this.hass);
const validAddon = store.addons.some( const validAddon = addonsInfo.addons.some(
(addon) => addon.slug === requestedAddon (addon) => addon.slug === requestedAddon
); );
if (!validAddon) { if (!validAddon) {
@@ -238,14 +239,12 @@ class HassioAddonDashboard extends LitElement {
if (["uninstall", "install", "update", "start", "stop"].includes(path)) { if (["uninstall", "install", "update", "start", "stop"].includes(path)) {
fireEvent(this, "supervisor-collection-refresh", { fireEvent(this, "supervisor-collection-refresh", {
collection: "addon", collection: "supervisor",
}); });
} }
if (path === "uninstall") { if (path === "uninstall") {
window.history.back(); window.history.back();
} else if (path === "install") {
this.addon = await fetchHassioAddonInfo(this.hass, this.addon!.slug);
} else { } else {
await this._routeDataChanged(); await this._routeDataChanged();
} }
@@ -263,11 +262,8 @@ class HassioAddonDashboard extends LitElement {
return; return;
} }
try { try {
if (!this.supervisor.addon) { const addoninfo = await fetchHassioAddonInfo(this.hass, addon);
const addonsInfo = await fetchHassioAddonsInfo(this.hass); this.addon = addoninfo;
fireEvent(this, "supervisor-update", { addon: addonsInfo });
}
this.addon = await fetchAddonInfo(this.hass, this.supervisor, addon);
} catch (err: any) { } catch (err: any) {
this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`; this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`;
this.addon = undefined; this.addon = undefined;

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
import Fuse from "fuse.js"; 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) { export function filterAndSort(addons: HassioAddonInfo[], filter: string) {
const options: Fuse.IFuseOptions<StoreAddon> = { const options: Fuse.IFuseOptions<HassioAddonInfo> = {
keys: ["name", "description", "slug"], keys: ["name", "description", "slug"],
isCaseSensitive: false, isCaseSensitive: false,
minMatchCharLength: 2, minMatchCharLength: 2,

View File

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

View File

@@ -24,7 +24,7 @@ class HassioAddons extends LitElement {
? html` <h1>${this.supervisor.localize("dashboard.addons")}</h1> ` ? html` <h1>${this.supervisor.localize("dashboard.addons")}</h1> `
: ""} : ""}
<div class="card-group"> <div class="card-group">
${!this.supervisor.addon.addons.length ${!this.supervisor.supervisor.addons?.length
? html` ? html`
<ha-card outlined> <ha-card outlined>
<div class="card-content"> <div class="card-content">
@@ -34,7 +34,7 @@ class HassioAddons extends LitElement {
</div> </div>
</ha-card> </ha-card>
` `
: this.supervisor.addon.addons : this.supervisor.supervisor.addons
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)) .sort((a, b) => caseInsensitiveStringCompare(a.name, b.name))
.map( .map(
(addon) => html` (addon) => html`

View File

@@ -201,24 +201,26 @@ class HassioBackupDialog
} }
if (!this._dialogParams?.onboarding) { if (!this._dialogParams?.onboarding) {
try { this.hass!.callApi(
await this.hass!.callApi( "POST",
"POST",
`hassio/${ `hassio/${
atLeastVersion(this.hass!.config.version, 2021, 9) atLeastVersion(this.hass!.config.version, 2021, 9)
? "backups" ? "backups"
: "snapshots" : "snapshots"
}/${this._backup!.slug}/restore/partial`, }/${this._backup!.slug}/restore/partial`,
backupDetails backupDetails
); ).then(
this.closeDialog(); () => {
} catch (error: any) { this.closeDialog();
this._error = error.body.message; },
} (error) => {
this._error = error.body.message;
}
);
} else { } else {
fireEvent(this, "restoring"); fireEvent(this, "restoring");
await fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, { fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
method: "POST", method: "POST",
body: JSON.stringify(backupDetails), 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 { customElement, property, state } from "lit/decorators";
import { createCloseHeading } from "../../../../src/components/ha-dialog"; import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-form/ha-form"; 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-icon-button";
import "../../../../src/components/ha-settings-row"; import "../../../../src/components/ha-settings-row";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; 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 type { HomeAssistant } from "../../../../src/types";
import { RegistriesDialogParams } from "./show-dialog-registries"; import { RegistriesDialogParams } from "./show-dialog-registries";
const SCHEMA = [ const SCHEMA: HaFormSchema[] = [
{ {
name: "registry", name: "registry",
required: true, required: true,
@@ -35,7 +35,7 @@ const SCHEMA = [
required: true, required: true,
selector: { text: { type: "password" } }, selector: { text: { type: "password" } },
}, },
] as const; ];
@customElement("dialog-hassio-registries") @customElement("dialog-hassio-registries")
class HassioRegistriesDialog extends LitElement { class HassioRegistriesDialog extends LitElement {
@@ -135,8 +135,8 @@ class HassioRegistriesDialog extends LitElement {
`; `;
} }
private _computeLabel = (schema: SchemaUnion<typeof SCHEMA>) => private _computeLabel = (schema: HaFormSchema) =>
this.supervisor.localize(`dialog.registries.${schema.name}`); this.supervisor.localize(`dialog.registries.${schema.name}`) || schema.name;
private _valueChanged(ev: CustomEvent) { private _valueChanged(ev: CustomEvent) {
this._input = ev.detail.value; 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 { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-icon-button"; import "../../../../src/components/ha-icon-button";
import { import {
fetchHassioAddonsInfo,
HassioAddonInfo, HassioAddonInfo,
HassioAddonRepository, HassioAddonRepository,
} from "../../../../src/data/hassio/addon"; } from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { setSupervisorOption } from "../../../../src/data/hassio/supervisor";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types"; import type { HomeAssistant } from "../../../../src/types";
import { HassioRepositoryDialogParams } from "./show-dialog-repositories"; import { HassioRepositoryDialogParams } from "./show-dialog-repositories";
import {
addStoreRepository,
fetchStoreRepositories,
removeStoreRepository,
} from "../../../../src/data/supervisor/store";
@customElement("dialog-hassio-repositories") @customElement("dialog-hassio-repositories")
class HassioRepositoriesDialog extends LitElement { class HassioRepositoriesDialog extends LitElement {
@@ -61,13 +58,7 @@ class HassioRepositoriesDialog extends LitElement {
private _filteredRepositories = memoizeOne((repos: HassioAddonRepository[]) => private _filteredRepositories = memoizeOne((repos: HassioAddonRepository[]) =>
repos repos
.filter( .filter((repo) => repo.slug !== "core" && repo.slug !== "local")
(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
)
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)) .sort((a, b) => caseInsensitiveStringCompare(a.name, b.name))
); );
@@ -87,7 +78,7 @@ class HassioRepositoriesDialog extends LitElement {
const repositories = this._filteredRepositories(this._repositories); const repositories = this._filteredRepositories(this._repositories);
const usedRepositories = this._filteredUsedRepositories( const usedRepositories = this._filteredUsedRepositories(
repositories, repositories,
this._dialogParams.supervisor.addon.addons this._dialogParams.supervisor.supervisor.addons
); );
return html` return html`
<ha-dialog <ha-dialog
@@ -224,7 +215,9 @@ class HassioRepositoriesDialog extends LitElement {
private async _loadData(): Promise<void> { private async _loadData(): Promise<void> {
try { try {
this._repositories = await fetchStoreRepositories(this.hass); const addonsinfo = await fetchHassioAddonsInfo(this.hass);
this._repositories = addonsinfo.repositories;
fireEvent(this, "supervisor-collection-refresh", { collection: "addon" }); fireEvent(this, "supervisor-collection-refresh", { collection: "addon" });
} catch (err: any) { } catch (err: any) {
@@ -238,9 +231,14 @@ class HassioRepositoriesDialog extends LitElement {
return; return;
} }
this._processing = true; this._processing = true;
const repositories = this._filteredRepositories(this._repositories!);
const newRepositories = repositories.map((repo) => repo.source);
newRepositories.push(input.value);
try { try {
await addStoreRepository(this.hass, input.value); await setSupervisorOption(this.hass, {
addons_repositories: newRepositories,
});
await this._loadData(); await this._loadData();
input.value = ""; input.value = "";
@@ -252,8 +250,19 @@ class HassioRepositoriesDialog extends LitElement {
private async _removeRepository(ev: Event) { private async _removeRepository(ev: Event) {
const slug = (ev.currentTarget as any).slug; 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 { try {
await removeStoreRepository(this.hass, slug); await setSupervisorOption(this.hass, {
addons_repositories: newRepositories,
});
await this._loadData(); await this._loadData();
} catch (err: any) { } catch (err: any) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);

View File

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

View File

@@ -26,7 +26,7 @@ import {
import { import {
UNHEALTHY_REASON_URL, UNHEALTHY_REASON_URL,
UNSUPPORTED_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 { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
import { bytesToString } from "../../../src/util/bytes-to-string"; import { bytesToString } from "../../../src/util/bytes-to-string";

View File

@@ -1,11 +1,4 @@
module.exports = { module.exports = {
"*.{js,ts}": [ "*.{js,ts}": 'eslint --ignore-pattern "**/build-scripts/**/*.js" --fix',
"prettier --write", "!(/translations)*.{js,ts,json,css,md,html}": "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",
}; };

View File

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

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "home-assistant-frontend" name = "home-assistant-frontend"
version = "20220816.0" version = "20220601.0"
license = {text = "Apache-2.0"} license = {text = "Apache-2.0"}
description = "The Home Assistant frontend" description = "The Home Assistant frontend"
readme = "README.md" readme = "README.md"
@@ -23,3 +23,8 @@ include-package-data = true
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
include = ["hass_frontend*"] include = ["hass_frontend*"]
[tool.mypy]
python_version = 3.4
show_error_codes = true
strict = true

View File

@@ -24,15 +24,10 @@ function auto(version) {
return patch(version); return patch(version);
} }
function nightly() {
return `${today()}.dev`;
}
const methods = { const methods = {
patch, patch,
today, today,
auto, auto,
nightly,
}; };
async function main(args) { async function main(args) {
@@ -62,11 +57,7 @@ async function main(args) {
console.log("Current version:", version); console.log("Current version:", version);
console.log("New version:", newVersion); console.log("New version:", newVersion);
fs.writeFileSync( fs.writeFileSync("pyproject.toml", setup.replace(version, newVersion), "utf-8");
"pyproject.toml",
setup.replace(version, newVersion),
"utf-8"
);
if (!commit) { if (!commit) {
return; return;

View File

@@ -1,2 +0,0 @@
# Setuptools v62.3 doesn't support editable installs with just 'pyproject.toml' (PEP 660).
# Keep this file until it does!

View File

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

View File

@@ -47,7 +47,7 @@ import {
mdiRobotVacuum, mdiRobotVacuum,
mdiScriptText, mdiScriptText,
mdiSineWave, mdiSineWave,
mdiMicrophoneMessage, mdiTextToSpeech,
mdiThermometer, mdiThermometer,
mdiThermostat, mdiThermostat,
mdiTimerOutline, mdiTimerOutline,
@@ -74,9 +74,8 @@ export const FIXED_DOMAIN_ICONS = {
camera: mdiVideo, camera: mdiVideo,
climate: mdiThermostat, climate: mdiThermostat,
configurator: mdiCog, configurator: mdiCog,
conversation: mdiMicrophoneMessage, conversation: mdiTextToSpeech,
counter: mdiCounter, counter: mdiCounter,
demo: mdiHomeAssistant,
fan: mdiFan, fan: mdiFan,
google_assistant: mdiGoogleAssistant, google_assistant: mdiGoogleAssistant,
group: mdiGoogleCirclesCommunities, group: mdiGoogleCirclesCommunities,
@@ -98,7 +97,6 @@ export const FIXED_DOMAIN_ICONS = {
proximity: mdiAppleSafari, proximity: mdiAppleSafari,
remote: mdiRemote, remote: mdiRemote,
scene: mdiPalette, scene: mdiPalette,
schedule: mdiCalendarClock,
script: mdiScriptText, script: mdiScriptText,
select: mdiFormatListBulleted, select: mdiFormatListBulleted,
sensor: mdiEye, sensor: mdiEye,
@@ -167,6 +165,46 @@ export const DOMAINS_WITH_CARD = [
"water_heater", "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. /** 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 * 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 * be the default) unless the element itself enforces it (e.g. a button). Also those elements
@@ -198,6 +236,9 @@ export const DOMAINS_INPUT_ROW = [
"vacuum", "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". */ /** States that we consider "off". */
export const STATES_OFF = ["closed", "locked", "off"]; export const STATES_OFF = ["closed", "locked", "off"];

View File

@@ -1,7 +1,7 @@
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation"; import { FrontendLocaleData } from "../../data/translation";
import { polyfillsLoaded } from "../translations/localize";
import { useAmPm } from "./use_am_pm"; import { useAmPm } from "./use_am_pm";
import { polyfillsLoaded } from "../translations/localize";
if (__BUILD__ === "latest" && polyfillsLoaded) { if (__BUILD__ === "latest" && polyfillsLoaded) {
await 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 // August 9, 2021, 8:23:15 AM
export const formatDateTimeWithSeconds = ( export const formatDateTimeWithSeconds = (
dateObj: Date, dateObj: Date,

View File

@@ -1,7 +1,7 @@
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation"; import { FrontendLocaleData } from "../../data/translation";
import { polyfillsLoaded } from "../translations/localize";
import { useAmPm } from "./use_am_pm"; import { useAmPm } from "./use_am_pm";
import { polyfillsLoaded } from "../translations/localize";
if (__BUILD__ === "latest" && polyfillsLoaded) { if (__BUILD__ === "latest" && polyfillsLoaded) {
await 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 { public setValue(storageKey: string, value: any): any {
this._storage[storageKey] = value; this._storage[storageKey] = value;
try { try {
if (value === undefined) { window.localStorage.setItem(storageKey, JSON.stringify(value));
window.localStorage.removeItem(storageKey);
} else {
window.localStorage.setItem(storageKey, JSON.stringify(value));
}
} catch (err: any) { } catch (err: any) {
// Safari in private mode doesn't allow localstorage // Safari in private mode doesn't allow localstorage
} }

View File

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

View File

@@ -64,12 +64,9 @@ export const computeStateDisplayFromEntityAttributes = (
// fallback to default // fallback to default
} }
} }
const unit = !attributes.unit_of_measurement return `${formatNumber(state, locale)}${
? "" attributes.unit_of_measurement ? " " + attributes.unit_of_measurement : ""
: attributes.unit_of_measurement === "%" }`;
? "%"
: ` ${attributes.unit_of_measurement}`;
return `${formatNumber(state, locale)}${unit}`;
} }
const domain = computeDomain(entityId); const domain = computeDomain(entityId);

View File

@@ -8,8 +8,6 @@ import {
mdiCalendar, mdiCalendar,
mdiCast, mdiCast,
mdiCastConnected, mdiCastConnected,
mdiCastOff,
mdiChartSankey,
mdiCheckCircleOutline, mdiCheckCircleOutline,
mdiClock, mdiClock,
mdiCloseCircleOutline, mdiCloseCircleOutline,
@@ -26,15 +24,6 @@ import {
mdiPowerPlug, mdiPowerPlug,
mdiPowerPlugOff, mdiPowerPlugOff,
mdiRestart, mdiRestart,
mdiSpeaker,
mdiSpeakerOff,
mdiSpeakerPause,
mdiSpeakerPlay,
mdiSwapHorizontal,
mdiTelevision,
mdiTelevisionOff,
mdiTelevisionPause,
mdiTelevisionPlay,
mdiToggleSwitchVariant, mdiToggleSwitchVariant,
mdiToggleSwitchVariantOff, mdiToggleSwitchVariantOff,
mdiWeatherNight, mdiWeatherNight,
@@ -136,40 +125,7 @@ export const domainIconWithoutDefault = (
} }
case "media_player": case "media_player":
switch (stateObj?.attributes.device_class) { return compareState === "playing" ? mdiCastConnected : mdiCast;
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;
}
}
case "switch": case "switch":
switch (stateObj?.attributes.device_class) { switch (stateObj?.attributes.device_class) {
@@ -197,12 +153,6 @@ export const domainIconWithoutDefault = (
? FIXED_DOMAIN_ICONS[domain] ? FIXED_DOMAIN_ICONS[domain]
: mdiWeatherNight; : mdiWeatherNight;
case "switch_as_x":
return mdiSwapHorizontal;
case "threshold":
return mdiChartSankey;
case "update": case "update":
return compareState === "on" return compareState === "on"
? updateIsInstalling(stateObj as UpdateEntity) ? updateIsInstalling(stateObj as UpdateEntity)

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

@@ -1,88 +0,0 @@
import { html } from "lit";
import { getConfigEntries } from "../../data/config_entries";
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
import { showZWaveJSAddNodeDialog } from "../../panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node";
import type { HomeAssistant } from "../../types";
import { documentationUrl } from "../../util/documentation-url";
import { isComponentLoaded } from "../config/is_component_loaded";
import { fireEvent } from "../dom/fire_event";
import { navigate } from "../navigate";
export const protocolIntegrationPicked = async (
element: HTMLElement,
hass: HomeAssistant,
slug: string
) => {
if (slug === "zwave_js") {
const entries = await getConfigEntries(hass, {
domain: "zwave_js",
});
if (!entries.length) {
// If the component isn't loaded, ask them to load the integration first
showConfirmationDialog(element, {
text: hass.localize(
"ui.panel.config.integrations.config_flow.missing_zwave_zigbee",
{
integration: "Z-Wave",
supported_hardware_link: html`<a
href=${documentationUrl(hass, "/docs/z-wave/controllers")}
target="_blank"
rel="noreferrer"
>${hass.localize(
"ui.panel.config.integrations.config_flow.supported_hardware"
)}</a
>`,
}
),
confirmText: hass.localize(
"ui.panel.config.integrations.config_flow.proceed"
),
confirm: () => {
fireEvent(element, "handler-picked", {
handler: "zwave_js",
});
},
});
return;
}
showZWaveJSAddNodeDialog(element, {
entry_id: entries[0].entry_id,
});
} else if (slug === "zha") {
// If the component isn't loaded, ask them to load the integration first
if (!isComponentLoaded(hass, "zha")) {
showConfirmationDialog(element, {
text: hass.localize(
"ui.panel.config.integrations.config_flow.missing_zwave_zigbee",
{
integration: "Zigbee",
supported_hardware_link: html`<a
href=${documentationUrl(
hass,
"/integrations/zha/#known-working-zigbee-radio-modules"
)}
target="_blank"
rel="noreferrer"
>${hass.localize(
"ui.panel.config.integrations.config_flow.supported_hardware"
)}</a
>`,
}
),
confirmText: hass.localize(
"ui.panel.config.integrations.config_flow.proceed"
),
confirm: () => {
fireEvent(element, "handler-picked", {
handler: "zha",
});
},
});
return;
}
navigate("/config/zha/add");
}
};

View File

@@ -5,6 +5,6 @@ export const clamp = (value: number, min: number, max: number) =>
export const conditionalClamp = (value: number, min?: number, max?: number) => { export const conditionalClamp = (value: number, min?: number, max?: number) => {
let result: number; let result: number;
result = min ? Math.max(value, min) : value; result = min ? Math.max(value, min) : value;
result = max ? Math.min(result, max) : result; result = max ? Math.min(value, max) : value;
return result; return result;
}; };

View File

@@ -3,66 +3,10 @@ import { shouldPolyfill as shouldPolyfillPluralRules } from "@formatjs/intl-plur
import { shouldPolyfill as shouldPolyfillRelativeTime } from "@formatjs/intl-relativetimeformat/lib/should-polyfill"; import { shouldPolyfill as shouldPolyfillRelativeTime } from "@formatjs/intl-relativetimeformat/lib/should-polyfill";
import { shouldPolyfill as shouldPolyfillDateTime } from "@formatjs/intl-datetimeformat/lib/should-polyfill"; import { shouldPolyfill as shouldPolyfillDateTime } from "@formatjs/intl-datetimeformat/lib/should-polyfill";
import IntlMessageFormat from "intl-messageformat"; import IntlMessageFormat from "intl-messageformat";
import { Resources, TranslationDict } from "../../types"; import { Resources } from "../../types";
import { getLocalLanguage } from "../../util/common-translation"; import { getLocalLanguage } from "../../util/common-translation";
// Exclude some patterns from key type checking for now export type LocalizeFunc = (key: string, ...args: any[]) => string;
// These are intended to be removed as errors are fixed
// Fixing component category will require tighter definition of types from backend and/or web socket
export type LocalizeKeys =
| FlattenObjectKeys<Omit<TranslationDict, "supervisor">>
| `panel.${string}`
| `state.${string}`
| `state_attributes.${string}`
| `state_badge.${string}`
| `ui.card.alarm_control_panel.${string}`
| `ui.card.weather.attributes.${string}`
| `ui.card.weather.cardinal_direction.${string}`
| `ui.components.logbook.${string}`
| `ui.components.selectors.file.${string}`
| `ui.dialogs.entity_registry.editor.${string}`
| `ui.dialogs.more_info_control.vacuum.${string}`
| `ui.dialogs.options_flow.loading.${string}`
| `ui.dialogs.quick-bar.commands.${string}`
| `ui.dialogs.repair_flow.loading.${string}`
| `ui.dialogs.unhealthy.reason.${string}`
| `ui.dialogs.unsupported.reason.${string}`
| `ui.panel.config.${string}.${"caption" | "description"}`
| `ui.panel.config.automation.${string}`
| `ui.panel.config.dashboard.${string}`
| `ui.panel.config.devices.${string}`
| `ui.panel.config.energy.${string}`
| `ui.panel.config.helpers.${string}`
| `ui.panel.config.info.${string}`
| `ui.panel.config.integrations.${string}`
| `ui.panel.config.logs.${string}`
| `ui.panel.config.lovelace.${string}`
| `ui.panel.config.network.${string}`
| `ui.panel.config.scene.${string}`
| `ui.panel.config.url.${string}`
| `ui.panel.config.zha.${string}`
| `ui.panel.config.zwave_js.${string}`
| `ui.panel.developer-tools.tabs.${string}`
| `ui.panel.lovelace.card.${string}`
| `ui.panel.lovelace.editor.${string}`
| `ui.panel.page-authorize.form.${string}`
| `component.${string}`;
// Tweaked from https://www.raygesualdo.com/posts/flattening-object-keys-with-typescript-types
export type FlattenObjectKeys<
T extends Record<string, any>,
Key extends keyof T = keyof T
> = Key extends string
? T[Key] extends Record<string, unknown>
? `${Key}.${FlattenObjectKeys<T[Key]>}`
: `${Key}`
: never;
export type LocalizeFunc<Keys extends string = LocalizeKeys> = (
key: Keys,
...args: any[]
) => string;
interface FormatType { interface FormatType {
[format: string]: any; [format: string]: any;
} }
@@ -121,12 +65,12 @@ export const polyfillsLoaded =
* } * }
*/ */
export const computeLocalize = async <Keys extends string = LocalizeKeys>( export const computeLocalize = async (
cache: any, cache: any,
language: string, language: string,
resources: Resources, resources: Resources,
formats?: FormatsType formats?: FormatsType
): Promise<LocalizeFunc<Keys>> => { ): Promise<LocalizeFunc> => {
if (polyfillsLoaded) { if (polyfillsLoaded) {
await polyfillsLoaded; await polyfillsLoaded;
} }

View File

@@ -11,8 +11,6 @@ import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import { clamp } from "../../common/number/clamp"; import { clamp } from "../../common/number/clamp";
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
interface Tooltip extends TooltipModel<any> { interface Tooltip extends TooltipModel<any> {
top: string; top: string;
left: string; left: string;
@@ -188,10 +186,6 @@ export default class HaChartBase extends LitElement {
ChartConstructor.defaults.color = computedStyles.getPropertyValue( ChartConstructor.defaults.color = computedStyles.getPropertyValue(
"--secondary-text-color" "--secondary-text-color"
); );
ChartConstructor.defaults.font.family =
computedStyles.getPropertyValue("--mdc-typography-body1-font-family") ||
computedStyles.getPropertyValue("--mdc-typography-font-family") ||
"Roboto, Noto, sans-serif";
this.chart = new ChartConstructor(ctx, { this.chart = new ChartConstructor(ctx, {
type: this.chartType, type: this.chartType,
@@ -330,9 +324,6 @@ export default class HaChartBase extends LitElement {
width: 16px; width: 16px;
flex-shrink: 0; flex-shrink: 0;
box-sizing: border-box; box-sizing: border-box;
margin-inline-end: 6px;
margin-inline-start: initial;
direction: var(--direction);
} }
.chartTooltip .bullet { .chartTooltip .bullet {
align-self: baseline; align-self: baseline;
@@ -341,9 +332,6 @@ export default class HaChartBase extends LitElement {
:host([rtl]) .chartTooltip .bullet { :host([rtl]) .chartTooltip .bullet {
margin-right: inherit; margin-right: inherit;
margin-left: 6px; margin-left: 6px;
margin-inline-end: inherit;
margin-inline-start: 6px;
direction: var(--direction);
} }
.chartTooltip { .chartTooltip {
padding: 8px; padding: 8px;
@@ -380,7 +368,6 @@ export default class HaChartBase extends LitElement {
.chartTooltip .title { .chartTooltip .title {
text-align: center; text-align: center;
font-weight: 500; font-weight: 500;
direction: ltr;
} }
.chartTooltip .footer { .chartTooltip .footer {
font-weight: 500; font-weight: 500;

View File

@@ -8,7 +8,7 @@ import {
} from "../../common/number/format_number"; } from "../../common/number/format_number";
import { LineChartEntity, LineChartState } from "../../data/history"; import { LineChartEntity, LineChartState } from "../../data/history";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import { MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base"; import "./ha-chart-base";
const safeParseFloat = (value) => { const safeParseFloat = (value) => {
const parsed = parseFloat(value); const parsed = parseFloat(value);
@@ -34,8 +34,6 @@ class StateHistoryChartLine extends LitElement {
@state() private _chartOptions?: ChartOptions; @state() private _chartOptions?: ChartOptions;
private _chartTime: Date = new Date();
protected render() { protected render() {
return html` return html`
<ha-chart-base <ha-chart-base
@@ -123,13 +121,7 @@ class StateHistoryChartLine extends LitElement {
locale: numberFormatToLocale(this.hass.locale), locale: numberFormatToLocale(this.hass.locale),
}; };
} }
if ( if (changedProps.has("data")) {
changedProps.has("data") ||
this._chartTime <
new Date(this.endTime.getTime() - MIN_TIME_BETWEEN_UPDATES)
) {
// If the line is more than 5 minutes old, re-gen it
// so the X axis grows even if there is no new data
this._generateData(); this._generateData();
} }
} }
@@ -143,7 +135,6 @@ class StateHistoryChartLine extends LitElement {
return; return;
} }
this._chartTime = new Date();
const endTime = this.endTime; const endTime = this.endTime;
const names = this.names || {}; const names = this.names || {};
entityStates.forEach((states) => { entityStates.forEach((states) => {

View File

@@ -9,7 +9,7 @@ import { numberFormatToLocale } from "../../common/number/format_number";
import { computeRTL } from "../../common/util/compute_rtl"; import { computeRTL } from "../../common/util/compute_rtl";
import { TimelineEntity } from "../../data/history"; import { TimelineEntity } from "../../data/history";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import { MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base"; import "./ha-chart-base";
import type { TimeLineData } from "./timeline-chart/const"; import type { TimeLineData } from "./timeline-chart/const";
/** Binary sensor device classes for which the static colors for on/off are NOT inverted. /** Binary sensor device classes for which the static colors for on/off are NOT inverted.
@@ -103,8 +103,6 @@ export class StateHistoryChartTimeline extends LitElement {
@state() private _chartOptions?: ChartOptions<"timeline">; @state() private _chartOptions?: ChartOptions<"timeline">;
private _chartTime: Date = new Date();
protected render() { protected render() {
return html` return html`
<ha-chart-base <ha-chart-base
@@ -213,13 +211,7 @@ export class StateHistoryChartTimeline extends LitElement {
locale: numberFormatToLocale(this.hass.locale), locale: numberFormatToLocale(this.hass.locale),
}; };
} }
if ( if (changedProps.has("data")) {
changedProps.has("data") ||
this._chartTime <
new Date(this.endTime.getTime() - MIN_TIME_BETWEEN_UPDATES)
) {
// If the line is more than 5 minutes old, re-gen it
// so the X axis grows even if there is no new data
this._generateData(); this._generateData();
} }
} }
@@ -232,7 +224,6 @@ export class StateHistoryChartTimeline extends LitElement {
stateHistory = []; stateHistory = [];
} }
this._chartTime = new Date();
const startTime = this.startTime; const startTime = this.startTime;
const endTime = this.endTime; const endTime = this.endTime;
const labels: string[] = []; const labels: string[] = [];

View File

@@ -186,7 +186,6 @@ class StateHistoryCharts extends LitElement {
line-height: 60px; line-height: 60px;
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
.container { .container {
max-height: var(--history-max-height); max-height: var(--history-max-height);
} }

View File

@@ -15,13 +15,13 @@ import {
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { getGraphColorByIndex } from "../../common/color/colors"; import { getGraphColorByIndex } from "../../common/color/colors";
import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { computeStateName } from "../../common/entity/compute_state_name";
import { import {
formatNumber, formatNumber,
numberFormatToLocale, numberFormatToLocale,
} from "../../common/number/format_number"; } from "../../common/number/format_number";
import { import {
getStatisticIds, getStatisticIds,
getStatisticLabel,
Statistics, Statistics,
statisticsHaveType, statisticsHaveType,
StatisticsMetaData, StatisticsMetaData,
@@ -233,18 +233,24 @@ class StatisticsChart extends LitElement {
const names = this.names || {}; const names = this.names || {};
statisticsData.forEach((stats) => { statisticsData.forEach((stats) => {
const firstStat = stats[0]; const firstStat = stats[0];
let name = names[firstStat.statistic_id];
if (!name) {
const entityState = this.hass.states[firstStat.statistic_id];
if (entityState) {
name = computeStateName(entityState);
} else {
name = firstStat.statistic_id;
}
}
const meta = this.statisticIds!.find( const meta = this.statisticIds!.find(
(stat) => stat.statistic_id === firstStat.statistic_id (stat) => stat.statistic_id === firstStat.statistic_id
); );
let name = names[firstStat.statistic_id];
if (!name) {
name = getStatisticLabel(this.hass, firstStat.statistic_id, meta);
}
if (!this.unit) { if (!this.unit) {
if (unit === undefined) { if (unit === undefined) {
unit = meta?.display_unit_of_measurement; unit = meta?.unit_of_measurement;
} else if (unit !== meta?.display_unit_of_measurement) { } else if (unit !== meta?.unit_of_measurement) {
unit = null; unit = null;
} }
} }

View File

@@ -221,10 +221,6 @@ class DateRangePickerElement extends WrappedElement {
.calendar-table { .calendar-table {
padding: 0 !important; padding: 0 !important;
} }
.daterangepicker.ltr {
direction: ltr;
text-align: left;
}
`; `;
const shadowRoot = this.shadowRoot!; const shadowRoot = this.shadowRoot!;
shadowRoot.appendChild(style); shadowRoot.appendChild(style);

View File

@@ -1,7 +1,7 @@
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";

View File

@@ -172,7 +172,8 @@ export abstract class HaDeviceAutomationPicker<
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
ha-select { ha-select {
display: block; width: 100%;
margin-top: 4px;
} }
`; `;
} }

View File

@@ -1,7 +1,7 @@
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";

View File

@@ -15,14 +15,6 @@ class HaEntityAttributePicker extends LitElement {
@property() public entityId?: string; @property() public entityId?: string;
/**
* List of attributes to be hidden.
* @type {Array}
* @attr hide-attributes
*/
@property({ type: Array, attribute: "hide-attributes" })
public hideAttributes?: string[];
@property({ type: Boolean }) public autofocus = false; @property({ type: Boolean }) public autofocus = false;
@property({ type: Boolean }) public disabled = false; @property({ type: Boolean }) public disabled = false;
@@ -50,12 +42,10 @@ class HaEntityAttributePicker extends LitElement {
if (changedProps.has("_opened") && this._opened) { if (changedProps.has("_opened") && this._opened) {
const state = this.entityId ? this.hass.states[this.entityId] : undefined; const state = this.entityId ? this.hass.states[this.entityId] : undefined;
(this._comboBox as any).items = state (this._comboBox as any).items = state
? Object.keys(state.attributes) ? Object.keys(state.attributes).map((key) => ({
.filter((key) => !this.hideAttributes?.includes(key)) value: key,
.map((key) => ({ label: formatAttributeName(key),
value: key, }))
label: formatAttributeName(key),
}))
: []; : [];
} }
} }
@@ -68,7 +58,7 @@ class HaEntityAttributePicker extends LitElement {
return html` return html`
<ha-combo-box <ha-combo-box
.hass=${this.hass} .hass=${this.hass}
.value=${this.value ? formatAttributeName(this.value) : ""} .value=${this.value || ""}
.autofocus=${this.autofocus} .autofocus=${this.autofocus}
.label=${this.label ?? .label=${this.label ??
this.hass.localize( this.hass.localize(

View File

@@ -1,7 +1,7 @@
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";

View File

@@ -1,111 +0,0 @@
import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import { computeStateDisplay } from "../../common/entity/compute_state_display";
import { PolymerChangedEvent } from "../../polymer-types";
import { getStates } from "../../common/entity/get_states";
import { HomeAssistant } from "../../types";
import "../ha-combo-box";
import type { HaComboBox } from "../ha-combo-box";
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
@customElement("ha-entity-state-picker")
class HaEntityStatePicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public entityId?: string;
@property() public attribute?: string;
@property({ type: Boolean }) public autofocus = false;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = false;
@property({ type: Boolean, attribute: "allow-custom-value" })
public allowCustomValue;
@property() public label?: string;
@property() public value?: string;
@property() public helper?: string;
@property({ type: Boolean }) private _opened = false;
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
protected shouldUpdate(changedProps: PropertyValues) {
return !(!changedProps.has("_opened") && this._opened);
}
protected updated(changedProps: PropertyValues) {
if (changedProps.has("_opened") && this._opened) {
const state = this.entityId ? this.hass.states[this.entityId] : undefined;
(this._comboBox as any).items =
this.entityId && state
? getStates(state, this.attribute).map((key) => ({
value: key,
label: !this.attribute
? computeStateDisplay(
this.hass.localize,
state,
this.hass.locale,
key
)
: key,
}))
: [];
}
}
protected render(): TemplateResult {
if (!this.hass) {
return html``;
}
return html`
<ha-combo-box
.hass=${this.hass}
.value=${this.value
? this.entityId && this.hass.states[this.entityId]
? computeStateDisplay(
this.hass.localize,
this.hass.states[this.entityId],
this.hass.locale,
this.value
)
: this.value
: ""}
.autofocus=${this.autofocus}
.label=${this.label ??
this.hass.localize("ui.components.entity.entity-state-picker.state")}
.disabled=${this.disabled || !this.entityId}
.required=${this.required}
.helper=${this.helper}
.allowCustomValue=${this.allowCustomValue}
item-value-path="value"
item-label-path="label"
@opened-changed=${this._openedChanged}
@value-changed=${this._valueChanged}
>
</ha-combo-box>
`;
}
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
this._opened = ev.detail.value;
}
private _valueChanged(ev: PolymerChangedEvent<string>) {
this.value = ev.detail.value;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-entity-state-picker": HaEntityStatePicker;
}
}

View File

@@ -1,6 +1,6 @@
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
@@ -31,23 +31,12 @@ export class HaStatisticPicker extends LitElement {
@property({ type: Boolean }) public disabled?: boolean; @property({ type: Boolean }) public disabled?: boolean;
/** /**
* Show only statistics natively stored with these units of measurements. * Show only statistics with these unit of measuments.
* @type {Array} * @type {Array}
* @attr include-statistics-unit-of-measurement * @attr include-unit-of-measurement
*/ */
@property({ @property({ type: Array, attribute: "include-unit-of-measurement" })
type: Array, public includeUnitOfMeasurement?: string[];
attribute: "include-statistics-unit-of-measurement",
})
public includeStatisticsUnitOfMeasurement?: string[];
/**
* Show only statistics displayed with these units of measurements.
* @type {Array}
* @attr include-display-unit-of-measurement
*/
@property({ type: Array, attribute: "include-display-unit-of-measurement" })
public includeDisplayUnitOfMeasurement?: string[];
/** /**
* Show only statistics with these device classes. * Show only statistics with these device classes.
@@ -97,8 +86,7 @@ export class HaStatisticPicker extends LitElement {
private _getStatistics = memoizeOne( private _getStatistics = memoizeOne(
( (
statisticIds: StatisticsMetaData[], statisticIds: StatisticsMetaData[],
includeStatisticsUnitOfMeasurement?: string[], includeUnitOfMeasurement?: string[],
includeDisplayUnitOfMeasurement?: string[],
includeDeviceClasses?: string[], includeDeviceClasses?: string[],
entitiesOnly?: boolean entitiesOnly?: boolean
): Array<{ id: string; name: string; state?: HassEntity }> => { ): Array<{ id: string; name: string; state?: HassEntity }> => {
@@ -113,18 +101,9 @@ export class HaStatisticPicker extends LitElement {
]; ];
} }
if (includeStatisticsUnitOfMeasurement) { if (includeUnitOfMeasurement) {
statisticIds = statisticIds.filter((meta) => statisticIds = statisticIds.filter((meta) =>
includeStatisticsUnitOfMeasurement.includes( includeUnitOfMeasurement.includes(meta.unit_of_measurement)
meta.statistics_unit_of_measurement
)
);
}
if (includeDisplayUnitOfMeasurement) {
statisticIds = statisticIds.filter((meta) =>
includeDisplayUnitOfMeasurement.includes(
meta.display_unit_of_measurement
)
); );
} }
@@ -205,8 +184,7 @@ export class HaStatisticPicker extends LitElement {
if (this.hasUpdated) { if (this.hasUpdated) {
(this.comboBox as any).items = this._getStatistics( (this.comboBox as any).items = this._getStatistics(
this.statisticIds!, this.statisticIds!,
this.includeStatisticsUnitOfMeasurement, this.includeUnitOfMeasurement,
this.includeDisplayUnitOfMeasurement,
this.includeDeviceClasses, this.includeDeviceClasses,
this.entitiesOnly this.entitiesOnly
); );
@@ -214,8 +192,7 @@ export class HaStatisticPicker extends LitElement {
this.updateComplete.then(() => { this.updateComplete.then(() => {
(this.comboBox as any).items = this._getStatistics( (this.comboBox as any).items = this._getStatistics(
this.statisticIds!, this.statisticIds!,
this.includeStatisticsUnitOfMeasurement, this.includeUnitOfMeasurement,
this.includeDisplayUnitOfMeasurement,
this.includeDeviceClasses, this.includeDeviceClasses,
this.entitiesOnly this.entitiesOnly
); );

View File

@@ -12,10 +12,8 @@ import { property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined"; import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import { computeActiveState } from "../../common/entity/compute_active_state"; import { computeActiveState } from "../../common/entity/compute_active_state";
import { computeDomain } from "../../common/entity/compute_domain";
import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { iconColorCSS } from "../../common/style/icon_color_css"; import { iconColorCSS } from "../../common/style/icon_color_css";
import { cameraUrlWithWidthHeight } from "../../data/camera";
import type { HomeAssistant } from "../../types"; import type { HomeAssistant } from "../../types";
import "../ha-state-icon"; import "../ha-state-icon";
@@ -95,9 +93,6 @@ export class StateBadge extends LitElement {
if (this.hass) { if (this.hass) {
imageUrl = this.hass.hassUrl(imageUrl); imageUrl = this.hass.hassUrl(imageUrl);
} }
if (computeDomain(stateObj.entity_id) === "camera") {
imageUrl = cameraUrlWithWidthHeight(imageUrl, 80, 80);
}
hostStyle.backgroundImage = `url(${imageUrl})`; hostStyle.backgroundImage = `url(${imageUrl})`;
this._showIcon = false; this._showIcon = false;
} else if (stateObj.state === "on") { } else if (stateObj.state === "on") {

View File

@@ -1,10 +1,11 @@
import { html, LitElement, TemplateResult } from "lit"; import { html, LitElement, TemplateResult } from "lit";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { isComponentLoaded } from "../common/config/is_component_loaded"; import { isComponentLoaded } from "../common/config/is_component_loaded";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { stringCompare } from "../common/string/compare"; import { stringCompare } from "../common/string/compare";
import { fetchHassioAddonsInfo, HassioAddonInfo } from "../data/hassio/addon"; import { HassioAddonInfo } from "../data/hassio/addon";
import { fetchHassioSupervisorInfo } from "../data/hassio/supervisor";
import { showAlertDialog } from "../dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
import { PolymerChangedEvent } from "../polymer-types"; import { PolymerChangedEvent } from "../polymer-types";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
@@ -77,27 +78,27 @@ class HaAddonPicker extends LitElement {
private async _getAddons() { private async _getAddons() {
try { try {
if (isComponentLoaded(this.hass, "hassio")) { if (isComponentLoaded(this.hass, "hassio")) {
const addonsInfo = await fetchHassioAddonsInfo(this.hass); const supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
this._addons = addonsInfo.addons this._addons = supervisorInfo.addons.sort((a, b) =>
.filter((addon) => addon.version) stringCompare(a.name, b.name)
.sort((a, b) => stringCompare(a.name, b.name)); );
} else { } else {
showAlertDialog(this, { showAlertDialog(this, {
title: this.hass.localize( title: this.hass.localize(
"ui.components.addon-picker.error.no_supervisor.title" "ui.componencts.addon-picker.error.no_supervisor.title"
), ),
text: this.hass.localize( text: this.hass.localize(
"ui.components.addon-picker.error.no_supervisor.description" "ui.componencts.addon-picker.error.no_supervisor.description"
), ),
}); });
} }
} catch (err: any) { } catch (err: any) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.hass.localize( title: this.hass.localize(
"ui.components.addon-picker.error.fetch_addons.title" "ui.componencts.addon-picker.error.fetch_addons.title"
), ),
text: this.hass.localize( text: this.hass.localize(
"ui.components.addon-picker.error.fetch_addons.description" "ui.componencts.addon-picker.error.fetch_addons.description"
), ),
}); });
} }

View File

@@ -83,6 +83,7 @@ class HaAlert extends LitElement {
position: relative; position: relative;
padding: 8px; padding: 8px;
display: flex; display: flex;
margin: 4px 0;
} }
.issue-type.rtl { .issue-type.rtl {
flex-direction: row-reverse; flex-direction: row-reverse;

View File

@@ -1,6 +1,6 @@
import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";

View File

@@ -76,7 +76,6 @@ class HaAttributes extends LitElement {
css` css`
.attribute-container { .attribute-container {
margin-bottom: 8px; margin-bottom: 8px;
direction: ltr;
} }
.data-entry { .data-entry {
display: flex; display: flex;

View File

@@ -1,11 +1,10 @@
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { css, html, LitElement, TemplateResult } from "lit"; import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation"; import { stopPropagation } from "../common/dom/stop_propagation";
import "./ha-select"; import "./ha-select";
import { HaTextField } from "./ha-textfield"; import "./ha-textfield";
import "./ha-input-helper-text"; import "./ha-input-helper-text";
export interface TimeChangedEvent { export interface TimeChangedEvent {
@@ -37,7 +36,7 @@ export class HaBaseTimeInput extends LitElement {
/** /**
* determines if inputs are required * determines if inputs are required
*/ */
@property({ type: Boolean }) public required = false; @property({ type: Boolean }) public required?: boolean;
/** /**
* 12 or 24 hr format * 12 or 24 hr format
@@ -124,6 +123,11 @@ export class HaBaseTimeInput extends LitElement {
*/ */
@property() amPm: "AM" | "PM" = "AM"; @property() amPm: "AM" | "PM" = "AM";
/**
* Formatted time string
*/
@property() value?: string;
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
${this.label ${this.label
@@ -136,11 +140,11 @@ export class HaBaseTimeInput extends LitElement {
id="day" id="day"
type="number" type="number"
inputmode="numeric" inputmode="numeric"
.value=${this.days.toFixed()} .value=${this.days}
.label=${this.dayLabel} .label=${this.dayLabel}
name="days" name="days"
@input=${this._valueChanged} @input=${this._valueChanged}
@focusin=${this._onFocus} @focus=${this._onFocus}
no-spinner no-spinner
.required=${this.required} .required=${this.required}
.autoValidate=${this.autoValidate} .autoValidate=${this.autoValidate}
@@ -157,16 +161,16 @@ export class HaBaseTimeInput extends LitElement {
id="hour" id="hour"
type="number" type="number"
inputmode="numeric" inputmode="numeric"
.value=${this.hours.toFixed()} .value=${this.hours}
.label=${this.hourLabel} .label=${this.hourLabel}
name="hours" name="hours"
@input=${this._valueChanged} @input=${this._valueChanged}
@focusin=${this._onFocus} @focus=${this._onFocus}
no-spinner no-spinner
.required=${this.required} .required=${this.required}
.autoValidate=${this.autoValidate} .autoValidate=${this.autoValidate}
maxlength="2" maxlength="2"
max=${ifDefined(this._hourMax)} .max=${this._hourMax}
min="0" min="0"
.disabled=${this.disabled} .disabled=${this.disabled}
suffix=":" suffix=":"
@@ -180,7 +184,7 @@ export class HaBaseTimeInput extends LitElement {
.value=${this._formatValue(this.minutes)} .value=${this._formatValue(this.minutes)}
.label=${this.minLabel} .label=${this.minLabel}
@input=${this._valueChanged} @input=${this._valueChanged}
@focusin=${this._onFocus} @focus=${this._onFocus}
name="minutes" name="minutes"
no-spinner no-spinner
.required=${this.required} .required=${this.required}
@@ -201,7 +205,7 @@ export class HaBaseTimeInput extends LitElement {
.value=${this._formatValue(this.seconds)} .value=${this._formatValue(this.seconds)}
.label=${this.secLabel} .label=${this.secLabel}
@input=${this._valueChanged} @input=${this._valueChanged}
@focusin=${this._onFocus} @focus=${this._onFocus}
name="seconds" name="seconds"
no-spinner no-spinner
.required=${this.required} .required=${this.required}
@@ -222,7 +226,7 @@ export class HaBaseTimeInput extends LitElement {
.value=${this._formatValue(this.milliseconds, 3)} .value=${this._formatValue(this.milliseconds, 3)}
.label=${this.millisecLabel} .label=${this.millisecLabel}
@input=${this._valueChanged} @input=${this._valueChanged}
@focusin=${this._onFocus} @focus=${this._onFocus}
name="milliseconds" name="milliseconds"
no-spinner no-spinner
.required=${this.required} .required=${this.required}
@@ -256,10 +260,9 @@ export class HaBaseTimeInput extends LitElement {
`; `;
} }
private _valueChanged(ev: InputEvent) { private _valueChanged(ev) {
const textField = ev.currentTarget as HaTextField; this[ev.target.name] =
this[textField.name] = ev.target.name === "amPm" ? ev.target.value : Number(ev.target.value);
textField.name === "amPm" ? textField.value : Number(textField.value);
const value: TimeChangedEvent = { const value: TimeChangedEvent = {
hours: this.hours, hours: this.hours,
minutes: this.minutes, minutes: this.minutes,
@@ -274,8 +277,8 @@ export class HaBaseTimeInput extends LitElement {
}); });
} }
private _onFocus(ev: FocusEvent) { private _onFocus(ev) {
(ev.currentTarget as HaTextField).select(); ev.target.select();
} }
/** /**
@@ -290,7 +293,7 @@ export class HaBaseTimeInput extends LitElement {
*/ */
private get _hourMax() { private get _hourMax() {
if (this.noHoursLimit) { if (this.noHoursLimit) {
return undefined; return null;
} }
if (this.format === 12) { if (this.format === 12) {
return 12; return 12;

View File

@@ -1,18 +1,13 @@
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import "./ha-select";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation"; import { stopPropagation } from "../common/dom/stop_propagation";
import { stringCompare } from "../common/string/compare"; import { stringCompare } from "../common/string/compare";
import { import { Blueprint, Blueprints, fetchBlueprints } from "../data/blueprint";
Blueprint,
BlueprintDomain,
Blueprints,
fetchBlueprints,
} from "../data/blueprint";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import "./ha-select";
@customElement("ha-blueprint-picker") @customElement("ha-blueprint-picker")
class HaBluePrintPicker extends LitElement { class HaBluePrintPicker extends LitElement {
@@ -22,7 +17,7 @@ class HaBluePrintPicker extends LitElement {
@property() public value = ""; @property() public value = "";
@property() public domain: BlueprintDomain = "automation"; @property() public domain = "automation";
@property() public blueprints?: Blueprints; @property() public blueprints?: Blueprints;
@@ -56,7 +51,7 @@ class HaBluePrintPicker extends LitElement {
return html` return html`
<ha-select <ha-select
.label=${this.label || .label=${this.label ||
this.hass.localize("ui.components.blueprint-picker.select_blueprint")} this.hass.localize("ui.components.blueprint-picker.label")}
fixedMenuPosition fixedMenuPosition
naturalMenuWidth naturalMenuWidth
.value=${this.value} .value=${this.value}
@@ -64,6 +59,11 @@ class HaBluePrintPicker extends LitElement {
@selected=${this._blueprintChanged} @selected=${this._blueprintChanged}
@closed=${stopPropagation} @closed=${stopPropagation}
> >
<mwc-list-item value="">
${this.hass.localize(
"ui.components.blueprint-picker.select_blueprint"
)}
</mwc-list-item>
${this._processedBlueprints(this.blueprints).map( ${this._processedBlueprints(this.blueprints).map(
(blueprint) => html` (blueprint) => html`
<mwc-list-item .value=${blueprint.path}> <mwc-list-item .value=${blueprint.path}>

View File

@@ -67,7 +67,8 @@ export class HaChip extends LitElement {
color: var(--ha-chip-icon-color, var(--ha-chip-text-color)); color: var(--ha-chip-icon-color, var(--ha-chip-text-color));
} }
.mdc-chip.mdc-chip--selected .mdc-chip__checkmark, .mdc-chip.mdc-chip--selected .mdc-chip__checkmark,
.mdc-chip .mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden) { .mdc-chip.no-text
.mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden) {
margin-right: -4px; margin-right: -4px;
margin-inline-start: -4px; margin-inline-start: -4px;
margin-inline-end: 4px; margin-inline-end: 4px;

View File

@@ -1,13 +1,17 @@
import { css, CSSResultGroup, html } from "lit"; import { ListItemBase } from "@material/mwc-list/mwc-list-item-base";
import { styles } from "@material/mwc-list/mwc-list-item.css";
import { css, CSSResult, html } from "lit";
import { customElement, property, query } from "lit/decorators"; import { customElement, property, query } from "lit/decorators";
import { HaListItem } from "./ha-list-item";
@customElement("ha-clickable-list-item") @customElement("ha-clickable-list-item")
export class HaClickableListItem extends HaListItem { export class HaClickableListItem extends ListItemBase {
@property() public href?: string; @property() public href?: string;
@property({ type: Boolean }) public disableHref = false; @property({ type: Boolean }) public disableHref = false;
// property used only in css
@property({ type: Boolean, reflect: true }) public rtl = false;
@property({ type: Boolean, reflect: true }) public openNewTab = false; @property({ type: Boolean, reflect: true }) public openNewTab = false;
@query("a") private _anchor!: HTMLAnchorElement; @query("a") private _anchor!: HTMLAnchorElement;
@@ -35,10 +39,18 @@ export class HaClickableListItem extends HaListItem {
}); });
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
super.styles, styles,
css` css`
:host {
padding-left: 0px;
padding-right: 0px;
}
:host([graphic="avatar"]:not([twoLine])),
:host([graphic="icon"]:not([twoLine])) {
height: 48px;
}
a { a {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -48,6 +60,19 @@ export class HaClickableListItem extends HaListItem {
padding-right: var(--mdc-list-side-padding, 20px); padding-right: var(--mdc-list-side-padding, 20px);
overflow: hidden; overflow: hidden;
} }
span.material-icons:first-of-type {
margin-inline-start: 0px !important;
margin-inline-end: var(
--mdc-list-item-graphic-margin,
16px
) !important;
direction: var(--direction);
}
span.material-icons:last-of-type {
margin-inline-start: auto !important;
margin-inline-end: 0px !important;
direction: var(--direction);
}
`, `,
]; ];
} }

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