diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 78d39cd415..99f9047671 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -26,6 +26,9 @@ "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, "files.trimTrailingWhitespace": true } } diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.md b/.github/ISSUE_TEMPLATE/BUG_REPORT.md index b4b130f671..c42de86e68 100644 --- a/.github/ISSUE_TEMPLATE/BUG_REPORT.md +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.md @@ -74,12 +74,12 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w ``` -## Problem-relevant configuration +## Problem-relevant frontend configuration ```yaml @@ -89,7 +89,7 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w ## Javascript errors shown in your browser console/inspector diff --git a/.github/workflows/netflify.yml b/.github/workflows/netflify.yml new file mode 100644 index 0000000000..47f13fef80 --- /dev/null +++ b/.github/workflows/netflify.yml @@ -0,0 +1,19 @@ +name: Netlify + +on: + schedule: + - cron: "0 0 * * *" + +jobs: + trigger_builds: + name: Trigger netlify build preview + runs-on: "ubuntu-latest" + steps: + - name: Trigger Cast build + run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_CAST_DEV_BUILD_HOOK }} + + - name: Trigger Demo build + run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_DEMO_DEV_BUILD_HOOK }} + + - name: Trigger Gallery build + run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_GALLERY_DEV_BUILD_HOOK }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000000..425d0e3a2f --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,80 @@ +name: Release + +on: + release: + types: + - published + +env: + WHEELS_TAG: 3.7-alpine3.11 + PYTHON_VERSION: 3.7 + NODE_VERSION: 12.1 + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v2 + + - name: Verify version + uses: home-assistant/actions/helpers/verify-version@master + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v2 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Set up Node ${{ env.NODE_VERSION }} + uses: actions/setup-node@v2 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Build and release package + run: | + export TWINE_USERNAME="__token__" + export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}" + + script/release + + wheels-init: + name: Init wheels build + needs: release + runs-on: ubuntu-latest + steps: + - name: Generate requirements.txt + run: | + # Sleep to give pypi time to populate the new version across mirrors + sleep 240 + version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' ) + echo "home-assistant-frontend==$version" > ./requirements.txt + + - name: Upload requirements.txt + uses: actions/upload-artifact@v2 + with: + name: requirements + path: ./requirements.txt + + build-wheels: + name: Build wheels for ${{ matrix.arch }} + needs: wheels-init + runs-on: ubuntu-latest + strategy: + matrix: + arch: ["aarch64", "armhf", "armv7", "amd64", "i386"] + steps: + - name: Download requirements.txt + uses: actions/download-artifact@v2 + with: + name: requirements + + - name: Build wheels + uses: home-assistant/wheels@master + with: + tag: ${{ env.WHEELS_TAG }} + arch: ${{ matrix.arch }} + wheels-host: ${{ secrets.WHEELS_HOST }} + wheels-key: ${{ secrets.WHEELS_KEY }} + wheels-user: wheels + requirements: "requirements.txt" diff --git a/.github/workflows/translations.yaml b/.github/workflows/translations.yaml new file mode 100644 index 0000000000..3aa24b9d80 --- /dev/null +++ b/.github/workflows/translations.yaml @@ -0,0 +1,65 @@ +name: Translations + +on: + schedule: + - cron: "30 0 * * *" + push: + branches: + - dev + paths: + - translations/en.json + +env: + NODE_VERSION: 12 + +jobs: + upload: + name: Upload + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v2 + + - name: Set up Node ${{ env.NODE_VERSION }} + uses: actions/setup-node@v2 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Upload Translations + run: | + export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}" + + ./script/translations_upload_base + + download: + name: Download + needs: upload + if: github.event_name == 'schedule' + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v2 + + - name: Set up Node ${{ env.NODE_VERSION }} + uses: actions/setup-node@v2 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Download Translations + run: | + export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}" + + npm install + ./script/translations_download + + - name: Initialize git + uses: home-assistant/actions/helpers/git-init@master + with: + name: GitHub Action + email: github-action@users.noreply.github.com + + - name: Update translation + run: | + git add translations + git commit -am "Translation update" + git push diff --git a/azure-pipelines-netlify.yml b/azure-pipelines-netlify.yml deleted file mode 100644 index 46a6782f44..0000000000 --- a/azure-pipelines-netlify.yml +++ /dev/null @@ -1,30 +0,0 @@ -# https://dev.azure.com/home-assistant - -trigger: none -pr: none -schedules: - - cron: "0 0 * * *" - displayName: "build preview" - branches: - include: - - dev - always: true -variables: - - group: netlify - -jobs: - -- job: 'Netlify_preview' - pool: - vmImage: 'ubuntu-latest' - steps: - - script: | - # Cast - curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_CAST} - - # Demo - curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_DEMO} - - # Gallery - curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_GALLERY} - displayName: 'Trigger netlify build preview' diff --git a/azure-pipelines-release.yml b/azure-pipelines-release.yml deleted file mode 100644 index e357b7fa99..0000000000 --- a/azure-pipelines-release.yml +++ /dev/null @@ -1,59 +0,0 @@ -# https://dev.azure.com/home-assistant - -trigger: - batch: true - tags: - include: - - "*" -pr: none -variables: - - name: versionWheels - value: '1.10.1-3.7-alpine3.11' - - name: versionNode - value: '12.1' - - group: twine -resources: - repositories: - - repository: azure - type: github - name: 'home-assistant/ci-azure' - endpoint: 'home-assistant' - - -stages: - - stage: "Validate" - jobs: - - template: templates/azp-job-version.yaml@azure - - - stage: "Build" - jobs: - - job: "ReleasePython" - pool: - vmImage: "ubuntu-latest" - steps: - - task: UsePythonVersion@0 - displayName: "Use Python 3.7" - inputs: - versionSpec: "3.7" - - task: NodeTool@0 - displayName: "Use Node $(versionNode)" - inputs: - versionSpec: "$(versionNode)" - - script: pip install twine wheel - displayName: "Install tools" - - script: | - export TWINE_USERNAME="$(twineUser)" - export TWINE_PASSWORD="$(twinePassword)" - - script/release - displayName: "Build and release package" - - stage: "Wheels" - jobs: - - template: templates/azp-job-wheels.yaml@azure - parameters: - builderVersion: '$(versionWheels)' - wheelsRequirement: 'requirement.txt' - preBuild: - - script: | - sleep 240 - echo "home-assistant-frontend==$(Build.SourceBranchName)" > requirement.txt diff --git a/azure-pipelines-translation.yml b/azure-pipelines-translation.yml deleted file mode 100644 index e6072b6e86..0000000000 --- a/azure-pipelines-translation.yml +++ /dev/null @@ -1,70 +0,0 @@ -# https://dev.azure.com/home-assistant - -trigger: - batch: true - branches: - include: - - dev - paths: - include: - - translations/en.json -pr: none -schedules: - - cron: "30 0 * * *" - displayName: "frontend translation update" - branches: - include: - - dev - always: true -variables: -- group: translation -resources: - repositories: - - repository: azure - type: github - name: 'home-assistant/ci-azure' - endpoint: 'home-assistant' - - -jobs: - -- job: 'Upload' - pool: - vmImage: 'ubuntu-latest' - steps: - - task: NodeTool@0 - displayName: 'Use Node 12.x' - inputs: - versionSpec: '12.x' - - script: | - export LOKALISE_TOKEN="$(lokaliseToken)" - export AZURE_BRANCH="$(Build.SourceBranchName)" - - ./script/translations_upload_base - displayName: 'Upload Translation' - -- job: 'Download' - dependsOn: - - 'Upload' - condition: or(eq(variables['Build.Reason'], 'Schedule'), eq(variables['Build.Reason'], 'Manual')) - pool: - vmImage: 'ubuntu-latest' - steps: - - task: NodeTool@0 - displayName: 'Use Node 12.x' - inputs: - versionSpec: '12.x' - - template: templates/azp-step-git-init.yaml@azure - - script: | - export LOKALISE_TOKEN="$(lokaliseToken)" - export AZURE_BRANCH="$(Build.SourceBranchName)" - - npm install - ./script/translations_download - displayName: 'Download Translation' - - script: | - git checkout dev - git add translation - git commit -am "[ci skip] Translation update" - git push - displayName: 'Update translation' diff --git a/build-scripts/webpack.js b/build-scripts/webpack.js index d29521ce35..fb4f572748 100644 --- a/build-scripts/webpack.js +++ b/build-scripts/webpack.js @@ -36,6 +36,7 @@ const createWebpackConfig = ({ const ignorePackages = bundle.ignorePackages({ latestBuild }); return { mode: isProdBuild ? "production" : "development", + target: ["web", latestBuild ? "es2017" : "es5"], devtool: isProdBuild ? "cheap-module-source-map" : "eval-cheap-module-source-map", @@ -131,22 +132,6 @@ const createWebpackConfig = ({ } return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`; }, - environment: { - // The environment supports arrow functions ('() => { ... }'). - arrowFunction: latestBuild, - // The environment supports BigInt as literal (123n). - bigIntLiteral: false, - // The environment supports const and let for variable declarations. - const: latestBuild, - // The environment supports destructuring ('{ a, b } = obj'). - destructuring: latestBuild, - // The environment supports an async import() function to import EcmaScript modules. - dynamicImport: latestBuild, - // The environment supports 'for of' iteration ('for (const x of array) { ... }'). - forOf: latestBuild, - // The environment supports ECMAScript Module syntax to import ECMAScript modules (import ... from '...'). - module: latestBuild, - }, chunkFilename: isProdBuild && !isStatsBuild ? "chunk.[chunkhash].js" diff --git a/cast/src/receiver/layout/hc-demo.ts b/cast/src/receiver/layout/hc-demo.ts index 5852111743..b4ddc41a67 100644 --- a/cast/src/receiver/layout/hc-demo.ts +++ b/cast/src/receiver/layout/hc-demo.ts @@ -1,8 +1,8 @@ import { customElement, html, - property, internalProperty, + property, TemplateResult, } from "lit-element"; import { mockHistory } from "../../../../demo/src/stubs/history"; diff --git a/cast/src/receiver/second-load.ts b/cast/src/receiver/second-load.ts index bb691527f6..e3c0885561 100644 --- a/cast/src/receiver/second-load.ts +++ b/cast/src/receiver/second-load.ts @@ -1,4 +1,4 @@ import "web-animations-js/web-animations-next-lite.min"; -import "../../../src/resources/roboto"; import "../../../src/resources/ha-style"; +import "../../../src/resources/roboto"; import "./layout/hc-lovelace"; diff --git a/demo/src/configs/arsaboo/entities.ts b/demo/src/configs/arsaboo/entities.ts index 4974139e2a..63b4909255 100644 --- a/demo/src/configs/arsaboo/entities.ts +++ b/demo/src/configs/arsaboo/entities.ts @@ -54,6 +54,8 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) => state: "21", attributes: { friendly_name: "Living room temperature", + device_class: "temperature", + unit_of_measurement: "°C", }, }, "sensor.study_temp_rounded": { @@ -61,6 +63,8 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) => state: "23", attributes: { friendly_name: "Study temperature", + device_class: "temperature", + unit_of_measurement: "°C", }, }, "sensor.living_room": { @@ -261,7 +265,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) => entity_id: "light.kitchen_lights", state: "off", attributes: { - friendly_name: "Kitchen lights", + friendly_name: "Kitchen Lights", supported_features: 1, }, }, @@ -484,7 +488,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) => attributes: { min_mireds: 111, max_mireds: 400, - friendly_name: "Garage lights", + friendly_name: "Garage Lights", supported_features: 55, }, }, diff --git a/demo/src/configs/arsaboo/lovelace.ts b/demo/src/configs/arsaboo/lovelace.ts index 79a275c578..f1395dd588 100644 --- a/demo/src/configs/arsaboo/lovelace.ts +++ b/demo/src/configs/arsaboo/lovelace.ts @@ -12,6 +12,7 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({ { type: "entities", title: localize("ui.panel.page-demo.config.arsaboo.labels.lights"), + state_color: true, entities: [ { entity: "light.kitchen_lights", diff --git a/demo/src/configs/jimpower/entities.ts b/demo/src/configs/jimpower/entities.ts index 532f38c9f5..1752a682e9 100644 --- a/demo/src/configs/jimpower/entities.ts +++ b/demo/src/configs/jimpower/entities.ts @@ -653,7 +653,7 @@ export const demoEntitiesJimpower: DemoConfig["entities"] = () => entity_id: "binary_sensor.smoke_sensor_158d0001b8ddc7", state: "off", attributes: { - Density: 0, + density: 0, battery_level: 59, friendly_name: "Downstairs Smoke Detector", device_class: "smoke", @@ -663,7 +663,7 @@ export const demoEntitiesJimpower: DemoConfig["entities"] = () => entity_id: "binary_sensor.smoke_sensor_158d0001b8deba", state: "off", attributes: { - Density: 0, + density: 0, battery_level: 65, friendly_name: "Upstairs Smoke Detector", device_class: "smoke", diff --git a/demo/src/configs/jimpower/lovelace.ts b/demo/src/configs/jimpower/lovelace.ts index 1ff466fced..3f9ba918cb 100644 --- a/demo/src/configs/jimpower/lovelace.ts +++ b/demo/src/configs/jimpower/lovelace.ts @@ -3,49 +3,7 @@ import { DemoConfig } from "../types"; export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({ name: "Kingia Castle", - resources: [ - // { - // url: "/local/custom_ui/dark-sky-weather-card.js?v=4", - // type: "js", - // }, - // { - // url: "/local/custom_ui/mini-media-player-bundle.js?v=0.9.8", - // type: "module", - // }, - // { - // url: "/local/custom_ui/tracker-card.js?v=0.1.5", - // type: "js", - // }, - // { - // url: "/local/custom_ui/surveillance-card.js?v=0.0.1", - // type: "module", - // }, - // { - // url: "/local/custom_ui/mini-graph-card-bundle.js?v=0.1.0", - // type: "module", - // }, - // { - // url: "/local/custom_ui/slider-entity-row.js?v=d6da75", - // type: "js", - // }, - // { - // url: - // "/local/custom_ui/compact-custom-header/compact-custom-header.js?v=0.2.7", - // type: "js", - // }, - // { - // url: "/local/custom_ui/waze-card.js?v=1.1.1", - // type: "js", - // }, - // { - // url: "/local/custom_ui/circle-sensor-card.js?v=1.2.0", - // type: "module", - // }, - // { - // url: "/local/custom_ui/monster-card.js?v=0.2.3", - // type: "js", - // }, - ], + resources: [], views: [ { cards: [ @@ -603,89 +561,6 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({ }, { cards: [ - // { - // style: { - // "background-image": 'url("/assets/jimpower/cardbackK.png")', - // "background-size": "100% 400px", - // "box-shadow": "3px 3px rgba(0,0,0,0.4)", - // "background-repeat": "no-repeat", - // color: "#999999", - // "border-radius": "20px", - // border: "solid 1px rgba(100,100,100,0.3)", - // "background-color": "rgba(50,50,50,0.3)", - // }, - // type: "custom:card-modder", - // card: { - // entity_visibility: "sensor.dark_sky_visibility", - // entity_sun: "sun.sun", - // entity_daily_summary: - // "sensor.bom_gc_forecast_detailed_summary_0", - // entity_temperature: "sensor.bom_temp", - // entity_forecast_high_temp_3: - // "sensor.bom_gc_forecast_max_temp_c_3", - // entity_forecast_high_temp_2: - // "sensor.bom_gc_forecast_max_temp_c_2", - // entity_forecast_high_temp_5: - // "sensor.bom_gc_forecast_max_temp_c_5", - // entity_forecast_high_temp_4: - // "sensor.bom_gc_forecast_max_temp_c_4", - // entity_wind_speed: "sensor.bom_wind_sp", - // entity_forecast_icon_4: "sensor.dark_sky_icon_4", - // entity_forecast_icon_5: "sensor.dark_sky_icon_5", - // entity_forecast_icon_2: "sensor.dark_sky_icon_2", - // entity_forecast_icon_3: "sensor.dark_sky_icon_3", - // entity_forecast_icon_1: "sensor.dark_sky_icon_1", - // entity_forecast_high_temp_1: - // "sensor.bom_gc_forecast_max_temp_c_1", - // entity_wind_bearing: "sensor.bom_wind_bear", - // entity_forecast_low_temp_2: - // "sensor.bom_gc_forecast_min_temp_c_2", - // entity_forecast_low_temp_3: - // "sensor.bom_gc_forecast_min_temp_c_3", - // entity_pressure: "sensor.bom_pres", - // entity_forecast_low_temp_1: - // "sensor.bom_gc_forecast_min_temp_c_1", - // entity_forecast_low_temp_4: - // "sensor.bom_gc_forecast_min_temp_c_4", - // entity_forecast_low_temp_5: - // "sensor.bom_gc_forecast_min_temp_c_5", - // entity_humidity: "sensor.bom_humd", - // type: "custom:dark-sky-weather-card", - // entity_current_conditions: "sensor.dark_sky_icon", - // }, - // }, - // { - // style: { - // "background-image": 'url("/assets/jimpower/home/waze_5.png")', - // "background-size": "100% 400px", - // "box-shadow": "3px 3px rgba(0,0,0,0.4)", - // "background-repeat": "no-repeat", - // "border-radius": "20px", - // border: "solid 1px rgba(100,100,100,0.3)", - // "background-color": "rgba(50,50,50,0.3)", - // }, - // type: "custom:card-modder", - // card: { - // entities: [ - // { - // name: "James", - // zone: "zone.home", - // entity: "sensor.james_to_home", - // }, - // { - // name: "Tina", - // zone: "zone.home", - // entity: "sensor.tina_to_home", - // }, - // { - // name: "Work", - // zone: "zone.powertec", - // entity: "sensor.commute_to_work", - // }, - // ], - // type: "custom:waze-card", - // }, - // }, { style: { "border-radius": "20px", @@ -722,46 +597,8 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({ ], type: "vertical-stack", }, - // { - // cards: [ - // { - // style: { - // "border-radius": "20px", - // color: "#999999", - // "box-shadow": "3px 3px rgba(0,0,0,0.4)", - // border: "solid 1px rgba(100,100,100,0.3)", - // }, - // type: "custom:card-modder", - // card: { - // type: "picture-entity", - // entity: "camera.bom_radar", - // }, - // }, - // // { - // // style: { - // // "background-image": 'url("/assets/jimpower/cardbackK.png")', - // // "background-size": "100% 525px", - // // "box-shadow": "3px 3px rgba(0,0,0,0.4)", - // // "background-repeat": "no-repeat", - // // color: "#999999", - // // "border-radius": "20px", - // // border: "solid 1px rgba(100,100,100,0.3)", - // // "background-color": "rgba(50,50,50,0.3)", - // // }, - // // type: "custom:card-modder", - // // card: { - // // title: null, - // // type: "custom:tracker-card", - // // trackers: [ - // // "sensor.custom_card_tracker", - // // "sensor.custom_component_tracker", - // // ], - // // }, - // // }, - // ], - // type: "vertical-stack", - // }, ], + path: "home", icon: "mdi:castle", name: "Home", background: @@ -881,26 +718,13 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({ card: { image: "/assets/jimpower/security/air_8.jpg", elements: [ - { - image: - "https://www.airvisual.com/assets/aqi/ic-face-1-green.svg", - type: "image", - style: { - width: "80px", - top: "30%", - left: "12%", - transform: "none", - height: "80px", - }, - entity: "sensor.us_air_pollution_level_2", - }, { style: { color: "hsl(120, 41%, 39%)", top: "50%", "font-weight": 600, - "font-size": "20px", - left: "44%", + "font-size": "50px", + left: "30%", }, type: "state-label", entity: "sensor.us_air_pollution_level_2", @@ -920,7 +744,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({ style: { color: "white", top: "80%", - left: "52%", + left: "48%", }, type: "state-icon", entity: "sensor.us_main_pollutant_2", @@ -1411,6 +1235,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({ type: "vertical-stack", }, ], + path: "security", icon: "hass:shield-home", name: "Security", background: diff --git a/demo/src/configs/kernehed/entities.ts b/demo/src/configs/kernehed/entities.ts index d408e8c2b3..53d6b74524 100644 --- a/demo/src/configs/kernehed/entities.ts +++ b/demo/src/configs/kernehed/entities.ts @@ -101,7 +101,12 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => "sensor.zwave_battery_front_door": { entity_id: "sensor.zwave_battery_front_door", state: "63", - attributes: { friendly_name: "Battery", icon: "mdi:battery-60" }, + attributes: { + friendly_name: "Battery", + icon: "mdi:battery-60", + unit_of_measurement: "%", + device_class: "battery", + }, }, "sensor.oskar_devices": { entity_id: "sensor.oskar_devices", @@ -164,7 +169,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => }, "input_select.christmas_pattern": { entity_id: "input_select.christmas_pattern", - state: "None", + state: "Rainbow", attributes: { options: [ "None", @@ -186,7 +191,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => }, "input_select.christmas_palette": { entity_id: "input_select.christmas_palette", - state: "None", + state: "Party", attributes: { options: [ "None", @@ -457,7 +462,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => state: "0.0", attributes: { unit_of_measurement: "kB/s", - friendly_name: "Nedladdning", + friendly_name: "Downloading", icon: "mdi:file-download", }, }, @@ -471,7 +476,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => state: "0.0", attributes: { unit_of_measurement: "kB/s", - friendly_name: "Uppladdning", + friendly_name: "Uploading", icon: "mdi:file-upload", }, }, diff --git a/demo/src/configs/kernehed/lovelace.ts b/demo/src/configs/kernehed/lovelace.ts index e679b711e4..b8550d2b62 100644 --- a/demo/src/configs/kernehed/lovelace.ts +++ b/demo/src/configs/kernehed/lovelace.ts @@ -2,44 +2,7 @@ import { DemoConfig } from "../types"; export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ name: "Hem", - resources: [ - // { - // url: "/local/custom-lovelace/monster-card.js", - // type: "js", - // }, - // { - // url: "/local/custom-lovelace/mini-media-player-bundle.js?v=0.9.8", - // type: "module", - // }, - // { - // url: "/local/custom-lovelace/slideshow-card.js?=1.1.0", - // type: "js", - // }, - // { - // url: "/local/custom-lovelace/fold-entity-row.js?v=3ae2c4", - // type: "js", - // }, - // { - // url: "/local/custom-lovelace/swipe-card/swipe-card.js?v=2.0.0", - // type: "module", - // }, - // { - // url: "/local/custom-lovelace/upcoming-media-card/upcoming-media-card.js", - // type: "js", - // }, - // { - // url: "/local/custom-lovelace/tracker-card.js?v=0.1.5", - // type: "js", - // }, - // { - // url: "/local/custom-lovelace/card-tools.js?v=6ce5d0", - // type: "js", - // }, - // { - // url: "/local/custom-lovelace/krisinfo.js?=0.0.1", - // type: "js", - // }, - ], + resources: [], views: [ { cards: [ @@ -64,7 +27,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ style: { color: "white", top: "93%", - left: "90%", + left: "85%", }, type: "state-label", entity: "sensor.battery_oskar", @@ -87,7 +50,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ { style: { color: "white", - top: "92%", + top: "93%", left: "20%", }, type: "state-label", @@ -96,8 +59,8 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ { style: { color: "white", - top: "92%", - left: "90%", + top: "93%", + left: "85%", }, type: "state-label", entity: "sensor.battery_bella", @@ -105,7 +68,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ { style: { color: "white", - top: "92%", + top: "93%", left: "55%", }, type: "state-label", @@ -131,78 +94,6 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ type: "entities", title: "Lock", }, - // { - // filter: { - // exclude: [ - // { - // state: "not_home", - // }, - // ], - // include: [ - // { - // entity_id: "device_tracker.annasiphone", - // }, - // { - // entity_id: "device_tracker.iphone_2", - // }, - // ], - // }, - // type: "custom:monster-card", - // card: { - // show_header_toggle: false, - // type: "entities", - // title: "G\u00e4ster", - // }, - // show_empty: false, - // }, - // { - // filter: { - // exclude: [ - // { - // state: "Inget", - // }, - // { - // state: "i.u.", - // }, - // ], - // include: [ - // { - // entity_id: "sensor.pollen_al", - // }, - // { - // entity_id: "sensor.pollen_alm", - // }, - // { - // entity_id: "sensor.pollen_salg_vide", - // }, - // { - // entity_id: "sensor.pollen_bjork", - // }, - // { - // entity_id: "sensor.pollen_bok", - // }, - // { - // entity_id: "sensor.pollen_ek", - // }, - // { - // entity_id: "sensor.pollen_grabo", - // }, - // { - // entity_id: "sensor.pollen_gras", - // }, - // { - // entity_id: "sensor.pollen_hassel", - // }, - // ], - // }, - // type: "custom:monster-card", - // card: { - // show_header_toggle: false, - // type: "entities", - // title: "Pollenniv\u00e5er", - // }, - // show_empty: false, - // }, { cards: [ { @@ -226,10 +117,6 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ ], type: "vertical-stack", }, - // { - // url: "https://embed.windy.com/embed2.html", - // type: "iframe", - // }, { entities: [ { @@ -263,6 +150,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ ], type: "glance", show_state: false, + columns: 4, }, { entities: ["sensor.oskar_bluetooth"], @@ -270,32 +158,6 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ type: "entities", title: "Occupancy", }, - // { - // filter: { - // exclude: [ - // { - // state: false, - // }, - // ], - // include: [ - // { - // entity_id: - // "binary_sensor.fibaro_system_unknown_type0c02_id1003_sensor_2", - // }, - // { - // entity_id: - // "binary_sensor.fibaro_system_unknown_type0c02_id1003_sensor_3", - // }, - // ], - // }, - // type: "custom:monster-card", - // card: { - // show_header_toggle: false, - // type: "entities", - // title: "Brandvarnare", - // }, - // show_empty: false, - // }, { type: "weather-forecast", entity: "weather.smhi_vader", @@ -378,41 +240,9 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ "binary_sensor.windows_server", "binary_sensor.teamspeak", "binary_sensor.harmony_hub", - // { - // style: { - // height: "1px", - // width: "85%", - // "margin-left": "auto", - // background: "#62717b", - // "margin-right": "auto", - // }, - // type: "divider", - // }, - // { - // items: ["sensor.uptime_router", "sensor.installerad_routeros"], - // head: { - // entity: "binary_sensor.router", - // }, - // type: "custom:fold-entity-row", - // group_config: { - // icon: "mdi:router", - // }, - // }, - // { - // items: [ - // "sensor.uptime_router_server", - // "sensor.installerad_routeros_server", - // ], - // head: { - // entity: "binary_sensor.router_server", - // }, - // type: "custom:fold-entity-row", - // group_config: { - // icon: "mdi:router", - // }, - // }, ], show_header_toggle: false, + state_color: true, type: "entities", title: "Network", }, @@ -422,29 +252,10 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ "binary_sensor.ubiquiti_switch", "binary_sensor.ubiquiti_nvr", "binary_sensor.entre_kamera", - // { - // items: ["sensor.uptime_ap_1"], - // head: { - // entity: "binary_sensor.accesspunkt_1", - // }, - // type: "custom:fold-entity-row", - // group_config: { - // icon: "router-wireless", - // }, - // }, - // { - // items: ["sensor.uptime_ap_2"], - // head: { - // entity: "binary_sensor.accesspunkt_2", - // }, - // type: "custom:fold-entity-row", - // group_config: { - // icon: "router-wireless", - // }, - // }, "sensor.total_clients_wireless", ], show_header_toggle: false, + state_color: true, type: "entities", title: "Ubiquiti", }, diff --git a/demo/src/configs/teachingbirds/lovelace.ts b/demo/src/configs/teachingbirds/lovelace.ts index 135dde6a0b..943231353a 100644 --- a/demo/src/configs/teachingbirds/lovelace.ts +++ b/demo/src/configs/teachingbirds/lovelace.ts @@ -215,6 +215,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({ card: { type: "glance", show_state: false, + columns: 4, }, state_filter: ["on"], }, @@ -808,67 +809,6 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({ ], type: "vertical-stack", }, - // { - // cards: [ - // { - // entities: [ - // { - // hide_when_off: true, - // toggle: true, - // type: "custom:slider-entity-row", - // name: "Bedside", - // entity: "light.bedside_lamp", - // }, - // { - // hide_when_off: true, - // toggle: true, - // type: "custom:slider-entity-row", - // name: "Bedroom", - // entity: "light.bedroom_ceiling_light", - // }, - // { - // hide_when_off: true, - // toggle: true, - // type: "custom:slider-entity-row", - // name: "Isa", - // entity: "light.isa_ceiling_light", - // }, - // { - // hide_when_off: true, - // toggle: true, - // type: "custom:slider-entity-row", - // name: "Upstairs hallway", - // entity: "light.upstairs_hallway_ceiling_light_level", - // }, - // { - // hide_when_off: true, - // toggle: true, - // type: "custom:slider-entity-row", - // name: "Nightlight", - // entity: "light.gateway_light_34ce008bfc4b", - // }, - // { - // hide_when_off: true, - // toggle: true, - // type: "custom:slider-entity-row", - // name: "Walk in closet", - // entity: "light.walk_in_closet_lights", - // }, - // { - // hide_when_off: true, - // toggle: false, - // type: "custom:slider-entity-row", - // name: "Stefan", - // entity: "light.stefan_lightstrip", - // }, - // ], - // show_header_toggle: false, - // type: "entities", - // title: "Upstairs", - // }, - // ], - // type: "vertical-stack", - // }, ], path: "lights", title: "Lights", @@ -918,10 +858,6 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({ name: "Dafang", icon: "mdi:webcam", }, - { - name: "IR Hallway", - entity: "sensor.system_ir_blaster", - }, { name: "IR Bedroom", entity: "sensor.system_ir_blaster_bedroom", @@ -940,7 +876,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({ "sensor.system_ring_chime", ], type: "glance", - columns: 5, + columns: 4, show_state: false, }, { diff --git a/demo/src/custom-cards/cast-demo-row.ts b/demo/src/custom-cards/cast-demo-row.ts index 04a0630418..ea0f3033a3 100644 --- a/demo/src/custom-cards/cast-demo-row.ts +++ b/demo/src/custom-cards/cast-demo-row.ts @@ -3,8 +3,8 @@ import { CSSResult, customElement, html, - LitElement, internalProperty, + LitElement, TemplateResult, } from "lit-element"; import { CastManager } from "../../../src/cast/cast_manager"; diff --git a/demo/src/custom-cards/ha-demo-card.ts b/demo/src/custom-cards/ha-demo-card.ts index ad909a4e4f..8da813d7bd 100644 --- a/demo/src/custom-cards/ha-demo-card.ts +++ b/demo/src/custom-cards/ha-demo-card.ts @@ -3,9 +3,9 @@ import { css, CSSResult, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import { until } from "lit-html/directives/until"; diff --git a/demo/src/entrypoint.ts b/demo/src/entrypoint.ts index a42ae3ccb1..e71ff28e88 100644 --- a/demo/src/entrypoint.ts +++ b/demo/src/entrypoint.ts @@ -1,8 +1,8 @@ -import "../../src/resources/safari-14-attachshadow-patch"; import "@polymer/polymer/lib/elements/dom-if"; import "@polymer/polymer/lib/elements/dom-repeat"; import "../../src/resources/ha-style"; import "../../src/resources/roboto"; +import "../../src/resources/safari-14-attachshadow-patch"; import "./ha-demo"; /* polyfill for paper-dropdown */ diff --git a/demo/src/ha-demo.ts b/demo/src/ha-demo.ts index c49d522d65..921420e45b 100644 --- a/demo/src/ha-demo.ts +++ b/demo/src/ha-demo.ts @@ -1,3 +1,4 @@ +// Compat needs to be first import import "../../src/resources/compatibility"; import { isNavigationClick } from "../../src/common/dom/is-navigation-click"; import { navigate } from "../../src/common/navigate"; diff --git a/gallery/src/components/demo-cards.js b/gallery/src/components/demo-cards.js index 9e076371c4..59faa671a6 100644 --- a/gallery/src/components/demo-cards.js +++ b/gallery/src/components/demo-cards.js @@ -2,10 +2,10 @@ import "@polymer/app-layout/app-toolbar/app-toolbar"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; -import "../../../src/components/ha-switch"; -import "../../../src/components/ha-formfield"; -import "./demo-card"; import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element"; +import "../../../src/components/ha-formfield"; +import "../../../src/components/ha-switch"; +import "./demo-card"; class DemoCards extends PolymerElement { static get template() { diff --git a/gallery/src/components/demo-more-info.js b/gallery/src/components/demo-more-info.js index 77e8161f6c..9e2fe30b22 100644 --- a/gallery/src/components/demo-more-info.js +++ b/gallery/src/components/demo-more-info.js @@ -2,37 +2,36 @@ import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; import "../../../src/components/ha-card"; -import "../../../src/state-summary/state-card-content"; import "../../../src/dialogs/more-info/more-info-content"; +import "../../../src/state-summary/state-card-content"; class DemoMoreInfo extends PolymerElement { static get template() { return html` - - +
+
+ + - - - + + +
+ +
`; } diff --git a/gallery/src/components/demo-more-infos.js b/gallery/src/components/demo-more-infos.js index f1de5f0908..26d5fd002f 100644 --- a/gallery/src/components/demo-more-infos.js +++ b/gallery/src/components/demo-more-infos.js @@ -2,6 +2,8 @@ import "@polymer/app-layout/app-toolbar/app-toolbar"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element"; +import "../../../src/components/ha-formfield"; import "../../../src/components/ha-switch"; import "./demo-more-info"; @@ -9,6 +11,10 @@ class DemoMoreInfos extends PolymerElement { static get template() { return html`
- Show entity + + + + + + +
-
- +
+
+ +
`; } @@ -51,6 +68,16 @@ class DemoMoreInfos extends PolymerElement { }, }; } + + _showConfigToggled(ev) { + this._showConfig = ev.target.checked; + } + + _darkThemeToggled(ev) { + applyThemesOnElement(this.$.container, { themes: {} }, "default", { + dark: ev.target.checked, + }); + } } customElements.define("demo-more-infos", DemoMoreInfos); diff --git a/gallery/src/data/plants.ts b/gallery/src/data/plants.ts new file mode 100644 index 0000000000..362afaac78 --- /dev/null +++ b/gallery/src/data/plants.ts @@ -0,0 +1,72 @@ +import { getEntity } from "../../../src/fake_data/entity"; + +export const createPlantEntities = () => [ + getEntity("plant", "lemon_tree", "ok", { + problem: "none", + sensors: { + moisture: "sensor.lemon_tree_moisture", + battery: "sensor.lemon_tree_battery", + temperature: "sensor.lemon_tree_temperature", + conductivity: "sensor.lemon_tree_conductivity", + brightness: "sensor.lemon_tree_brightness", + }, + unit_of_measurement_dict: { + temperature: "°C", + moisture: "%", + brightness: "lx", + battery: "%", + conductivity: "μS/cm", + }, + moisture: 54, + battery: 95, + temperature: 15.6, + conductivity: 1, + brightness: 12, + max_brightness: 20, + friendly_name: "Lemon Tree", + }), + getEntity("plant", "apple_tree", "ok", { + problem: "brightness", + sensors: { + moisture: "sensor.apple_tree_moisture", + battery: "sensor.apple_tree_battery", + temperature: "sensor.apple_tree_temperature", + conductivity: "sensor.apple_tree_conductivity", + brightness: "sensor.apple_tree_brightness", + }, + unit_of_measurement_dict: { + temperature: "°C", + moisture: "%", + brightness: "lx", + battery: "%", + conductivity: "μS/cm", + }, + moisture: 54, + battery: 2, + temperature: 15.6, + conductivity: 1, + brightness: 25, + max_brightness: 20, + friendly_name: "Apple Tree", + }), + getEntity("plant", "sunflowers", "ok", { + problem: "moisture, temperature, conductivity", + sensors: { + moisture: "sensor.sunflowers_moisture", + temperature: "sensor.sunflowers_temperature", + conductivity: "sensor.sunflowers_conductivity", + brightness: "sensor.sunflowers_brightness", + }, + unit_of_measurement_dict: { + temperature: "°C", + moisture: "%", + brightness: "lx", + conductivity: "μS/cm", + }, + moisture: 54, + temperature: 15.6, + conductivity: 1, + brightness: 25, + entity_picture: "/images/sunflowers.jpg", + }), +]; diff --git a/gallery/src/demos/demo-hui-alarm-panel-card.ts b/gallery/src/demos/demo-hui-alarm-panel-card.ts index efeacf5f19..4c66b22559 100644 --- a/gallery/src/demos/demo-hui-alarm-panel-card.ts +++ b/gallery/src/demos/demo-hui-alarm-panel-card.ts @@ -1,6 +1,11 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import { getEntity } from "../../../src/fake_data/entity"; import { provideHass } from "../../../src/fake_data/provide_hass"; import "../components/demo-cards"; @@ -71,28 +76,19 @@ const CONFIGS = [ }, ]; -class DemoAlarmPanelEntity extends PolymerElement { - static get template() { - return html` `; +@customElement("demo-hui-alarm-panel-card") +class DemoAlarmPanelEntity extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; } - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; - } - - public ready() { - super.ready(); - this._setupDemo(); - } - - private async _setupDemo() { - const hass = provideHass(this.$.demos); - await hass.updateTranslations(null, "en"); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); + hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); } } diff --git a/gallery/src/demos/demo-hui-conditional-card.ts b/gallery/src/demos/demo-hui-conditional-card.ts index 561ad45376..cf70db64aa 100644 --- a/gallery/src/demos/demo-hui-conditional-card.ts +++ b/gallery/src/demos/demo-hui-conditional-card.ts @@ -1,6 +1,11 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import { getEntity } from "../../../src/fake_data/entity"; import { provideHass } from "../../../src/fake_data/provide_hass"; import "../components/demo-cards"; @@ -53,24 +58,19 @@ const CONFIGS = [ }, ]; -class DemoConditional extends PolymerElement { - static get template() { - return html` `; +@customElement("demo-hui-conditional-card") +class DemoConditional extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; } - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; - } - - public ready() { - super.ready(); - const hass = provideHass(this.$.demos); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); } } diff --git a/gallery/src/demos/demo-hui-entities-card.ts b/gallery/src/demos/demo-hui-entities-card.ts index bb0261e9d5..7511e061b5 100644 --- a/gallery/src/demos/demo-hui-entities-card.ts +++ b/gallery/src/demos/demo-hui-entities-card.ts @@ -1,6 +1,11 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import { getEntity } from "../../../src/fake_data/entity"; import { provideHass } from "../../../src/fake_data/provide_hass"; import "../components/demo-cards"; @@ -217,24 +222,19 @@ const CONFIGS = [ }, ]; -class DemoEntities extends PolymerElement { - static get template() { - return html` `; +@customElement("demo-hui-entities-card") +class DemoEntities extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; } - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; - } - - public ready() { - super.ready(); - const hass = provideHass(this.$.demos); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); } } diff --git a/gallery/src/demos/demo-hui-entity-button-card.ts b/gallery/src/demos/demo-hui-entity-button-card.ts index 5c9430bf64..1e3201fa10 100644 --- a/gallery/src/demos/demo-hui-entity-button-card.ts +++ b/gallery/src/demos/demo-hui-entity-button-card.ts @@ -1,6 +1,11 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import { getEntity } from "../../../src/fake_data/entity"; import { provideHass } from "../../../src/fake_data/provide_hass"; import "../components/demo-cards"; @@ -48,7 +53,7 @@ const CONFIGS = [ config: ` - type: button entity: light.bed_light - tap_action: + tap_action: action: toggle `, }, @@ -69,24 +74,19 @@ const CONFIGS = [ }, ]; -class DemoButtonEntity extends PolymerElement { - static get template() { - return html` `; +@customElement("demo-hui-entity-button-card") +class DemoButtonEntity extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; } - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; - } - - public ready() { - super.ready(); - const hass = provideHass(this.$.demos); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); } } diff --git a/gallery/src/demos/demo-hui-entity-filter-card.ts b/gallery/src/demos/demo-hui-entity-filter-card.ts index 66a0c08b70..c3e22f0e46 100644 --- a/gallery/src/demos/demo-hui-entity-filter-card.ts +++ b/gallery/src/demos/demo-hui-entity-filter-card.ts @@ -1,6 +1,11 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import { getEntity } from "../../../src/fake_data/entity"; import { provideHass } from "../../../src/fake_data/provide_hass"; import "../components/demo-cards"; @@ -43,7 +48,7 @@ const ENTITIES = [ const CONFIGS = [ { - heading: "Controller", + heading: "Unfiltered controller", config: ` - type: entities entities: @@ -53,7 +58,7 @@ const CONFIGS = [ `, }, { - heading: "Basic", + heading: "Filtered entities card", config: ` - type: entity-filter entities: @@ -69,7 +74,27 @@ const CONFIGS = [ `, }, { - heading: "With card config", + heading: 'With "entities" card config', + config: ` +- type: entity-filter + entities: + - device_tracker.demo_anne_therese + - device_tracker.demo_home_boy + - device_tracker.demo_paulus + - light.bed_light + - light.ceiling_lights + - light.kitchen_lights + state_filter: + - "on" + - not_home + card: + type: entities + title: Custom Title + show_header_toggle: false + `, + }, + { + heading: 'With "glance" card config', config: ` - type: entity-filter entities: @@ -84,31 +109,27 @@ const CONFIGS = [ - not_home card: type: glance - show_state: false + show_state: true + title: Custom Title `, }, ]; -class DemoFilter extends PolymerElement { - static get template() { - return html` `; +@customElement("demo-hui-entity-filter-card") +class DemoEntityFilter extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; } - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; - } - - public ready() { - super.ready(); - const hass = provideHass(this.$.demos); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); } } -customElements.define("demo-hui-entity-filter-card", DemoFilter); +customElements.define("demo-hui-entity-filter-card", DemoEntityFilter); diff --git a/gallery/src/demos/demo-hui-gauge-card.ts b/gallery/src/demos/demo-hui-gauge-card.ts index 1704d31f6e..35e794f317 100644 --- a/gallery/src/demos/demo-hui-gauge-card.ts +++ b/gallery/src/demos/demo-hui-gauge-card.ts @@ -1,6 +1,11 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import { getEntity } from "../../../src/fake_data/entity"; import { provideHass } from "../../../src/fake_data/provide_hass"; import "../components/demo-cards"; @@ -107,24 +112,19 @@ const CONFIGS = [ }, ]; -class DemoGaugeEntity extends PolymerElement { - static get template() { - return html` `; +@customElement("demo-hui-gauge-card") +class DemoGaugeEntity extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; } - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; - } - - public ready() { - super.ready(); - const hass = provideHass(this.$.demos); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); } } diff --git a/gallery/src/demos/demo-hui-glance-card.ts b/gallery/src/demos/demo-hui-glance-card.ts index 5455232a3d..2d3d83193e 100644 --- a/gallery/src/demos/demo-hui-glance-card.ts +++ b/gallery/src/demos/demo-hui-glance-card.ts @@ -1,6 +1,11 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import { getEntity } from "../../../src/fake_data/entity"; import { provideHass } from "../../../src/fake_data/provide_hass"; import "../components/demo-cards"; @@ -77,7 +82,8 @@ const CONFIGS = [ heading: "With title", config: ` - type: glance - title: This is glance + title: Custom title + columns: 4 entities: - device_tracker.demo_paulus - media_player.living_room @@ -104,9 +110,10 @@ const CONFIGS = [ `, }, { - heading: "No name", + heading: "No entity names", config: ` - type: glance + columns: 4 show_name: false entities: - device_tracker.demo_paulus @@ -119,9 +126,10 @@ const CONFIGS = [ `, }, { - heading: "No state", + heading: "No state labels", config: ` - type: glance + columns: 4 show_state: false entities: - device_tracker.demo_paulus @@ -134,9 +142,10 @@ const CONFIGS = [ `, }, { - heading: "No name and no state", + heading: "No names and no state labels", config: ` - type: glance + columns: 4 show_name: false show_state: false entities: @@ -150,47 +159,24 @@ const CONFIGS = [ `, }, { - heading: "Custom name, custom icon", + heading: "Custom name + custom icon", config: ` - type: glance + columns: 4 entities: - entity: device_tracker.demo_paulus name: ¯\\_(ツ)_/¯ icon: mdi:home-assistant - - media_player.living_room - - sun.sun - - cover.kitchen_window - - entity: light.kitchen_lights - icon: mdi:alarm-light - - lock.kitchen_door - - light.ceiling_lights - `, - }, - { - heading: "Custom tap action", - config: ` -- type: glance - entities: - - entity: lock.kitchen_door - tap_action: - type: toggle - - entity: light.ceiling_lights - tap_action: - action: call-service - service: light.turn_on - service_data: - entity_id: light.ceiling_lights - - device_tracker.demo_paulus - - media_player.living_room - - sun.sun - - cover.kitchen_window - - light.kitchen_lights + - entity: media_player.living_room + name: ¯\\_(ツ)_/¯ + icon: mdi:home-assistant `, }, { heading: "Selectively hidden name", config: ` - type: glance + columns: 4 entities: - device_tracker.demo_paulus - entity: media_player.living_room @@ -199,45 +185,51 @@ const CONFIGS = [ - entity: cover.kitchen_window name: - light.kitchen_lights + - entity: lock.kitchen_door + name: + - light.ceiling_lights `, }, { - heading: "Primary theme", + heading: "Custom tap action", config: ` - type: glance - theming: primary + columns: 4 entities: - - device_tracker.demo_paulus - - media_player.living_room - - sun.sun - - cover.kitchen_window - - light.kitchen_lights - - lock.kitchen_door - - light.ceiling_lights + - entity: lock.kitchen_door + name: Custom + tap_action: + type: toggle + - entity: light.ceiling_lights + name: Custom + tap_action: + action: call-service + service: light.turn_on + service_data: + entity_id: light.ceiling_lights + - entity: sun.sun + name: Regular + - entity: light.kitchen_lights + name: Regular `, }, ]; -class DemoPicEntity extends PolymerElement { - static get template() { - return html` `; +@customElement("demo-hui-glance-card") +class DemoGlanceEntity extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; } - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; - } - - public ready() { - super.ready(); - const hass = provideHass(this.$.demos); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); } } -customElements.define("demo-hui-glance-card", DemoPicEntity); +customElements.define("demo-hui-glance-card", DemoGlanceEntity); diff --git a/gallery/src/demos/demo-hui-iframe-card.ts b/gallery/src/demos/demo-hui-iframe-card.ts index 596f6d302f..a2d493476b 100644 --- a/gallery/src/demos/demo-hui-iframe-card.ts +++ b/gallery/src/demos/demo-hui-iframe-card.ts @@ -1,6 +1,4 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { customElement, html, LitElement, TemplateResult } from "lit-element"; import "../components/demo-cards"; const CONFIGS = [ @@ -37,18 +35,10 @@ const CONFIGS = [ }, ]; -class DemoIframe extends PolymerElement { - static get template() { - return html` `; - } - - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; +@customElement("demo-hui-iframe-card") +class DemoIframe extends LitElement { + protected render(): TemplateResult { + return html``; } } diff --git a/gallery/src/demos/demo-hui-light-card.ts b/gallery/src/demos/demo-hui-light-card.ts index 97808f930a..71d12b4af8 100644 --- a/gallery/src/demos/demo-hui-light-card.ts +++ b/gallery/src/demos/demo-hui-light-card.ts @@ -1,6 +1,11 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import { getEntity } from "../../../src/fake_data/entity"; import { provideHass } from "../../../src/fake_data/provide_hass"; import "../components/demo-cards"; @@ -63,24 +68,19 @@ const CONFIGS = [ }, ]; -class DemoLightEntity extends PolymerElement { - static get template() { - return html` `; +@customElement("demo-hui-light-card") +class DemoLightEntity extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; } - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; - } - - public ready() { - super.ready(); - const hass = provideHass(this.$.demos); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); } } diff --git a/gallery/src/demos/demo-hui-map-card.ts b/gallery/src/demos/demo-hui-map-card.ts index 392826fb93..2cc55f499d 100644 --- a/gallery/src/demos/demo-hui-map-card.ts +++ b/gallery/src/demos/demo-hui-map-card.ts @@ -1,6 +1,11 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import { getEntity } from "../../../src/fake_data/entity"; import { provideHass } from "../../../src/fake_data/provide_hass"; import "../components/demo-cards"; @@ -161,24 +166,19 @@ const CONFIGS = [ }, ]; -class DemoMap extends PolymerElement { - static get template() { - return html` `; +@customElement("demo-hui-map-card") +class DemoMap extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; } - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; - } - - public ready() { - super.ready(); - const hass = provideHass(this.$.demos); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); } } diff --git a/gallery/src/demos/demo-hui-markdown-card.ts b/gallery/src/demos/demo-hui-markdown-card.ts index 1ae6b5f0f7..8321eff7d3 100644 --- a/gallery/src/demos/demo-hui-markdown-card.ts +++ b/gallery/src/demos/demo-hui-markdown-card.ts @@ -1,6 +1,11 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import { mockTemplate } from "../../../demo/src/stubs/template"; import { provideHass } from "../../../src/fake_data/provide_hass"; import "../components/demo-cards"; @@ -254,23 +259,19 @@ const CONFIGS = [ }, ]; -class DemoMarkdown extends PolymerElement { - static get template() { - return html` `; +@customElement("demo-hui-markdown-card") +class DemoMarkdown extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; } - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; - } - - public ready() { - super.ready(); - const hass = provideHass(this.$.demos); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); + hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); mockTemplate(hass); } } diff --git a/gallery/src/demos/demo-hui-media-control-card.ts b/gallery/src/demos/demo-hui-media-control-card.ts index 1f12c851bc..55bbc0168c 100644 --- a/gallery/src/demos/demo-hui-media-control-card.ts +++ b/gallery/src/demos/demo-hui-media-control-card.ts @@ -1,6 +1,11 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import { provideHass } from "../../../src/fake_data/provide_hass"; import "../components/demo-cards"; import { createMediaPlayerEntities } from "../data/media_players"; @@ -158,26 +163,21 @@ const CONFIGS = [ }, ]; -class DemoHuiMediControlCard extends PolymerElement { - static get template() { - return html` `; +@customElement("demo-hui-media-control-card") +class DemoHuiMediaControlCard extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; } - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; - } - - public ready() { - super.ready(); - const hass = provideHass(this.$.demos); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); hass.addEntities(createMediaPlayerEntities()); } } -customElements.define("demo-hui-media-control-card", DemoHuiMediControlCard); +customElements.define("demo-hui-media-control-card", DemoHuiMediaControlCard); diff --git a/gallery/src/demos/demo-hui-media-player-rows.ts b/gallery/src/demos/demo-hui-media-player-rows.ts index 147f36985e..9ffabfee9b 100644 --- a/gallery/src/demos/demo-hui-media-player-rows.ts +++ b/gallery/src/demos/demo-hui-media-player-rows.ts @@ -1,6 +1,11 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import { provideHass } from "../../../src/fake_data/provide_hass"; import "../components/demo-cards"; import { createMediaPlayerEntities } from "../data/media_players"; @@ -26,9 +31,9 @@ const CONFIGS = [ - entity: media_player.android_cast name: Screen casting - entity: media_player.image_display - name: Digital Picture Frame + name: Digital Picture Frame - entity: media_player.sonos_idle - name: Sonos Idle + name: Sonos Idle - entity: media_player.idle_browse_media name: Idle waiting for Browse Media - entity: media_player.theater_off @@ -38,7 +43,7 @@ const CONFIGS = [ - entity: media_player.theater_off_static name: Player Off (cannot be switched on) - entity: media_player.theater_on_static - name: Player On (cannot be switched off) + name: Player On (cannot be switched off) - entity: media_player.idle name: Player Idle - entity: media_player.playing @@ -55,26 +60,21 @@ const CONFIGS = [ }, ]; -class DemoHuiMediaPlayerRows extends PolymerElement { - static get template() { - return html` `; +@customElement("demo-hui-media-player-row") +class DemoHuiMediaPlayerRow extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; } - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; - } - - public ready() { - super.ready(); - const hass = provideHass(this.$.demos); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); hass.addEntities(createMediaPlayerEntities()); } } -customElements.define("demo-hui-media-player-rows", DemoHuiMediaPlayerRows); +customElements.define("demo-hui-media-player-row", DemoHuiMediaPlayerRow); diff --git a/gallery/src/demos/demo-hui-picture-elements-card.ts b/gallery/src/demos/demo-hui-picture-elements-card.ts index 6e0ec3360a..6e6ffd86a0 100644 --- a/gallery/src/demos/demo-hui-picture-elements-card.ts +++ b/gallery/src/demos/demo-hui-picture-elements-card.ts @@ -1,6 +1,11 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import { getEntity } from "../../../src/fake_data/entity"; import { provideHass } from "../../../src/fake_data/provide_hass"; import "../components/demo-cards"; @@ -125,26 +130,21 @@ const CONFIGS = [ }, ]; -class DemoPicElements extends PolymerElement { - static get template() { - return html` `; +@customElement("demo-hui-picture-elements-card") +class DemoPictureElements extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; } - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; - } - - public ready() { - super.ready(); - const hass = provideHass(this.$.demos); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); } } -customElements.define("demo-hui-picture-elements-card", DemoPicElements); +customElements.define("demo-hui-picture-elements-card", DemoPictureElements); diff --git a/gallery/src/demos/demo-hui-picture-entity-card.ts b/gallery/src/demos/demo-hui-picture-entity-card.ts index d4df8f31f4..fd0c5fea94 100644 --- a/gallery/src/demos/demo-hui-picture-entity-card.ts +++ b/gallery/src/demos/demo-hui-picture-entity-card.ts @@ -1,6 +1,11 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import { getEntity } from "../../../src/fake_data/entity"; import { provideHass } from "../../../src/fake_data/provide_hass"; import "../components/demo-cards"; @@ -80,26 +85,21 @@ const CONFIGS = [ }, ]; -class DemoPicEntity extends PolymerElement { - static get template() { - return html` `; +@customElement("demo-hui-picture-entity-card") +class DemoPictureEntity extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; } - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; - } - - public ready() { - super.ready(); - const hass = provideHass(this.$.demos); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); } } -customElements.define("demo-hui-picture-entity-card", DemoPicEntity); +customElements.define("demo-hui-picture-entity-card", DemoPictureEntity); diff --git a/gallery/src/demos/demo-hui-picture-glance-card.ts b/gallery/src/demos/demo-hui-picture-glance-card.ts index 684aaa2071..9ecafb7f5a 100644 --- a/gallery/src/demos/demo-hui-picture-glance-card.ts +++ b/gallery/src/demos/demo-hui-picture-glance-card.ts @@ -1,6 +1,11 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import { getEntity } from "../../../src/fake_data/entity"; import { provideHass } from "../../../src/fake_data/provide_hass"; import "../components/demo-cards"; @@ -121,26 +126,21 @@ const CONFIGS = [ }, ]; -class DemoPicGlance extends PolymerElement { - static get template() { - return html` `; +@customElement("demo-hui-picture-glance-card") +class DemoPictureGlance extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; } - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; - } - - public ready() { - super.ready(); - const hass = provideHass(this.$.demos); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); } } -customElements.define("demo-hui-picture-glance-card", DemoPicGlance); +customElements.define("demo-hui-picture-glance-card", DemoPictureGlance); diff --git a/gallery/src/demos/demo-hui-plant-card.ts b/gallery/src/demos/demo-hui-plant-card.ts new file mode 100644 index 0000000000..a1d49afe5b --- /dev/null +++ b/gallery/src/demos/demo-hui-plant-card.ts @@ -0,0 +1,55 @@ +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; +import { provideHass } from "../../../src/fake_data/provide_hass"; +import "../components/demo-cards"; +import { createPlantEntities } from "../data/plants"; + +const CONFIGS = [ + { + heading: "Basic example", + config: ` +- type: plant-status + entity: plant.lemon_tree + `, + }, + { + heading: "Problem (too bright) + low battery", + config: ` +- type: plant-status + entity: plant.apple_tree + `, + }, + { + heading: "With picture + multiple problems", + config: ` +- type: plant-status + entity: plant.sunflowers + name: Sunflowers Name Overwrite + `, + }, +]; + +@customElement("demo-hui-plant-card") +export class DemoPlantEntity extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; + } + + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); + hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); + hass.addEntities(createPlantEntities()); + } +} + +customElements.define("demo-hui-plant-card", DemoPlantEntity); diff --git a/gallery/src/demos/demo-hui-shopping-list-card.ts b/gallery/src/demos/demo-hui-shopping-list-card.ts index 9abb1afc1e..a17bd61f12 100644 --- a/gallery/src/demos/demo-hui-shopping-list-card.ts +++ b/gallery/src/demos/demo-hui-shopping-list-card.ts @@ -1,6 +1,11 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import { provideHass } from "../../../src/fake_data/provide_hass"; import "../components/demo-cards"; @@ -20,24 +25,19 @@ const CONFIGS = [ }, ]; -class DemoShoppingListEntity extends PolymerElement { - static get template() { - return html` `; +@customElement("demo-hui-shopping-list-card") +class DemoShoppingListEntity extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; } - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; - } - - public ready() { - super.ready(); - const hass = provideHass(this.$.demos); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); hass.mockAPI("shopping_list", () => [ { name: "list", id: 1, complete: false }, diff --git a/gallery/src/demos/demo-hui-stack-card.ts b/gallery/src/demos/demo-hui-stack-card.ts index 982eab4f25..26f03c4051 100644 --- a/gallery/src/demos/demo-hui-stack-card.ts +++ b/gallery/src/demos/demo-hui-stack-card.ts @@ -1,6 +1,11 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import { mockHistory } from "../../../demo/src/stubs/history"; import { getEntity } from "../../../src/fake_data/entity"; import { provideHass } from "../../../src/fake_data/provide_hass"; @@ -132,24 +137,19 @@ const CONFIGS = [ }, ]; -class DemoStack extends PolymerElement { - static get template() { - return html` `; +@customElement("demo-hui-stack-card") +class DemoStack extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; } - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; - } - - public ready() { - super.ready(); - const hass = provideHass(this.$.demos); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); mockHistory(hass); } diff --git a/gallery/src/demos/demo-hui-thermostat-card.ts b/gallery/src/demos/demo-hui-thermostat-card.ts index 81b344e689..30e08d3e29 100644 --- a/gallery/src/demos/demo-hui-thermostat-card.ts +++ b/gallery/src/demos/demo-hui-thermostat-card.ts @@ -1,6 +1,11 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import { getEntity } from "../../../src/fake_data/entity"; import { provideHass } from "../../../src/fake_data/provide_hass"; import "../components/demo-cards"; @@ -74,24 +79,19 @@ const CONFIGS = [ }, ]; -class DemoThermostatEntity extends PolymerElement { - static get template() { - return html` `; +@customElement("demo-hui-thermostat-card") +class DemoThermostatEntity extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; } - static get properties() { - return { - _configs: { - type: Object, - value: CONFIGS, - }, - }; - } - - public ready() { - super.ready(); - const hass = provideHass(this.$.demos); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); hass.addEntities(ENTITIES); } } diff --git a/gallery/src/demos/demo-more-info-light.ts b/gallery/src/demos/demo-more-info-light.ts index 70b77560d9..3a05f1becf 100644 --- a/gallery/src/demos/demo-more-info-light.ts +++ b/gallery/src/demos/demo-more-info-light.ts @@ -1,12 +1,29 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { + customElement, + html, + LitElement, + property, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; import "../../../src/components/ha-card"; -import { SUPPORT_BRIGHTNESS } from "../../../src/data/light"; -import { getEntity } from "../../../src/fake_data/entity"; -import { provideHass } from "../../../src/fake_data/provide_hass"; -import "../components/demo-more-infos"; +import { + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + SUPPORT_COLOR_TEMP, + SUPPORT_EFFECT, + SUPPORT_FLASH, + SUPPORT_TRANSITION, + SUPPORT_WHITE_VALUE, +} from "../../../src/data/light"; import "../../../src/dialogs/more-info/more-info-content"; +import { getEntity } from "../../../src/fake_data/entity"; +import { + MockHomeAssistant, + provideHass, +} from "../../../src/fake_data/provide_hass"; +import "../components/demo-more-infos"; const ENTITIES = [ getEntity("light", "bed_light", "on", { @@ -14,38 +31,52 @@ const ENTITIES = [ }), getEntity("light", "kitchen_light", "on", { friendly_name: "Brightness Light", - brightness: 80, + brightness: 200, supported_features: SUPPORT_BRIGHTNESS, }), + getEntity("light", "color_temperature_light", "on", { + friendly_name: "White Color Temperature Light", + brightness: 128, + color_temp: 75, + min_mireds: 30, + max_mireds: 150, + supported_features: SUPPORT_BRIGHTNESS + SUPPORT_COLOR_TEMP, + }), + getEntity("light", "color_effectslight", "on", { + friendly_name: "Color Effets Light", + brightness: 255, + hs_color: [30, 100], + white_value: 36, + supported_features: + SUPPORT_BRIGHTNESS + + SUPPORT_EFFECT + + SUPPORT_FLASH + + SUPPORT_COLOR + + SUPPORT_TRANSITION + + SUPPORT_WHITE_VALUE, + effect_list: ["random", "colorloop"], + }), ]; -class DemoMoreInfoLight extends PolymerElement { - static get template() { +@customElement("demo-more-info-light") +class DemoMoreInfoLight extends LitElement { + @property() public hass!: MockHomeAssistant; + + @query("demo-more-infos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { return html` ent.entityId)} > `; } - static get properties() { - return { - _entities: { - type: Array, - value: ENTITIES.map((ent) => ent.entityId), - }, - }; - } - - public ready() { - super.ready(); - this._setupDemo(); - } - - private async _setupDemo() { - const hass = provideHass(this); - await hass.updateTranslations(null, "en"); + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); + hass.updateTranslations(null, "en"); hass.addEntities(ENTITIES); } } diff --git a/gallery/src/demos/demo-util-long-press.ts b/gallery/src/demos/demo-util-long-press.ts index 518af1df39..9fa774918f 100644 --- a/gallery/src/demos/demo-util-long-press.ts +++ b/gallery/src/demos/demo-util-long-press.ts @@ -1,9 +1,10 @@ import "@material/mwc-button"; -import { html, LitElement, TemplateResult } from "lit-element"; +import { customElement, html, LitElement, TemplateResult } from "lit-element"; import "../../../src/components/ha-card"; import { ActionHandlerEvent } from "../../../src/data/lovelace"; import { actionHandler } from "../../../src/panels/lovelace/common/directives/action-handler-directive"; +@customElement("demo-util-long-press") export class DemoUtilLongPress extends LitElement { protected render(): TemplateResult { return html` @@ -20,7 +21,7 @@ export class DemoUtilLongPress extends LitElement { -
(try pressing and scrolling too!)
+
Try pressing and scrolling too!
` )} @@ -62,5 +63,3 @@ export class DemoUtilLongPress extends LitElement { `; } } - -customElements.define("demo-util-long-press", DemoUtilLongPress); diff --git a/gallery/src/ha-gallery.js b/gallery/src/ha-gallery.js index 779790b632..c1de40203d 100644 --- a/gallery/src/ha-gallery.js +++ b/gallery/src/ha-gallery.js @@ -14,8 +14,6 @@ import "../../src/styles/polymer-ha-style"; // eslint-disable-next-line import/extensions import { DEMOS } from "../build/import-demos"; -const fixPath = (path) => path.substr(2, path.length - 5); - class HaGallery extends PolymerElement { static get template() { return html` diff --git a/hassio/src/addon-store/hassio-addon-store.ts b/hassio/src/addon-store/hassio-addon-store.ts index fc15fc38dc..2e21b19f84 100644 --- a/hassio/src/addon-store/hassio-addon-store.ts +++ b/hassio/src/addon-store/hassio-addon-store.ts @@ -12,6 +12,7 @@ import { } from "lit-element"; import { html, TemplateResult } from "lit-html"; import { atLeastVersion } from "../../../src/common/config/version"; +import { fireEvent } from "../../../src/common/dom/fire_event"; import "../../../src/common/search/search-input"; import "../../../src/components/ha-button-menu"; import "../../../src/components/ha-svg-icon"; @@ -22,6 +23,7 @@ import { reloadHassioAddons, } from "../../../src/data/hassio/addon"; import { extractApiErrorMessage } from "../../../src/data/hassio/common"; +import { fetchHassioSupervisorInfo } from "../../../src/data/hassio/supervisor"; import "../../../src/layouts/hass-loading-screen"; import "../../../src/layouts/hass-tabs-subpage"; import { HomeAssistant, Route } from "../../../src/types"; @@ -190,7 +192,11 @@ class HassioAddonStore extends LitElement { private async _loadData() { try { - const addonsInfo = await fetchHassioAddonsInfo(this.hass); + const [addonsInfo, supervisor] = await Promise.all([ + fetchHassioAddonsInfo(this.hass), + fetchHassioSupervisorInfo(this.hass), + ]); + fireEvent(this, "supervisor-update", { supervisor }); this._repos = addonsInfo.repositories; this._repos.sort(sortRepos); this._addons = addonsInfo.addons; diff --git a/hassio/src/addon-view/config/hassio-addon-audio.ts b/hassio/src/addon-view/config/hassio-addon-audio.ts index 71ef1170d5..e3349b0144 100644 --- a/hassio/src/addon-view/config/hassio-addon-audio.ts +++ b/hassio/src/addon-view/config/hassio-addon-audio.ts @@ -7,13 +7,14 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, TemplateResult, } from "lit-element"; import "web-animations-js/web-animations-next-lite.min"; +import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/ha-card"; import { HassioAddonDetails, @@ -28,7 +29,6 @@ import { haStyle } from "../../../../src/resources/styles"; import { HomeAssistant } from "../../../../src/types"; import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart"; import { hassioStyle } from "../../resources/hassio-style"; -import "../../../../src/components/buttons/ha-progress-button"; @customElement("hassio-addon-audio") class HassioAddonAudio extends LitElement { diff --git a/hassio/src/addon-view/config/hassio-addon-config-tab.ts b/hassio/src/addon-view/config/hassio-addon-config-tab.ts index 29e3f1778e..323d50df73 100644 --- a/hassio/src/addon-view/config/hassio-addon-config-tab.ts +++ b/hassio/src/addon-view/config/hassio-addon-config-tab.ts @@ -7,11 +7,11 @@ import { property, TemplateResult, } from "lit-element"; +import "../../../../src/components/ha-circular-progress"; import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import { haStyle } from "../../../../src/resources/styles"; import { HomeAssistant } from "../../../../src/types"; import { hassioStyle } from "../../resources/hassio-style"; -import "../../../../src/components/ha-circular-progress"; import "./hassio-addon-audio"; import "./hassio-addon-config"; import "./hassio-addon-network"; @@ -26,28 +26,41 @@ class HassioAddonConfigDashboard extends LitElement { if (!this.addon) { return html``; } + const hasOptions = + this.addon.options && Object.keys(this.addon.options).length; + const hasSchema = + this.addon.schema && Object.keys(this.addon.schema).length; + return html`
- - ${this.addon.network + ${hasOptions || hasSchema || this.addon.network || this.addon.audio ? html` - + ${hasOptions || hasSchema + ? html` + + ` + : ""} + ${this.addon.network + ? html` + + ` + : ""} + ${this.addon.audio + ? html` + + ` + : ""} ` - : ""} - ${this.addon.audio - ? html` - - ` - : ""} + : "This add-on does not expose configuration for you to mess with.... 👋"}
`; } diff --git a/hassio/src/addon-view/hassio-addon-dashboard.ts b/hassio/src/addon-view/hassio-addon-dashboard.ts index 9876486d5f..bee5d12166 100644 --- a/hassio/src/addon-view/hassio-addon-dashboard.ts +++ b/hassio/src/addon-view/hassio-addon-dashboard.ts @@ -14,12 +14,12 @@ import { TemplateResult, } from "lit-element"; import memoizeOne from "memoize-one"; +import "../../../src/components/ha-circular-progress"; import { fetchHassioAddonInfo, HassioAddonDetails, } from "../../../src/data/hassio/addon"; import "../../../src/layouts/hass-tabs-subpage"; -import "../../../src/components/ha-circular-progress"; import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage"; import { haStyle } from "../../../src/resources/styles"; import { HomeAssistant, Route } from "../../../src/types"; diff --git a/hassio/src/addon-view/info/hassio-addon-info-tab.ts b/hassio/src/addon-view/info/hassio-addon-info-tab.ts index a2c8fc378b..620ea89f51 100644 --- a/hassio/src/addon-view/info/hassio-addon-info-tab.ts +++ b/hassio/src/addon-view/info/hassio-addon-info-tab.ts @@ -7,8 +7,8 @@ import { property, TemplateResult, } from "lit-element"; -import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import "../../../../src/components/ha-circular-progress"; +import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import { haStyle } from "../../../../src/resources/styles"; import { HomeAssistant } from "../../../../src/types"; import { hassioStyle } from "../../resources/hassio-style"; diff --git a/hassio/src/addon-view/log/hassio-addon-log-tab.ts b/hassio/src/addon-view/log/hassio-addon-log-tab.ts index 8961e61dd9..54bace246b 100644 --- a/hassio/src/addon-view/log/hassio-addon-log-tab.ts +++ b/hassio/src/addon-view/log/hassio-addon-log-tab.ts @@ -7,8 +7,8 @@ import { property, TemplateResult, } from "lit-element"; -import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import "../../../../src/components/ha-circular-progress"; +import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import { haStyle } from "../../../../src/resources/styles"; import { HomeAssistant } from "../../../../src/types"; import { hassioStyle } from "../../resources/hassio-style"; diff --git a/hassio/src/components/supervisor-metric.ts b/hassio/src/components/supervisor-metric.ts new file mode 100644 index 0000000000..b0af0fd9a2 --- /dev/null +++ b/hassio/src/components/supervisor-metric.ts @@ -0,0 +1,87 @@ +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, +} from "lit-element"; +import { classMap } from "lit-html/directives/class-map"; +import "../../../src/components/ha-bar"; +import "../../../src/components/ha-settings-row"; +import { roundWithOneDecimal } from "../../../src/util/calculate"; + +@customElement("supervisor-metric") +class SupervisorMetric extends LitElement { + @property({ type: Number }) public value!: number; + + @property({ type: String }) public description!: string; + + @property({ type: String }) public tooltip?: string; + + protected render(): TemplateResult { + const roundedValue = roundWithOneDecimal(this.value); + return html` + + ${this.description} + +
+ + ${roundedValue}% + + 50, + "target-critical": roundedValue > 85, + })}" + .value=${this.value} + > +
+
`; + } + + static get styles(): CSSResult { + return css` + ha-settings-row { + padding: 0; + height: 54px; + width: 100%; + } + ha-settings-row > div[slot="description"] { + white-space: normal; + color: var(--secondary-text-color); + display: flex; + justify-content: space-between; + } + ha-bar { + --ha-bar-primary-color: var( + --hassio-bar-ok-color, + var(--success-color) + ); + } + .target-warning { + --ha-bar-primary-color: var( + --hassio-bar-warning-color, + var(--warning-color) + ); + } + .target-critical { + --ha-bar-primary-color: var( + --hassio-bar-critical-color, + var(--error-color) + ); + } + .value { + width: 42px; + padding-right: 4px; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "supervisor-metric": SupervisorMetric; + } +} diff --git a/hassio/src/dialogs/markdown/dialog-hassio-markdown.ts b/hassio/src/dialogs/markdown/dialog-hassio-markdown.ts index 6b36d31e5c..95177a18f5 100644 --- a/hassio/src/dialogs/markdown/dialog-hassio-markdown.ts +++ b/hassio/src/dialogs/markdown/dialog-hassio-markdown.ts @@ -3,9 +3,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import { createCloseHeading } from "../../../../src/components/ha-dialog"; diff --git a/hassio/src/entrypoint.ts b/hassio/src/entrypoint.ts index 00c34787e6..24d3e45359 100644 --- a/hassio/src/entrypoint.ts +++ b/hassio/src/entrypoint.ts @@ -1,6 +1,7 @@ +// Compat needs to be first import import "../../src/resources/compatibility"; -import "../../src/resources/safari-14-attachshadow-patch"; import "../../src/resources/roboto"; +import "../../src/resources/safari-14-attachshadow-patch"; import "./hassio-main"; const styleEl = document.createElement("style"); diff --git a/hassio/src/hassio-main.ts b/hassio/src/hassio-main.ts index 2ee909885e..81d736e881 100644 --- a/hassio/src/hassio-main.ts +++ b/hassio/src/hassio-main.ts @@ -1,11 +1,11 @@ -import { html, PropertyValues, customElement, property } from "lit-element"; -import "./hassio-router"; -import { HomeAssistant, Route } from "../../src/types"; -import { HassioPanelInfo } from "../../src/data/hassio/supervisor"; +import { customElement, html, property, PropertyValues } from "lit-element"; +import { atLeastVersion } from "../../src/common/config/version"; import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element"; import { fireEvent } from "../../src/common/dom/fire_event"; +import { HassioPanelInfo } from "../../src/data/hassio/supervisor"; import { makeDialogManager } from "../../src/dialogs/make-dialog-manager"; -import { atLeastVersion } from "../../src/common/config/version"; +import { HomeAssistant, Route } from "../../src/types"; +import "./hassio-router"; import { SupervisorBaseElement } from "./supervisor-base-element"; @customElement("hassio-main") diff --git a/hassio/src/ingress-view/hassio-ingress-view.ts b/hassio/src/ingress-view/hassio-ingress-view.ts index 14962c6f36..531673ac2f 100644 --- a/hassio/src/ingress-view/hassio-ingress-view.ts +++ b/hassio/src/ingress-view/hassio-ingress-view.ts @@ -1,14 +1,17 @@ +import { mdiMenu } from "@mdi/js"; import { css, CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, TemplateResult, } from "lit-element"; +import { fireEvent } from "../../../src/common/dom/fire_event"; +import { navigate } from "../../../src/common/navigate"; import { fetchHassioAddonInfo, HassioAddonDetails, @@ -17,13 +20,10 @@ import { createHassioSession, validateHassioSession, } from "../../../src/data/hassio/ingress"; +import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; import "../../../src/layouts/hass-loading-screen"; import "../../../src/layouts/hass-subpage"; import { HomeAssistant, Route } from "../../../src/types"; -import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; -import { navigate } from "../../../src/common/navigate"; -import { mdiMenu } from "@mdi/js"; -import { fireEvent } from "../../../src/common/dom/fire_event"; @customElement("hassio-ingress-view") class HassioIngressView extends LitElement { diff --git a/hassio/src/snapshots/hassio-snapshots.ts b/hassio/src/snapshots/hassio-snapshots.ts index d156f567a9..4cce8cc0ff 100644 --- a/hassio/src/snapshots/hassio-snapshots.ts +++ b/hassio/src/snapshots/hassio-snapshots.ts @@ -264,7 +264,7 @@ class HassioSnapshots extends LitElement { } protected updated(changedProps: PropertyValues) { - if (changedProps.has("supervisorInfo")) { + if (changedProps.has("supervisor")) { this._addonList = this.supervisor.supervisor.addons .map((addon) => ({ slug: addon.slug, diff --git a/hassio/src/system/hassio-core-info.ts b/hassio/src/system/hassio-core-info.ts new file mode 100644 index 0000000000..96c3aad65f --- /dev/null +++ b/hassio/src/system/hassio-core-info.ts @@ -0,0 +1,246 @@ +import "@material/mwc-button"; +import "@material/mwc-list/mwc-list-item"; +import { + css, + CSSResult, + customElement, + html, + internalProperty, + LitElement, + property, + TemplateResult, +} from "lit-element"; +import "../../../src/components/buttons/ha-progress-button"; +import "../../../src/components/ha-button-menu"; +import "../../../src/components/ha-card"; +import "../../../src/components/ha-settings-row"; +import { + extractApiErrorMessage, + fetchHassioStats, + HassioStats, +} from "../../../src/data/hassio/common"; +import { restartCore, updateCore } from "../../../src/data/supervisor/core"; +import { Supervisor } from "../../../src/data/supervisor/supervisor"; +import { + showAlertDialog, + showConfirmationDialog, +} from "../../../src/dialogs/generic/show-dialog-box"; +import { haStyle } from "../../../src/resources/styles"; +import { HomeAssistant } from "../../../src/types"; +import { bytesToString } from "../../../src/util/bytes-to-string"; +import "../components/supervisor-metric"; +import { hassioStyle } from "../resources/hassio-style"; + +@customElement("hassio-core-info") +class HassioCoreInfo extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public supervisor!: Supervisor; + + @internalProperty() private _metrics?: HassioStats; + + protected render(): TemplateResult | void { + const metrics = [ + { + description: "Core CPU Usage", + value: this._metrics?.cpu_percent, + }, + { + description: "Core RAM Usage", + value: this._metrics?.memory_percent, + tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString( + this._metrics?.memory_limit + )}`, + }, + ]; + + return html` + +
+
+ + + Version + + + core-${this.supervisor.core.version} + + + + + Newest Version + + + core-${this.supervisor.core.version_latest} + + ${this.supervisor.core.update_available + ? html` + + Update + + ` + : ""} + +
+
+ ${metrics.map( + (metric) => + html` + + ` + )} +
+
+
+ + Restart Core + +
+
+ `; + } + + protected firstUpdated(): void { + this._loadData(); + } + + private async _loadData(): Promise { + this._metrics = await fetchHassioStats(this.hass, "core"); + } + + private async _coreRestart(ev: CustomEvent): Promise { + const button = ev.currentTarget as any; + button.progress = true; + + const confirmed = await showConfirmationDialog(this, { + title: "Restart Home Assistant Core", + text: "Are you sure you want to restart Home Assistant Core", + confirmText: "restart", + dismissText: "cancel", + }); + + if (!confirmed) { + button.progress = false; + return; + } + + try { + await restartCore(this.hass); + } catch (err) { + showAlertDialog(this, { + title: "Failed to restart Home Assistant Core", + text: extractApiErrorMessage(err), + }); + } finally { + button.progress = false; + } + } + + private async _coreUpdate(ev: CustomEvent): Promise { + const button = ev.currentTarget as any; + button.progress = true; + + const confirmed = await showConfirmationDialog(this, { + title: "Update Home Assistant Core", + text: `Are you sure you want to update Home Assistant Core to version ${this.supervisor.core.version_latest}?`, + confirmText: "update", + dismissText: "cancel", + }); + + if (!confirmed) { + button.progress = false; + return; + } + + try { + await updateCore(this.hass); + } catch (err) { + showAlertDialog(this, { + title: "Failed to update Home Assistant Core", + text: extractApiErrorMessage(err), + }); + } finally { + button.progress = false; + } + } + + static get styles(): CSSResult[] { + return [ + haStyle, + hassioStyle, + css` + ha-card { + height: 100%; + justify-content: space-between; + flex-direction: column; + display: flex; + } + .card-actions { + height: 48px; + border-top: none; + display: flex; + justify-content: flex-end; + align-items: center; + } + .card-content { + display: flex; + flex-direction: column; + height: calc(100% - 124px); + justify-content: space-between; + } + ha-settings-row { + padding: 0; + height: 54px; + width: 100%; + } + ha-settings-row[three-line] { + height: 74px; + } + ha-settings-row > span[slot="description"] { + white-space: normal; + color: var(--secondary-text-color); + } + + .warning { + --mdc-theme-primary: var(--error-color); + } + + ha-button-menu { + color: var(--secondary-text-color); + --mdc-menu-min-width: 200px; + } + @media (min-width: 563px) { + paper-listbox { + max-height: 150px; + overflow: auto; + } + } + paper-item { + cursor: pointer; + min-height: 35px; + } + mwc-list-item ha-svg-icon { + color: var(--secondary-text-color); + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hassio-core-info": HassioCoreInfo; + } +} diff --git a/hassio/src/system/hassio-host-info.ts b/hassio/src/system/hassio-host-info.ts index 42b066c214..773fcc8bab 100644 --- a/hassio/src/system/hassio-host-info.ts +++ b/hassio/src/system/hassio-host-info.ts @@ -43,6 +43,11 @@ import { } from "../../../src/dialogs/generic/show-dialog-box"; import { haStyle } from "../../../src/resources/styles"; import { HomeAssistant } from "../../../src/types"; +import { + getValueInPercentage, + roundWithOneDecimal, +} from "../../../src/util/calculate"; +import "../components/supervisor-metric"; import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown"; import { showNetworkDialog } from "../dialogs/network/show-dialog-network"; import { hassioStyle } from "../resources/hassio-style"; @@ -57,80 +62,105 @@ class HassioHostInfo extends LitElement { const primaryIpAddress = this.supervisor.host.features.includes("network") ? this._primaryIpAddress(this.supervisor.network!) : ""; - return html` - -
- ${this.supervisor.host.features.includes("hostname") - ? html` - - Hostname - - - ${this.supervisor.host.hostname} - - - - ` - : ""} - ${this.supervisor.host.features.includes("network") - ? html` - - IP Address - - - ${primaryIpAddress} - - - - ` - : ""} - - - Operating System - - - ${this.supervisor.host.operating_system} - - ${this.supervisor.os.update_available - ? html` - +
+
+ ${this.supervisor.host.features.includes("hostname") + ? html` + + Hostname + + + ${this.supervisor.host.hostname} + + - Update - - ` + + ` : ""} - - ${!this.supervisor.host.features.includes("hassos") - ? html` - - Docker version - - - ${this.supervisor.info.docker} - - ` - : ""} - ${this.supervisor.host.deployment - ? html` - - Deployment - - - ${this.supervisor.host.deployment} - - ` - : ""} + ${this.supervisor.host.features.includes("network") + ? html` + + IP Address + + + ${primaryIpAddress} + + + + ` + : ""} + + + + Operating System + + + ${this.supervisor.host.operating_system} + + ${this.supervisor.os.update_available + ? html` + + Update + + ` + : ""} + + ${!this.supervisor.host.features.includes("hassos") + ? html` + + Docker version + + + ${this.supervisor.info.docker} + + ` + : ""} + ${this.supervisor.host.deployment + ? html` + + Deployment + + + ${this.supervisor.host.deployment} + + ` + : ""} +
+
+ ${metrics.map( + (metric) => + html` + + ` + )} +
${this.supervisor.host.features.includes("reboot") @@ -140,7 +170,7 @@ class HassioHostInfo extends LitElement { class="warning" @click=${this._hostReboot} > - Reboot + Reboot Host ` : ""} @@ -151,7 +181,7 @@ class HassioHostInfo extends LitElement { class="warning" @click=${this._hostShutdown} > - Shutdown + Shutdown Host ` : ""} @@ -183,6 +213,10 @@ class HassioHostInfo extends LitElement { this._loadData(); } + private _getUsedSpace = memoizeOne((used: number, total: number) => + roundWithOneDecimal(getValueInPercentage(used, 0, total)) + ); + private _primaryIpAddress = memoizeOne((network_info: NetworkInfo) => { if (!network_info || !network_info.interfaces) { return ""; @@ -369,6 +403,12 @@ class HassioHostInfo extends LitElement { justify-content: space-between; align-items: center; } + .card-content { + display: flex; + flex-direction: column; + height: calc(100% - 124px); + justify-content: space-between; + } ha-settings-row { padding: 0; height: 54px; diff --git a/hassio/src/system/hassio-supervisor-info.ts b/hassio/src/system/hassio-supervisor-info.ts index a64f8e2d73..b29252d2ad 100644 --- a/hassio/src/system/hassio-supervisor-info.ts +++ b/hassio/src/system/hassio-supervisor-info.ts @@ -3,6 +3,7 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, TemplateResult, @@ -12,7 +13,11 @@ import "../../../src/components/buttons/ha-progress-button"; import "../../../src/components/ha-card"; import "../../../src/components/ha-settings-row"; import "../../../src/components/ha-switch"; -import { extractApiErrorMessage } from "../../../src/data/hassio/common"; +import { + extractApiErrorMessage, + fetchHassioStats, + HassioStats, +} from "../../../src/data/hassio/common"; import { fetchHassioSupervisorInfo, reloadSupervisor, @@ -28,7 +33,9 @@ import { } from "../../../src/dialogs/generic/show-dialog-box"; import { haStyle } from "../../../src/resources/styles"; import { HomeAssistant } from "../../../src/types"; +import { bytesToString } from "../../../src/util/bytes-to-string"; import { documentationUrl } from "../../../src/util/documentation-url"; +import "../components/supervisor-metric"; import { hassioStyle } from "../resources/hassio-style"; const UNSUPPORTED_REASON = { @@ -87,127 +94,164 @@ class HassioSupervisorInfo extends LitElement { @property({ attribute: false }) public supervisor!: Supervisor; + @internalProperty() private _metrics?: HassioStats; + protected render(): TemplateResult | void { + const metrics = [ + { + description: "Supervisor CPU Usage", + value: this._metrics?.cpu_percent, + }, + { + description: "Supervisor RAM Usage", + value: this._metrics?.memory_percent, + tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString( + this._metrics?.memory_limit + )}`, + }, + ]; return html`
- - - Version - - - ${this.supervisor.supervisor.version} - - - - - Newest Version - - - ${this.supervisor.supervisor.version_latest} - - ${this.supervisor.supervisor.update_available - ? html` - - Update - - ` - : ""} - - - - Channel - - - ${this.supervisor.supervisor.channel} - - ${this.supervisor.supervisor.channel === "beta" - ? html` - - Leave beta channel - - ` - : this.supervisor.supervisor.channel === "stable" - ? html` - - Join beta channel - - ` - : ""} - +
+ + + Version + + + supervisor-${this.supervisor.supervisor.version} + + + + + Newest Version + + + supervisor-${this.supervisor.supervisor.version_latest} + + ${this.supervisor.supervisor.update_available + ? html` + + Update + + ` + : ""} + + + + Channel + + + ${this.supervisor.supervisor.channel} + + ${this.supervisor.supervisor.channel === "beta" + ? html` + + Leave beta channel + + ` + : this.supervisor.supervisor.channel === "stable" + ? html` + + Join beta channel + + ` + : ""} + - ${this.supervisor.supervisor.supported - ? html` - - Share Diagnostics - -
- Share crash reports and diagnostic information. + ${this.supervisor.supervisor.supported + ? html` + + Share Diagnostics + +
+ Share crash reports and diagnostic information. + +
+ +
` + : html`
+ You are running an unsupported installation. -
- - ` - : html`
- You are running an unsupported installation. - -
`} - ${!this.supervisor.supervisor.healthy - ? html`
- Your installation is running in an unhealthy state. - -
` - : ""} +
`} + ${!this.supervisor.supervisor.healthy + ? html`
+ Your installation is running in an unhealthy state. + +
` + : ""} +
+
+ ${metrics.map( + (metric) => + html` + + ` + )} +
- Reload + Reload Supervisor - Restart + Restart Supervisor
`; } + protected firstUpdated(): void { + this._loadData(); + } + + private async _loadData(): Promise { + this._metrics = await fetchHassioStats(this.hass, "supervisor"); + } + private async _toggleBeta(ev: CustomEvent): Promise { const button = ev.currentTarget as any; button.progress = true; @@ -282,6 +326,18 @@ class HassioSupervisorInfo extends LitElement { const button = ev.currentTarget as any; button.progress = true; + const confirmed = await showConfirmationDialog(this, { + title: "Restart the Supervisor", + text: "Are you sure you want to restart the Supervisor", + confirmText: "restart", + dismissText: "cancel", + }); + + if (!confirmed) { + button.progress = false; + return; + } + try { await restartSupervisor(this.hass); } catch (err) { @@ -426,6 +482,15 @@ class HassioSupervisorInfo extends LitElement { justify-content: space-between; align-items: center; } + .card-content { + display: flex; + flex-direction: column; + height: calc(100% - 124px); + justify-content: space-between; + } + .metrics-block { + margin-top: 16px; + } button.link { color: var(--primary-color); } diff --git a/hassio/src/system/hassio-system-metrics.ts b/hassio/src/system/hassio-system-metrics.ts deleted file mode 100644 index 8cd4450e90..0000000000 --- a/hassio/src/system/hassio-system-metrics.ts +++ /dev/null @@ -1,185 +0,0 @@ -import "@material/mwc-button"; -import "@material/mwc-list/mwc-list-item"; -import { - css, - CSSResult, - customElement, - html, - internalProperty, - LitElement, - property, - TemplateResult, -} from "lit-element"; -import { classMap } from "lit-html/directives/class-map"; -import memoizeOne from "memoize-one"; -import "../../../src/components/buttons/ha-progress-button"; -import "../../../src/components/ha-bar"; -import "../../../src/components/ha-button-menu"; -import "../../../src/components/ha-card"; -import "../../../src/components/ha-settings-row"; -import { fetchHassioStats, HassioStats } from "../../../src/data/hassio/common"; -import { HassioHostInfo } from "../../../src/data/hassio/host"; -import { Supervisor } from "../../../src/data/supervisor/supervisor"; -import { haStyle } from "../../../src/resources/styles"; -import { HomeAssistant } from "../../../src/types"; -import { bytesToString } from "../../../src/util/bytes-to-string"; -import { - getValueInPercentage, - roundWithOneDecimal, -} from "../../../src/util/calculate"; -import { hassioStyle } from "../resources/hassio-style"; - -@customElement("hassio-system-metrics") -class HassioSystemMetrics extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @property({ attribute: false }) public supervisor!: Supervisor; - - @internalProperty() private _supervisorMetrics?: HassioStats; - - @internalProperty() private _coreMetrics?: HassioStats; - - protected render(): TemplateResult | void { - const metrics = [ - { - description: "Core CPU Usage", - value: this._coreMetrics?.cpu_percent, - }, - { - description: "Core RAM Usage", - value: this._coreMetrics?.memory_percent, - tooltip: `${bytesToString( - this._coreMetrics?.memory_usage - )}/${bytesToString(this._coreMetrics?.memory_limit)}`, - }, - { - description: "Supervisor CPU Usage", - value: this._supervisorMetrics?.cpu_percent, - }, - { - description: "Supervisor RAM Usage", - value: this._supervisorMetrics?.memory_percent, - tooltip: `${bytesToString( - this._supervisorMetrics?.memory_usage - )}/${bytesToString(this._supervisorMetrics?.memory_limit)}`, - }, - { - description: "Used Space", - value: this._getUsedSpace(this.supervisor.host), - tooltip: `${this.supervisor.host.disk_used} GB/${this.supervisor.host.disk_total} GB`, - }, - ]; - - return html` - -
- ${metrics.map((metric) => - this._renderMetric( - metric.description, - metric.value ?? 0, - metric.tooltip - ) - )} -
-
- `; - } - - protected firstUpdated(): void { - this._loadData(); - } - - private _renderMetric( - description: string, - value: number, - tooltip?: string - ): TemplateResult { - const roundedValue = roundWithOneDecimal(value); - return html` - - ${description} - -
- - ${roundedValue}% - - 50, - "target-critical": roundedValue > 85, - })}" - .value=${value} - > -
-
`; - } - - private _getUsedSpace = memoizeOne((hostInfo: HassioHostInfo) => - roundWithOneDecimal( - getValueInPercentage(hostInfo.disk_used, 0, hostInfo.disk_total) - ) - ); - - private async _loadData(): Promise { - const [supervisor, core] = await Promise.all([ - fetchHassioStats(this.hass, "supervisor"), - fetchHassioStats(this.hass, "core"), - ]); - this._supervisorMetrics = supervisor; - this._coreMetrics = core; - } - - static get styles(): CSSResult[] { - return [ - haStyle, - hassioStyle, - css` - ha-card { - height: 100%; - justify-content: space-between; - flex-direction: column; - display: flex; - } - ha-settings-row { - padding: 0; - height: 54px; - width: 100%; - } - ha-settings-row > div[slot="description"] { - white-space: normal; - color: var(--secondary-text-color); - display: flex; - justify-content: space-between; - } - ha-bar { - --ha-bar-primary-color: var( - --hassio-bar-ok-color, - var(--success-color) - ); - } - .target-warning { - --ha-bar-primary-color: var( - --hassio-bar-warning-color, - var(--warning-color) - ); - } - .target-critical { - --ha-bar-primary-color: var( - --hassio-bar-critical-color, - var(--error-color) - ); - } - .value { - width: 42px; - padding-right: 4px; - } - `, - ]; - } -} - -declare global { - interface HTMLElementTagNameMap { - "hassio-system-metrics": HassioSystemMetrics; - } -} diff --git a/hassio/src/system/hassio-system.ts b/hassio/src/system/hassio-system.ts index 9c44de311b..fa9999485e 100644 --- a/hassio/src/system/hassio-system.ts +++ b/hassio/src/system/hassio-system.ts @@ -13,10 +13,10 @@ import { haStyle } from "../../../src/resources/styles"; import { HomeAssistant, Route } from "../../../src/types"; import { supervisorTabs } from "../hassio-tabs"; import { hassioStyle } from "../resources/hassio-style"; +import "./hassio-core-info"; import "./hassio-host-info"; import "./hassio-supervisor-info"; import "./hassio-supervisor-log"; -import "./hassio-system-metrics"; @customElement("hassio-system") class HassioSystem extends LitElement { @@ -41,6 +41,10 @@ class HassioSystem extends LitElement { System
+ -
diff --git a/package.json b/package.json index 6349d3864f..f6e9c22437 100644 --- a/package.json +++ b/package.json @@ -29,22 +29,22 @@ "@fullcalendar/daygrid": "5.1.0", "@fullcalendar/interaction": "5.1.0", "@fullcalendar/list": "5.1.0", - "@material/chips": "=8.0.0-canary.774dcfc8e.0", - "@material/mwc-button": "^0.19.0", - "@material/mwc-checkbox": "^0.19.0", - "@material/mwc-circular-progress": "^0.19.0", - "@material/mwc-dialog": "^0.19.0", - "@material/mwc-fab": "^0.19.0", - "@material/mwc-formfield": "^0.19.0", - "@material/mwc-icon-button": "^0.19.0", - "@material/mwc-list": "^0.19.0", - "@material/mwc-menu": "^0.19.0", - "@material/mwc-radio": "^0.19.0", - "@material/mwc-ripple": "^0.19.0", - "@material/mwc-switch": "^0.19.0", - "@material/mwc-tab": "^0.19.0", - "@material/mwc-tab-bar": "^0.19.0", - "@material/top-app-bar": "=8.0.0-canary.774dcfc8e.0", + "@material/chips": "=9.0.0-canary.1c156d69d.0", + "@material/mwc-button": "^0.20.0", + "@material/mwc-checkbox": "^0.20.0", + "@material/mwc-circular-progress": "^0.20.0", + "@material/mwc-dialog": "^0.20.0", + "@material/mwc-fab": "^0.20.0", + "@material/mwc-formfield": "^0.20.0", + "@material/mwc-icon-button": "^0.20.0", + "@material/mwc-list": "^0.20.0", + "@material/mwc-menu": "^0.20.0", + "@material/mwc-radio": "^0.20.0", + "@material/mwc-ripple": "^0.20.0", + "@material/mwc-switch": "^0.20.0", + "@material/mwc-tab": "^0.20.0", + "@material/mwc-tab-bar": "^0.20.0", + "@material/top-app-bar": "=9.0.0-canary.1c156d69d.0", "@mdi/js": "5.6.55", "@mdi/svg": "5.6.55", "@polymer/app-layout": "^3.0.2", @@ -120,7 +120,7 @@ "resize-observer-polyfill": "^1.5.1", "roboto-fontface": "^0.10.0", "sortablejs": "^1.10.2", - "superstruct": "^0.10.12", + "superstruct": "^0.10.13", "tinykeys": "^1.1.1", "unfetch": "^4.1.0", "vis-data": "^7.1.1", diff --git a/setup.py b/setup.py index 3e58b0b9ad..c0915cc7e9 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name="home-assistant-frontend", - version="20201229.1", + version="20210127.0", description="The Home Assistant frontend", url="https://github.com/home-assistant/home-assistant-polymer", author="The Home Assistant Authors", diff --git a/src/auth/ha-auth-flow.ts b/src/auth/ha-auth-flow.ts index a4234c56ca..a4092f0e41 100644 --- a/src/auth/ha-auth-flow.ts +++ b/src/auth/ha-auth-flow.ts @@ -3,9 +3,9 @@ import { css, CSSResult, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, TemplateResult, } from "lit-element"; diff --git a/src/auth/ha-authorize.ts b/src/auth/ha-authorize.ts index 0d5cdd4181..3cd85aa5f2 100644 --- a/src/auth/ha-authorize.ts +++ b/src/auth/ha-authorize.ts @@ -2,21 +2,25 @@ import { css, CSSResult, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, } from "lit-element"; +import punycode from "punycode"; +import { extractSearchParamsObject } from "../common/url/search-params"; import { AuthProvider, - fetchAuthProviders, AuthUrlSearchParams, + fetchAuthProviders, } from "../data/auth"; +import { + DiscoveryInformation, + fetchDiscoveryInformation, +} from "../data/discovery"; import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin"; import { registerServiceWorker } from "../util/register-service-worker"; import "./ha-auth-flow"; -import { extractSearchParamsObject } from "../common/url/search-params"; -import punycode from "punycode"; import("./ha-pick-auth-provider"); @@ -31,6 +35,8 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) { @internalProperty() private _authProviders?: AuthProvider[]; + @internalProperty() private _discovery?: DiscoveryInformation; + constructor() { super(); this.translationFragment = "page-authorize"; @@ -58,14 +64,17 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) { // the name with a bold tag. const loggingInWith = document.createElement("div"); loggingInWith.innerText = this.localize( - "ui.panel.page-authorize.logging_in_with", + this._discovery?.location_name + ? "ui.panel.page-authorize.logging_in_to_with" + : "ui.panel.page-authorize.logging_in_with", + "locationName", + "LOCATION", "authProviderName", "NAME" ); - loggingInWith.innerHTML = loggingInWith.innerHTML.replace( - "**NAME**", - `${this._authProvider!.name}` - ); + loggingInWith.innerHTML = loggingInWith.innerHTML + .replace("**LOCATION**", `${this._discovery?.location_name}`) + .replace("**NAME**", `${this._authProvider!.name}`); const inactiveProviders = this._authProviders.filter( (prv) => prv !== this._authProvider @@ -105,6 +114,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) { protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); this._fetchAuthProviders(); + this._fetchDiscoveryInfo(); if (!this.redirectUri) { return; @@ -126,6 +136,10 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) { } } + private async _fetchDiscoveryInfo() { + this._discovery = await fetchDiscoveryInformation(); + } + private async _fetchAuthProviders() { // Fetch auth providers try { diff --git a/src/common/config/can_show_page.ts b/src/common/config/can_show_page.ts index 0ac4f6d2ec..05e96d43e2 100644 --- a/src/common/config/can_show_page.ts +++ b/src/common/config/can_show_page.ts @@ -1,6 +1,6 @@ -import { isComponentLoaded } from "./is_component_loaded"; import { PageNavigation } from "../../layouts/hass-tabs-subpage"; import { HomeAssistant } from "../../types"; +import { isComponentLoaded } from "./is_component_loaded"; export const canShowPage = (hass: HomeAssistant, page: PageNavigation) => { return ( diff --git a/src/common/const.ts b/src/common/const.ts index 4b4f8e6225..bbf051f668 100644 --- a/src/common/const.ts +++ b/src/common/const.ts @@ -34,6 +34,7 @@ export const FIXED_DOMAIN_ICONS = { light: "hass:lightbulb", mailbox: "hass:mailbox", notify: "hass:comment-alert", + number: "hass:ray-vertex", persistent_notification: "hass:bell", person: "hass:account", plant: "hass:flower", @@ -77,6 +78,7 @@ export const DOMAINS_WITH_CARD = [ "input_text", "lock", "media_player", + "number", "scene", "script", "timer", @@ -114,6 +116,7 @@ export const DOMAINS_HIDE_MORE_INFO = [ "input_number", "input_select", "input_text", + "number", "scene", ]; @@ -138,6 +141,9 @@ export const DOMAINS_TOGGLE = new Set([ "humidifier", ]); +/** Domains that have a dynamic entity image / picture. */ +export const DOMAINS_WITH_DYNAMIC_PICTURE = new Set(["camera", "media_player"]); + /** Temperature units. */ export const UNIT_C = "°C"; export const UNIT_F = "°F"; diff --git a/src/common/datetime/check_valid_date.ts b/src/common/datetime/check_valid_date.ts new file mode 100644 index 0000000000..e380a9b79f --- /dev/null +++ b/src/common/datetime/check_valid_date.ts @@ -0,0 +1,7 @@ +export default function checkValidDate(date?: Date): boolean { + if (!date) { + return false; + } + + return date instanceof Date && !isNaN(date.valueOf()); +} diff --git a/src/common/datetime/format_date.ts b/src/common/datetime/format_date.ts index d8c46d4e8d..050006fe7d 100644 --- a/src/common/datetime/format_date.ts +++ b/src/common/datetime/format_date.ts @@ -9,3 +9,12 @@ export const formatDate = toLocaleDateStringSupportsOptions day: "numeric", }) : (dateObj: Date) => format(dateObj, "longDate"); + +export const formatDateWeekday = toLocaleDateStringSupportsOptions + ? (dateObj: Date, locales: string) => + dateObj.toLocaleDateString(locales, { + weekday: "long", + month: "short", + day: "numeric", + }) + : (dateObj: Date) => format(dateObj, "dddd, MMM D"); diff --git a/src/common/datetime/format_time.ts b/src/common/datetime/format_time.ts index c27874f3a6..6b79f0c173 100644 --- a/src/common/datetime/format_time.ts +++ b/src/common/datetime/format_time.ts @@ -17,3 +17,12 @@ export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions second: "2-digit", }) : (dateObj: Date) => format(dateObj, "mediumTime"); + +export const formatTimeWeekday = toLocaleTimeStringSupportsOptions + ? (dateObj: Date, locales: string) => + dateObj.toLocaleTimeString(locales, { + weekday: "long", + hour: "numeric", + minute: "2-digit", + }) + : (dateObj: Date) => format(dateObj, "dddd, HH:mm"); diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index f946d34015..91b7a606dc 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -3,9 +3,9 @@ import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; import { formatDate } from "../datetime/format_date"; import { formatDateTime } from "../datetime/format_date_time"; import { formatTime } from "../datetime/format_time"; +import { formatNumber } from "../string/format_number"; import { LocalizeFunc } from "../translations/localize"; import { computeStateDomain } from "./compute_state_domain"; -import { formatNumber } from "../string/format_number"; export const computeStateDisplay = ( localize: LocalizeFunc, @@ -63,7 +63,7 @@ export const computeStateDisplay = ( if (domain === "humidifier") { if (compareState === "on" && stateObj.attributes.humidity) { - return `${stateObj.attributes.humidity}%`; + return `${stateObj.attributes.humidity} %`; } } diff --git a/src/common/entity/state_card_type.ts b/src/common/entity/state_card_type.ts index 2013ee2058..a1f2aa561e 100644 --- a/src/common/entity/state_card_type.ts +++ b/src/common/entity/state_card_type.ts @@ -1,9 +1,9 @@ import { HassEntity } from "home-assistant-js-websocket"; +import { UNAVAILABLE } from "../../data/entity"; import { HomeAssistant } from "../../types"; import { DOMAINS_WITH_CARD } from "../const"; import { canToggleState } from "./can_toggle_state"; import { computeStateDomain } from "./compute_state_domain"; -import { UNAVAILABLE } from "../../data/entity"; export const stateCardType = (hass: HomeAssistant, stateObj: HassEntity) => { if (stateObj.state === UNAVAILABLE) { diff --git a/src/common/image/extract_color.ts b/src/common/image/extract_color.ts index e9561bcc8c..96497100ed 100644 --- a/src/common/image/extract_color.ts +++ b/src/common/image/extract_color.ts @@ -1,7 +1,7 @@ -import Vibrant from "node-vibrant/lib/browser"; -import MMCQ from "@vibrant/quantizer-mmcq"; -import { BasicPipeline } from "@vibrant/core/lib/pipeline"; -import { Swatch, Vec3 } from "@vibrant/color"; +// We import the minified bundle because the unminified bundle +// has some quirks that break wds. See #7784 for unminified version. +import Vibrant from "node-vibrant/dist/vibrant"; +import type { Swatch, Vec3 } from "@vibrant/color"; import { getRGBContrastRatio } from "../color/rgb"; const CONTRAST_RATIO = 4.5; @@ -104,23 +104,15 @@ const customGenerator = (colors: Swatch[]) => { } return { - foreground: new Swatch(foregroundColor, 0), + // We can't import Swatch constructor from the minified bundle, take it from background color. + // @ts-expect-error + foreground: new backgroundColor.constructor(foregroundColor, 0), background: backgroundColor, }; }; -Vibrant.use( - new BasicPipeline().filter - .register( - "default", - (r: number, g: number, b: number, a: number) => - a >= 125 && !(r > 250 && g > 250 && b > 250) - ) - .quantizer.register("mmcq", MMCQ) - // Our generator has different output - // @ts-expect-error - .generator.register("default", customGenerator) -); +// Set our custom generator as the default. +Vibrant._pipeline.generator.register("default", customGenerator); export const extractColors = (url: string, downsampleColors = 16) => new Vibrant(url, { diff --git a/src/common/search/search-input.ts b/src/common/search/search-input.ts index 97a52b1864..aff3a1a2a7 100644 --- a/src/common/search/search-input.ts +++ b/src/common/search/search-input.ts @@ -1,3 +1,5 @@ +import "@material/mwc-icon-button/mwc-icon-button"; +import { mdiClose, mdiMagnify } from "@mdi/js"; import "@polymer/paper-input/paper-input"; import { css, @@ -10,8 +12,6 @@ import { html, TemplateResult } from "lit-html"; import { classMap } from "lit-html/directives/class-map"; import "../../components/ha-svg-icon"; import { fireEvent } from "../dom/fire_event"; -import { mdiMagnify, mdiClose } from "@mdi/js"; -import "@material/mwc-icon-button/mwc-icon-button"; @customElement("search-input") class SearchInput extends LitElement { diff --git a/src/common/structs/handle-errors.ts b/src/common/structs/handle-errors.ts new file mode 100644 index 0000000000..7aa8889045 --- /dev/null +++ b/src/common/structs/handle-errors.ts @@ -0,0 +1,45 @@ +import { StructError } from "superstruct"; +import type { HomeAssistant } from "../../types"; + +export const handleStructError = ( + hass: HomeAssistant, + err: Error +): { warnings: string[]; errors?: string[] } => { + if (!(err instanceof StructError)) { + return { warnings: [err.message], errors: undefined }; + } + const errors: string[] = []; + const warnings: string[] = []; + for (const failure of err.failures()) { + if (failure.value === undefined) { + errors.push( + hass.localize( + "ui.errors.config.key_missing", + "key", + failure.path.join(".") + ) + ); + } else if (failure.type === "never") { + warnings.push( + hass.localize( + "ui.errors.config.key_not_expected", + "key", + failure.path.join(".") + ) + ); + } else { + warnings.push( + hass.localize( + "ui.errors.config.key_wrong_type", + "key", + failure.path.join("."), + "type_correct", + failure.type, + "type_wrong", + JSON.stringify(failure.value) + ) + ); + } + } + return { warnings, errors }; +}; diff --git a/src/common/structs/is-entity-id.ts b/src/common/structs/is-entity-id.ts new file mode 100644 index 0000000000..c408187e84 --- /dev/null +++ b/src/common/structs/is-entity-id.ts @@ -0,0 +1,30 @@ +import { struct, StructContext, StructResult } from "superstruct"; + +const isEntityId = (value: unknown, context: StructContext): StructResult => { + if (typeof value !== "string") { + return [context.fail({ type: "string" })]; + } + if (!value.includes(".")) { + return [ + context.fail({ + type: "Entity ID should be in the format 'domain.entity'", + }), + ]; + } + return true; +}; + +export const EntityId = struct("entity-id", isEntityId); + +const isEntityIdOrAll = ( + value: unknown, + context: StructContext +): StructResult => { + if (typeof value === "string" && value === "all") { + return true; + } + + return isEntityId(value, context); +}; + +export const EntityIdOrAll = struct("entity-id-all", isEntityIdOrAll); diff --git a/src/panels/lovelace/common/structs/is-icon.ts b/src/common/structs/is-icon.ts similarity index 85% rename from src/panels/lovelace/common/structs/is-icon.ts rename to src/common/structs/is-icon.ts index d88dcdb593..7ca4ff9ed5 100644 --- a/src/panels/lovelace/common/structs/is-icon.ts +++ b/src/common/structs/is-icon.ts @@ -1,4 +1,4 @@ -import { StructContext, StructResult, struct } from "superstruct"; +import { struct, StructContext, StructResult } from "superstruct"; const isIcon = (value: unknown, context: StructContext): StructResult => { if (typeof value !== "string") { diff --git a/src/common/translations/localize.ts b/src/common/translations/localize.ts index 47bbca7692..94edcc8a7a 100644 --- a/src/common/translations/localize.ts +++ b/src/common/translations/localize.ts @@ -1,5 +1,5 @@ -import IntlMessageFormat from "intl-messageformat"; import { shouldPolyfill } from "@formatjs/intl-pluralrules/should-polyfill"; +import IntlMessageFormat from "intl-messageformat"; import { Resources } from "../../types"; export type LocalizeFunc = (key: string, ...args: any[]) => string; diff --git a/src/common/util/copy-clipboard.ts b/src/common/util/copy-clipboard.ts index 3c89e40982..1708858c85 100644 --- a/src/common/util/copy-clipboard.ts +++ b/src/common/util/copy-clipboard.ts @@ -1,12 +1,17 @@ -export const copyToClipboard = (str) => { +export const copyToClipboard = async (str) => { if (navigator.clipboard) { - navigator.clipboard.writeText(str); - } else { - const el = document.createElement("textarea"); - el.value = str; - document.body.appendChild(el); - el.select(); - document.execCommand("copy"); - document.body.removeChild(el); + try { + await navigator.clipboard.writeText(str); + return; + } catch { + // just continue with the fallback coding below + } } + + const el = document.createElement("textarea"); + el.value = str; + document.body.appendChild(el); + el.select(); + document.execCommand("copy"); + document.body.removeChild(el); }; diff --git a/src/components/buttons/ha-progress-button.ts b/src/components/buttons/ha-progress-button.ts index c6c325fdab..bb431cf7cb 100644 --- a/src/components/buttons/ha-progress-button.ts +++ b/src/components/buttons/ha-progress-button.ts @@ -7,10 +7,9 @@ import { html, LitElement, property, - TemplateResult, query, + TemplateResult, } from "lit-element"; - import "../ha-circular-progress"; @customElement("ha-progress-button") diff --git a/src/components/data-table/sort-filter.ts b/src/components/data-table/sort-filter.ts index 79a41e6d77..6fcd8e522a 100644 --- a/src/components/data-table/sort-filter.ts +++ b/src/components/data-table/sort-filter.ts @@ -1,5 +1,4 @@ import { wrap } from "comlink"; - import type { api } from "./sort_filter_worker"; type FilterDataType = api["filterData"]; diff --git a/src/components/date-range-picker.ts b/src/components/date-range-picker.ts index 70211da366..f7ec87169e 100644 --- a/src/components/date-range-picker.ts +++ b/src/components/date-range-picker.ts @@ -1,11 +1,11 @@ // @ts-nocheck -import Vue from "vue"; import wrap from "@vue/web-component-wrapper"; +import { customElement } from "lit-element/lib/decorators"; +import Vue from "vue"; import DateRangePicker from "vue2-daterange-picker"; import dateRangePickerStyles from "vue2-daterange-picker/dist/vue2-daterange-picker.css"; import { fireEvent } from "../common/dom/fire_event"; import { Constructor } from "../types"; -import { customElement } from "lit-element/lib/decorators"; const Component = Vue.extend({ props: { @@ -210,7 +210,7 @@ class DateRangePickerElement extends WrappedElement { } .calendar-table { padding: 0 !important; - } + } `; const shadowRoot = this.shadowRoot!; shadowRoot.appendChild(style); diff --git a/src/components/device/ha-area-devices-picker.ts b/src/components/device/ha-area-devices-picker.ts index ed3d5c25f3..6e635d0720 100644 --- a/src/components/device/ha-area-devices-picker.ts +++ b/src/components/device/ha-area-devices-picker.ts @@ -1,5 +1,6 @@ -import "@material/mwc-icon-button/mwc-icon-button"; import "@material/mwc-button/mwc-button"; +import "@material/mwc-icon-button/mwc-icon-button"; +import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; @@ -11,9 +12,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, TemplateResult, } from "lit-element"; @@ -37,9 +38,8 @@ import { import { SubscribeMixin } from "../../mixins/subscribe-mixin"; import { PolymerChangedEvent } from "../../polymer-types"; import { HomeAssistant } from "../../types"; -import "./ha-devices-picker"; import "../ha-svg-icon"; -import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js"; +import "./ha-devices-picker"; interface DevicesByArea { [areaId: string]: AreaDevices; diff --git a/src/components/device/ha-device-automation-picker.ts b/src/components/device/ha-device-automation-picker.ts index fe56884a09..dba3b7b224 100644 --- a/src/components/device/ha-device-automation-picker.ts +++ b/src/components/device/ha-device-automation-picker.ts @@ -6,9 +6,9 @@ import { css, CSSResult, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import { fireEvent } from "../../common/dom/fire_event"; diff --git a/src/components/device/ha-device-picker.ts b/src/components/device/ha-device-picker.ts index c1e86dd761..0139a08c42 100644 --- a/src/components/device/ha-device-picker.ts +++ b/src/components/device/ha-device-picker.ts @@ -1,5 +1,5 @@ -import "../ha-svg-icon"; import "@material/mwc-icon-button/mwc-icon-button"; +import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; @@ -38,7 +38,7 @@ import { import { SubscribeMixin } from "../../mixins/subscribe-mixin"; import { PolymerChangedEvent } from "../../polymer-types"; import { HomeAssistant } from "../../types"; -import { mdiClose, mdiMenuUp, mdiMenuDown } from "@mdi/js"; +import "../ha-svg-icon"; interface Device { name: string; diff --git a/src/components/entity/ha-battery-icon.ts b/src/components/entity/ha-battery-icon.ts index 1838f6af5e..45f4f7d553 100644 --- a/src/components/entity/ha-battery-icon.ts +++ b/src/components/entity/ha-battery-icon.ts @@ -1,6 +1,6 @@ +import { customElement, html, LitElement, property } from "lit-element"; import { batteryIcon } from "../../common/entity/battery_icon"; import "../ha-icon"; -import { customElement, html, property, LitElement } from "lit-element"; @customElement("ha-battery-icon") export class HaBatteryIcon extends LitElement { diff --git a/src/components/entity/ha-entity-attribute-picker.ts b/src/components/entity/ha-entity-attribute-picker.ts index 5b1dbfc080..784ea421f8 100644 --- a/src/components/entity/ha-entity-attribute-picker.ts +++ b/src/components/entity/ha-entity-attribute-picker.ts @@ -1,3 +1,4 @@ +import "@material/mwc-icon-button/mwc-icon-button"; import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; @@ -17,10 +18,9 @@ import { import { fireEvent } from "../../common/dom/fire_event"; import { PolymerChangedEvent } from "../../polymer-types"; import { HomeAssistant } from "../../types"; +import { formatAttributeName } from "../../util/hass-attributes-util"; import "../ha-svg-icon"; import "./state-badge"; -import { formatAttributeName } from "../../util/hass-attributes-util"; -import "@material/mwc-icon-button/mwc-icon-button"; export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean; diff --git a/src/components/entity/ha-entity-toggle.ts b/src/components/entity/ha-entity-toggle.ts index ca46ad32b0..93bd745e87 100644 --- a/src/components/entity/ha-entity-toggle.ts +++ b/src/components/entity/ha-entity-toggle.ts @@ -1,21 +1,21 @@ -import "../ha-icon-button"; import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResult, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, TemplateResult, } from "lit-element"; import { STATES_OFF } from "../../common/const"; import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { computeStateName } from "../../common/entity/compute_state_name"; -import { UNAVAILABLE_STATES, UNAVAILABLE } from "../../data/entity"; +import { UNAVAILABLE, UNAVAILABLE_STATES } from "../../data/entity"; import { forwardHaptic } from "../../data/haptics"; import { HomeAssistant } from "../../types"; +import "../ha-icon-button"; import "../ha-switch"; const isOn = (stateObj?: HassEntity) => diff --git a/src/components/entity/ha-state-label-badge.ts b/src/components/entity/ha-state-label-badge.ts index 11a4761a2c..86e4d899b1 100644 --- a/src/components/entity/ha-state-label-badge.ts +++ b/src/components/entity/ha-state-label-badge.ts @@ -4,9 +4,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, TemplateResult, } from "lit-element"; @@ -18,10 +18,10 @@ import { computeStateName } from "../../common/entity/compute_state_name"; import { domainIcon } from "../../common/entity/domain_icon"; import { stateIcon } from "../../common/entity/state_icon"; import { timerTimeRemaining } from "../../common/entity/timer_time_remaining"; +import { formatNumber } from "../../common/string/format_number"; +import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; import { HomeAssistant } from "../../types"; import "../ha-label-badge"; -import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; -import { formatNumber } from "../../common/string/format_number"; @customElement("ha-state-label-badge") export class HaStateLabelBadge extends LitElement { diff --git a/src/components/entity/state-badge.ts b/src/components/entity/state-badge.ts index 200b125e90..4711bf4dfc 100644 --- a/src/components/entity/state-badge.ts +++ b/src/components/entity/state-badge.ts @@ -11,14 +11,11 @@ import { } from "lit-element"; import { ifDefined } from "lit-html/directives/if-defined"; import { styleMap } from "lit-html/directives/style-map"; - import { computeActiveState } from "../../common/entity/compute_active_state"; import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { stateIcon } from "../../common/entity/state_icon"; import { iconColorCSS } from "../../common/style/icon_color_css"; - import type { HomeAssistant } from "../../types"; - import "../ha-icon"; export class StateBadge extends LitElement { @@ -40,7 +37,8 @@ export class StateBadge extends LitElement { protected render(): TemplateResult { const stateObj = this.stateObj; - if (!stateObj) { + // We either need a `stateObj` or one override + if (!stateObj && !this.overrideIcon && !this.overrideImage) { return html`
`; @@ -50,7 +48,7 @@ export class StateBadge extends LitElement { return html``; } - const domain = computeStateDomain(stateObj); + const domain = stateObj ? computeStateDomain(stateObj) : undefined; return html` `; } protected updated(changedProps: PropertyValues) { - if (!changedProps.has("stateObj") || !this.stateObj) { + if ( + !changedProps.has("stateObj") && + !changedProps.has("overrideImage") && + !changedProps.has("overrideIcon") + ) { return; } const stateObj = this.stateObj; @@ -117,7 +119,15 @@ export class StateBadge extends LitElement { iconStyle.filter = `brightness(${(brightness + 245) / 5}%)`; } } + } else if (this.overrideImage) { + let imageUrl = this.overrideImage; + if (this.hass) { + imageUrl = this.hass.hassUrl(imageUrl); + } + hostStyle.backgroundImage = `url(${imageUrl})`; + this._showIcon = false; } + this._iconStyle = iconStyle; Object.assign(this.style, hostStyle); } diff --git a/src/components/entity/state-info.ts b/src/components/entity/state-info.ts index e4d132ab84..d493fb625d 100644 --- a/src/components/entity/state-info.ts +++ b/src/components/entity/state-info.ts @@ -1,4 +1,5 @@ import "@polymer/paper-tooltip/paper-tooltip"; +import type { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResult, @@ -8,12 +9,9 @@ import { property, TemplateResult, } from "lit-element"; -import type { HassEntity } from "home-assistant-js-websocket"; - import { computeStateName } from "../../common/entity/compute_state_name"; import { computeRTL } from "../../common/util/compute_rtl"; import type { HomeAssistant } from "../../types"; - import "../ha-relative-time"; import "./state-badge"; @@ -25,7 +23,7 @@ class StateInfo extends LitElement { @property({ type: Boolean }) public inDialog = false; - // property used only in css + // property used only in CSS @property({ type: Boolean, reflect: true }) public rtl = false; protected render(): TemplateResult { diff --git a/src/components/ha-area-picker.ts b/src/components/ha-area-picker.ts index 9ad7391448..c6b263fd54 100644 --- a/src/components/ha-area-picker.ts +++ b/src/components/ha-area-picker.ts @@ -1,5 +1,5 @@ -import "./ha-svg-icon"; import "@material/mwc-icon-button/mwc-icon-button"; +import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; @@ -11,27 +11,21 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, - TemplateResult, PropertyValues, query, + TemplateResult, } from "lit-element"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../common/dom/fire_event"; +import { computeDomain } from "../common/entity/compute_domain"; import { AreaRegistryEntry, createAreaRegistryEntry, subscribeAreaRegistry, } from "../data/area_registry"; -import { - showAlertDialog, - showPromptDialog, -} from "../dialogs/generic/show-dialog-box"; -import { SubscribeMixin } from "../mixins/subscribe-mixin"; -import { PolymerChangedEvent } from "../polymer-types"; -import { HomeAssistant } from "../types"; -import memoizeOne from "memoize-one"; import { DeviceEntityLookup, DeviceRegistryEntry, @@ -41,9 +35,15 @@ import { EntityRegistryEntry, subscribeEntityRegistry, } from "../data/entity_registry"; -import { computeDomain } from "../common/entity/compute_domain"; +import { + showAlertDialog, + showPromptDialog, +} from "../dialogs/generic/show-dialog-box"; +import { SubscribeMixin } from "../mixins/subscribe-mixin"; +import { PolymerChangedEvent } from "../polymer-types"; +import { HomeAssistant } from "../types"; import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker"; -import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js"; +import "./ha-svg-icon"; const rowRenderer = ( root: HTMLElement, diff --git a/src/components/ha-attributes.ts b/src/components/ha-attributes.ts index 593d1bcb0e..6d5556cef2 100644 --- a/src/components/ha-attributes.ts +++ b/src/components/ha-attributes.ts @@ -12,6 +12,7 @@ import { until } from "lit-html/directives/until"; import hassAttributeUtil, { formatAttributeName, } from "../util/hass-attributes-util"; +import { haStyle } from "../resources/styles"; let jsYamlPromise: Promise; @@ -55,30 +56,36 @@ class HaAttributes extends LitElement { `; } - static get styles(): CSSResult { - return css` - .data-entry { - display: flex; - flex-direction: row; - justify-content: space-between; - } - .data-entry .value { - max-width: 50%; - overflow-wrap: break-word; - text-align: right; - } - .key { - flex-grow: 1; - } - .attribution { - color: var(--secondary-text-color); - text-align: center; - } - pre { - font-family: inherit; - font-size: inherit; - } - `; + static get styles(): CSSResult[] { + return [ + haStyle, + css` + .data-entry { + display: flex; + flex-direction: row; + justify-content: space-between; + } + .data-entry .value { + max-width: 60%; + overflow-wrap: break-word; + text-align: right; + } + .key { + flex-grow: 1; + } + .attribution { + color: var(--secondary-text-color); + text-align: center; + } + pre { + font-family: inherit; + font-size: inherit; + margin: 0px; + overflow-wrap: break-word; + white-space: pre-line; + } + `, + ]; } private computeDisplayAttributes(filtersArray: string[]): string[] { @@ -102,6 +109,7 @@ class HaAttributes extends LitElement { if (value === null) { return "-"; } + // YAML handling if ( (Array.isArray(value) && value.some((val) => val instanceof Object)) || (!Array.isArray(value) && value instanceof Object) @@ -112,6 +120,19 @@ class HaAttributes extends LitElement { const yaml = jsYamlPromise.then((jsYaml) => jsYaml.safeDump(value)); return html`
${until(yaml, "")}
`; } + // URL handling + if (typeof value === "string" && value.startsWith("http")) { + try { + // If invalid URL, exception will be raised + const url = new URL(value); + if (url.protocol === "http:" || url.protocol === "https:") + return html`${value}`; + } catch (_) { + // Nothing to do here + } + } return Array.isArray(value) ? value.join(", ") : value; } } diff --git a/src/components/ha-bar.ts b/src/components/ha-bar.ts index 6d449c3306..3378958a56 100644 --- a/src/components/ha-bar.ts +++ b/src/components/ha-bar.ts @@ -7,7 +7,6 @@ import { svg, TemplateResult, } from "lit-element"; - import { getValueInPercentage, normalize, diff --git a/src/components/ha-button-toggle-group.ts b/src/components/ha-button-toggle-group.ts index f932cb4357..33c65cdf82 100644 --- a/src/components/ha-button-toggle-group.ts +++ b/src/components/ha-button-toggle-group.ts @@ -1,3 +1,4 @@ +import "@material/mwc-button/mwc-button"; import "@material/mwc-icon-button/mwc-icon-button"; import { css, @@ -11,7 +12,6 @@ import { import { fireEvent } from "../common/dom/fire_event"; import type { ToggleButton } from "../types"; import "./ha-svg-icon"; -import "@material/mwc-button/mwc-button"; @customElement("ha-button-toggle-group") export class HaButtonToggleGroup extends LitElement { diff --git a/src/components/ha-camera-stream.ts b/src/components/ha-camera-stream.ts index 4a8a14e05f..adc3913644 100644 --- a/src/components/ha-camera-stream.ts +++ b/src/components/ha-camera-stream.ts @@ -12,6 +12,7 @@ import { import { fireEvent } from "../common/dom/fire_event"; import { computeStateName } from "../common/entity/compute_state_name"; import { supportsFeature } from "../common/entity/supports-feature"; +import { isComponentLoaded } from "../common/config/is_component_loaded"; import { CameraEntity, CAMERA_SUPPORT_STREAM, @@ -86,7 +87,7 @@ class HaCameraStream extends LitElement { private get _shouldRenderMJPEG() { return ( this._forceMJPEG === this.stateObj!.entity_id || - !this.hass!.config.components.includes("stream") || + !isComponentLoaded(this.hass!, "stream") || !supportsFeature(this.stateObj!, CAMERA_SUPPORT_STREAM) ); } diff --git a/src/components/ha-circular-progress.ts b/src/components/ha-circular-progress.ts index c3e188d267..7b69afa6b8 100644 --- a/src/components/ha-circular-progress.ts +++ b/src/components/ha-circular-progress.ts @@ -1,5 +1,5 @@ -import { customElement, property } from "lit-element"; import { CircularProgress } from "@material/mwc-circular-progress"; +import { customElement, property } from "lit-element"; @customElement("ha-circular-progress") // @ts-ignore diff --git a/src/components/ha-climate-control.js b/src/components/ha-climate-control.js index f4732d8623..9962680dfd 100644 --- a/src/components/ha-climate-control.js +++ b/src/components/ha-climate-control.js @@ -1,9 +1,9 @@ import "@polymer/iron-flex-layout/iron-flex-layout-classes"; -import "./ha-icon-button"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; import { EventsMixin } from "../mixins/events-mixin"; +import "./ha-icon-button"; /* * @appliesMixin EventsMixin diff --git a/src/components/ha-climate-state.ts b/src/components/ha-climate-state.ts index ba5e5844ec..9b46f6cdb8 100644 --- a/src/components/ha-climate-state.ts +++ b/src/components/ha-climate-state.ts @@ -1,3 +1,4 @@ +import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResult, @@ -7,11 +8,9 @@ import { property, TemplateResult, } from "lit-element"; -import { HassEntity } from "home-assistant-js-websocket"; - +import { formatNumber } from "../common/string/format_number"; import { CLIMATE_PRESET_NONE } from "../data/climate"; import type { HomeAssistant } from "../types"; -import { formatNumber } from "../common/string/format_number"; @customElement("ha-climate-state") class HaClimateState extends LitElement { diff --git a/src/components/ha-code-editor.ts b/src/components/ha-code-editor.ts index 5dc3b7f826..50e851fd22 100644 --- a/src/components/ha-code-editor.ts +++ b/src/components/ha-code-editor.ts @@ -131,81 +131,81 @@ export class HaCodeEditor extends UpdatingElement { .cm-s-default .CodeMirror-cursor { border-left: 1px solid var(--secondary-text-color); } - + .cm-s-default div.CodeMirror-selected, .cm-s-default.CodeMirror-focused div.CodeMirror-selected { background: rgba(var(--rgb-primary-color), 0.2); } - + .cm-s-default .CodeMirror-line::selection, .cm-s-default .CodeMirror-line>span::selection, .cm-s-default .CodeMirror-line>span>span::selection { background: rgba(var(--rgb-primary-color), 0.2); } - + .cm-s-default .cm-keyword { color: var(--codemirror-keyword, #6262FF); } - + .cm-s-default .cm-operator { color: var(--codemirror-operator, #cda869); } - + .cm-s-default .cm-variable-2 { color: var(--codemirror-variable-2, #690); } - + .cm-s-default .cm-builtin { color: var(--codemirror-builtin, #9B7536); } - + .cm-s-default .cm-atom { color: var(--codemirror-atom, #F90); } - + .cm-s-default .cm-number { color: var(--codemirror-number, #ca7841); } - + .cm-s-default .cm-def { color: var(--codemirror-def, #8DA6CE); } - + .cm-s-default .cm-string { color: var(--codemirror-string, #07a); } - + .cm-s-default .cm-string-2 { color: var(--codemirror-string-2, #bd6b18); } - + .cm-s-default .cm-comment { color: var(--codemirror-comment, #777); } - + .cm-s-default .cm-variable { color: var(--codemirror-variable, #07a); } - + .cm-s-default .cm-tag { color: var(--codemirror-tag, #997643); } - + .cm-s-default .cm-meta { color: var(--codemirror-meta, var(--primary-text-color)); } - + .cm-s-default .cm-attribute { color: var(--codemirror-attribute, #d6bb6d); } - + .cm-s-default .cm-property { color: var(--codemirror-property, #905); } - + .cm-s-default .cm-qualifier { color: var(--codemirror-qualifier, #690); } - + .cm-s-default .cm-variable-3 { color: var(--codemirror-variable-3, #07a); } diff --git a/src/components/ha-combo-box.js b/src/components/ha-combo-box.js index 62d66b4511..b844d27496 100644 --- a/src/components/ha-combo-box.js +++ b/src/components/ha-combo-box.js @@ -1,4 +1,3 @@ -import "./ha-icon-button"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; import { html } from "@polymer/polymer/lib/utils/html-tag"; @@ -6,6 +5,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light"; import { EventsMixin } from "../mixins/events-mixin"; +import "./ha-icon-button"; class HaComboBox extends EventsMixin(PolymerElement) { static get template() { diff --git a/src/components/ha-cover-controls.ts b/src/components/ha-cover-controls.ts index 06def92e23..08910a7735 100644 --- a/src/components/ha-cover-controls.ts +++ b/src/components/ha-cover-controls.ts @@ -1,3 +1,4 @@ +import type { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResult, @@ -10,14 +11,11 @@ import { TemplateResult, } from "lit-element"; import { classMap } from "lit-html/directives/class-map"; -import type { HassEntity } from "home-assistant-js-websocket"; - -import type { HomeAssistant } from "../types"; -import { UNAVAILABLE } from "../data/entity"; -import CoverEntity from "../util/cover-model"; - -import "./ha-icon-button"; import { computeCloseIcon, computeOpenIcon } from "../common/entity/cover_icon"; +import { UNAVAILABLE } from "../data/entity"; +import type { HomeAssistant } from "../types"; +import CoverEntity from "../util/cover-model"; +import "./ha-icon-button"; @customElement("ha-cover-controls") class HaCoverControls extends LitElement { diff --git a/src/components/ha-cover-tilt-controls.ts b/src/components/ha-cover-tilt-controls.ts index 790721f11a..be2f6f4087 100644 --- a/src/components/ha-cover-tilt-controls.ts +++ b/src/components/ha-cover-tilt-controls.ts @@ -1,21 +1,19 @@ import { HassEntity } from "home-assistant-js-websocket"; import { + css, + CSSResult, + customElement, + html, + internalProperty, LitElement, property, - internalProperty, - CSSResult, - css, - customElement, - TemplateResult, - html, PropertyValues, + TemplateResult, } from "lit-element"; import { classMap } from "lit-html/directives/class-map"; - import { UNAVAILABLE } from "../data/entity"; import { HomeAssistant } from "../types"; import CoverEntity from "../util/cover-model"; - import "./ha-icon-button"; @customElement("ha-cover-tilt-controls") @@ -52,7 +50,7 @@ class HaCoverTiltControls extends LitElement { > diff --git a/src/components/ha-date-range-picker.ts b/src/components/ha-date-range-picker.ts index 895a33f984..91db9cab23 100644 --- a/src/components/ha-date-range-picker.ts +++ b/src/components/ha-date-range-picker.ts @@ -1,3 +1,9 @@ +import "@material/mwc-button/mwc-button"; +import "@material/mwc-list/mwc-list"; +import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; +import "@material/mwc-list/mwc-list-item"; +import { mdiCalendar } from "@mdi/js"; +import "@polymer/paper-input/paper-input"; import { css, CSSResult, @@ -5,20 +11,14 @@ import { html, LitElement, property, - TemplateResult, PropertyValues, + TemplateResult, } from "lit-element"; -import { HomeAssistant } from "../types"; -import { mdiCalendar } from "@mdi/js"; import { formatDateTime } from "../common/datetime/format_date_time"; -import "@material/mwc-button/mwc-button"; -import "@material/mwc-list/mwc-list-item"; -import "./ha-svg-icon"; -import "@polymer/paper-input/paper-input"; -import "@material/mwc-list/mwc-list"; -import "./date-range-picker"; import { computeRTLDirection } from "../common/util/compute_rtl"; -import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; +import { HomeAssistant } from "../types"; +import "./date-range-picker"; +import "./ha-svg-icon"; export interface DateRangePickerRanges { [key: string]: [Date, Date]; diff --git a/src/components/ha-expansion-panel.ts b/src/components/ha-expansion-panel.ts index f328e3590e..be3dc4f77d 100644 --- a/src/components/ha-expansion-panel.ts +++ b/src/components/ha-expansion-panel.ts @@ -1,3 +1,4 @@ +import { mdiChevronDown } from "@mdi/js"; import { css, CSSResult, @@ -8,10 +9,9 @@ import { query, TemplateResult, } from "lit-element"; +import { classMap } from "lit-html/directives/class-map"; import { fireEvent } from "../common/dom/fire_event"; import "./ha-svg-icon"; -import { mdiChevronDown } from "@mdi/js"; -import { classMap } from "lit-html/directives/class-map"; @customElement("ha-expansion-panel") class HaExpansionPanel extends LitElement { @@ -74,6 +74,7 @@ class HaExpansionPanel extends LitElement { var(--divider-color, #e0e0e0) ); border-radius: var(--ha-card-border-radius, 4px); + padding: 0 8px; } .summary { @@ -83,6 +84,7 @@ class HaExpansionPanel extends LitElement { align-items: center; cursor: pointer; overflow: hidden; + font-weight: 500; } .summary-icon { diff --git a/src/components/ha-fab.ts b/src/components/ha-fab.ts index f0917173a0..9725630b6c 100644 --- a/src/components/ha-fab.ts +++ b/src/components/ha-fab.ts @@ -1,5 +1,5 @@ -import type { Fab } from "@material/mwc-fab"; import "@material/mwc-fab"; +import type { Fab } from "@material/mwc-fab"; import { customElement } from "lit-element"; import { Constructor } from "../types"; diff --git a/src/components/ha-form/ha-form-constant.ts b/src/components/ha-form/ha-form-constant.ts index 9ce3a02437..4e6d6e21b4 100644 --- a/src/components/ha-form/ha-form-constant.ts +++ b/src/components/ha-form/ha-form-constant.ts @@ -1,15 +1,15 @@ import { + css, + CSSResult, customElement, html, LitElement, property, - TemplateResult, - CSSResult, - css, PropertyValues, + TemplateResult, } from "lit-element"; -import { HaFormElement, HaFormConstantSchema } from "./ha-form"; import { fireEvent } from "../../common/dom/fire_event"; +import { HaFormConstantSchema, HaFormElement } from "./ha-form"; @customElement("ha-form-constant") export class HaFormConstant extends LitElement implements HaFormElement { diff --git a/src/components/ha-form/ha-form-multi_select.ts b/src/components/ha-form/ha-form-multi_select.ts index 29f174a160..290c1a17b0 100644 --- a/src/components/ha-form/ha-form-multi_select.ts +++ b/src/components/ha-form/ha-form-multi_select.ts @@ -9,19 +9,19 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, query, TemplateResult, } from "lit-element"; import { fireEvent } from "../../common/dom/fire_event"; +import "../ha-icon"; import { HaFormElement, HaFormMultiSelectData, HaFormMultiSelectSchema, } from "./ha-form"; -import "../ha-icon"; @customElement("ha-form-multi_select") export class HaFormMultiSelect extends LitElement implements HaFormElement { diff --git a/src/components/ha-form/ha-form-positive_time_period_dict.ts b/src/components/ha-form/ha-form-positive_time_period_dict.ts index d5a0db5975..453eaf2a68 100644 --- a/src/components/ha-form/ha-form-positive_time_period_dict.ts +++ b/src/components/ha-form/ha-form-positive_time_period_dict.ts @@ -6,8 +6,7 @@ import { query, TemplateResult, } from "lit-element"; -import { fireEvent } from "../../common/dom/fire_event"; -import "../paper-time-input"; +import "../ha-time-input"; import { HaFormElement, HaFormTimeData, HaFormTimeSchema } from "./ha-form"; @customElement("ha-form-positive_time_period_dict") @@ -20,7 +19,7 @@ export class HaFormTimePeriod extends LitElement implements HaFormElement { @property() public suffix!: string; - @query("paper-time-input", true) private _input?: HTMLElement; + @query("ha-time-input", true) private _input?: HTMLElement; public focus() { if (this._input) { @@ -30,86 +29,13 @@ export class HaFormTimePeriod extends LitElement implements HaFormElement { protected render(): TemplateResult { return html` - + .data=${this.data} + > `; } - - private get _hours() { - return this.data && this.data.hours ? Number(this.data.hours) : 0; - } - - private get _minutes() { - return this.data && this.data.minutes ? Number(this.data.minutes) : 0; - } - - private get _seconds() { - return this.data && this.data.seconds ? Number(this.data.seconds) : 0; - } - - private _parseDuration(value) { - return value.toString().padStart(2, "0"); - } - - private _hourChanged(ev) { - this._durationChanged(ev, "hours"); - } - - private _minChanged(ev) { - this._durationChanged(ev, "minutes"); - } - - private _secChanged(ev) { - this._durationChanged(ev, "seconds"); - } - - private _durationChanged(ev, unit) { - let value = Number(ev.detail.value); - - if (value === this[`_${unit}`]) { - return; - } - - let hours = this._hours; - let minutes = this._minutes; - - if (unit === "seconds" && value > 59) { - minutes += Math.floor(value / 60); - value %= 60; - } - - if (unit === "minutes" && value > 59) { - hours += Math.floor(value / 60); - value %= 60; - } - - fireEvent(this, "value-changed", { - value: { - hours, - minutes, - seconds: this._seconds, - ...{ [unit]: value }, - }, - }); - } } declare global { diff --git a/src/components/ha-form/ha-form-string.ts b/src/components/ha-form/ha-form-string.ts index 3f62f9de31..5775051dce 100644 --- a/src/components/ha-form/ha-form-string.ts +++ b/src/components/ha-form/ha-form-string.ts @@ -1,16 +1,16 @@ -import "../ha-icon-button"; import "@polymer/paper-input/paper-input"; import type { PaperInputElement } from "@polymer/paper-input/paper-input"; import { customElement, html, + internalProperty, LitElement, property, - internalProperty, query, TemplateResult, } from "lit-element"; import { fireEvent } from "../../common/dom/fire_event"; +import "../ha-icon-button"; import type { HaFormElement, HaFormStringData, diff --git a/src/components/ha-form/ha-form.ts b/src/components/ha-form/ha-form.ts index 87af775202..25cf8cd18d 100644 --- a/src/components/ha-form/ha-form.ts +++ b/src/components/ha-form/ha-form.ts @@ -8,14 +8,15 @@ import { } from "lit-element"; import { dynamicElement } from "../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../common/dom/fire_event"; +import { HaTimeData } from "../ha-time-input"; import "./ha-form-boolean"; +import "./ha-form-constant"; import "./ha-form-float"; import "./ha-form-integer"; import "./ha-form-multi_select"; import "./ha-form-positive_time_period_dict"; import "./ha-form-select"; import "./ha-form-string"; -import "./ha-form-constant"; export type HaFormSchema = | HaFormConstantSchema @@ -71,7 +72,7 @@ export interface HaFormBooleanSchema extends HaFormBaseSchema { } export interface HaFormTimeSchema extends HaFormBaseSchema { - type: "time"; + type: "positive_time_period_dict"; } export interface HaFormDataContainer { @@ -93,11 +94,7 @@ export type HaFormFloatData = number; export type HaFormBooleanData = boolean; export type HaFormSelectData = string; export type HaFormMultiSelectData = string[]; -export interface HaFormTimeData { - hours?: number; - minutes?: number; - seconds?: number; -} +export type HaFormTimeData = HaTimeData; export interface HaFormElement extends LitElement { schema: HaFormSchema | HaFormSchema[]; diff --git a/src/components/ha-gauge.ts b/src/components/ha-gauge.ts index 249a5639a5..692189c243 100644 --- a/src/components/ha-gauge.ts +++ b/src/components/ha-gauge.ts @@ -1,18 +1,17 @@ import { - LitElement, - svg, - customElement, css, - property, + customElement, internalProperty, + LitElement, + property, PropertyValues, + svg, } from "lit-element"; -import { styleMap } from "lit-html/directives/style-map"; -import { afterNextRender } from "../common/util/render-status"; import { ifDefined } from "lit-html/directives/if-defined"; - -import { getValueInPercentage, normalize } from "../util/calculate"; +import { styleMap } from "lit-html/directives/style-map"; import { formatNumber } from "../common/string/format_number"; +import { afterNextRender } from "../common/util/render-status"; +import { getValueInPercentage, normalize } from "../util/calculate"; const getAngle = (value: number, min: number, max: number) => { const percentage = getValueInPercentage(normalize(value, min, max), min, max); diff --git a/src/components/ha-icon-button-arrow-next.ts b/src/components/ha-icon-button-arrow-next.ts index fa2917db6a..85bc2d245f 100644 --- a/src/components/ha-icon-button-arrow-next.ts +++ b/src/components/ha-icon-button-arrow-next.ts @@ -1,15 +1,15 @@ +import "@material/mwc-icon-button/mwc-icon-button"; +import { mdiArrowLeft, mdiArrowRight } from "@mdi/js"; import { + customElement, + html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, - html, - customElement, } from "lit-element"; -import { mdiArrowLeft, mdiArrowRight } from "@mdi/js"; -import "@material/mwc-icon-button/mwc-icon-button"; -import "./ha-svg-icon"; import { HomeAssistant } from "../types"; +import "./ha-svg-icon"; @customElement("ha-icon-button-arrow-next") export class HaIconButtonArrowNext extends LitElement { diff --git a/src/components/ha-icon-button-arrow-prev.ts b/src/components/ha-icon-button-arrow-prev.ts index c7fbada10d..c426c589a7 100644 --- a/src/components/ha-icon-button-arrow-prev.ts +++ b/src/components/ha-icon-button-arrow-prev.ts @@ -1,15 +1,15 @@ +import "@material/mwc-icon-button/mwc-icon-button"; +import { mdiArrowLeft, mdiArrowRight } from "@mdi/js"; import { + customElement, + html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, - html, - customElement, } from "lit-element"; -import { mdiArrowLeft, mdiArrowRight } from "@mdi/js"; -import "@material/mwc-icon-button/mwc-icon-button"; -import "./ha-svg-icon"; import { HomeAssistant } from "../types"; +import "./ha-svg-icon"; @customElement("ha-icon-button-arrow-prev") export class HaIconButtonArrowPrev extends LitElement { diff --git a/src/components/ha-icon-button-next.ts b/src/components/ha-icon-button-next.ts index 48eca31a4a..04213393eb 100644 --- a/src/components/ha-icon-button-next.ts +++ b/src/components/ha-icon-button-next.ts @@ -1,15 +1,15 @@ +import "@material/mwc-icon-button"; +import { mdiChevronLeft, mdiChevronRight } from "@mdi/js"; import { + customElement, + html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, - html, - customElement, } from "lit-element"; -import { mdiChevronRight, mdiChevronLeft } from "@mdi/js"; -import "@material/mwc-icon-button"; -import "./ha-svg-icon"; import { HomeAssistant } from "../types"; +import "./ha-svg-icon"; @customElement("ha-icon-button-next") export class HaIconButtonNext extends LitElement { diff --git a/src/components/ha-icon-button-prev.ts b/src/components/ha-icon-button-prev.ts index 8b88afedda..6384039f13 100644 --- a/src/components/ha-icon-button-prev.ts +++ b/src/components/ha-icon-button-prev.ts @@ -1,15 +1,15 @@ +import "@material/mwc-icon-button/mwc-icon-button"; +import { mdiChevronLeft, mdiChevronRight } from "@mdi/js"; import { + customElement, + html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, - html, - customElement, } from "lit-element"; -import { mdiChevronRight, mdiChevronLeft } from "@mdi/js"; -import "@material/mwc-icon-button/mwc-icon-button"; -import "./ha-svg-icon"; import { HomeAssistant } from "../types"; +import "./ha-svg-icon"; @customElement("ha-icon-button-prev") export class HaIconButtonPrev extends LitElement { diff --git a/src/components/ha-icon-button.ts b/src/components/ha-icon-button.ts index 1c645b50cd..e8cc62ab55 100644 --- a/src/components/ha-icon-button.ts +++ b/src/components/ha-icon-button.ts @@ -1,12 +1,12 @@ import "@material/mwc-icon-button"; import { + css, + CSSResult, customElement, html, - TemplateResult, - property, LitElement, - CSSResult, - css, + property, + TemplateResult, } from "lit-element"; import "./ha-icon"; diff --git a/src/components/ha-icon-next.ts b/src/components/ha-icon-next.ts index 742f342e91..6b6eef9b01 100644 --- a/src/components/ha-icon-next.ts +++ b/src/components/ha-icon-next.ts @@ -1,5 +1,5 @@ +import { mdiChevronLeft, mdiChevronRight } from "@mdi/js"; import { HaSvgIcon } from "./ha-svg-icon"; -import { mdiChevronRight, mdiChevronLeft } from "@mdi/js"; export class HaIconNext extends HaSvgIcon { public connectedCallback() { diff --git a/src/components/ha-icon-prev.ts b/src/components/ha-icon-prev.ts index df800fd52d..29712fc89b 100644 --- a/src/components/ha-icon-prev.ts +++ b/src/components/ha-icon-prev.ts @@ -1,5 +1,5 @@ +import { mdiChevronLeft, mdiChevronRight } from "@mdi/js"; import { HaSvgIcon } from "./ha-svg-icon"; -import { mdiChevronRight, mdiChevronLeft } from "@mdi/js"; export class HaIconPrev extends HaSvgIcon { public connectedCallback() { diff --git a/src/components/ha-icon.ts b/src/components/ha-icon.ts index 8fe065bc6e..2cb7e0bdf6 100644 --- a/src/components/ha-icon.ts +++ b/src/components/ha-icon.ts @@ -1,28 +1,28 @@ import "@polymer/iron-icon/iron-icon"; import { - customElement, - LitElement, - property, - internalProperty, - PropertyValues, - html, - TemplateResult, css, CSSResult, + customElement, + html, + internalProperty, + LitElement, + property, + PropertyValues, + TemplateResult, } from "lit-element"; -import "./ha-svg-icon"; -import { customIconsets, CustomIcon } from "../data/custom_iconsets"; +import { fireEvent } from "../common/dom/fire_event"; +import { debounce } from "../common/util/debounce"; +import { CustomIcon, customIconsets } from "../data/custom_iconsets"; import { - Chunks, - MDI_PREFIXES, - getIcon, - findIconChunk, - Icons, checkCacheVersion, + Chunks, + findIconChunk, + getIcon, + Icons, + MDI_PREFIXES, writeCache, } from "../data/iconsets"; -import { debounce } from "../common/util/debounce"; -import { fireEvent } from "../common/dom/fire_event"; +import "./ha-svg-icon"; interface DeprecatedIcon { [key: string]: { diff --git a/src/components/ha-markdown.ts b/src/components/ha-markdown.ts index 484776a2b0..c98f5d07a9 100644 --- a/src/components/ha-markdown.ts +++ b/src/components/ha-markdown.ts @@ -7,7 +7,6 @@ import { property, TemplateResult, } from "lit-element"; - import "./ha-markdown-element"; @customElement("ha-markdown") diff --git a/src/components/ha-push-notifications-toggle.js b/src/components/ha-push-notifications-toggle.js index b8bead07fc..dfb876b9a7 100644 --- a/src/components/ha-push-notifications-toggle.js +++ b/src/components/ha-push-notifications-toggle.js @@ -2,8 +2,8 @@ import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; import { getAppKey } from "../data/notify_html5"; -import { EventsMixin } from "../mixins/events-mixin"; import { showPromptDialog } from "../dialogs/generic/show-dialog-box"; +import { EventsMixin } from "../mixins/events-mixin"; import "./ha-switch"; export const pushSupported = diff --git a/src/components/ha-related-items.ts b/src/components/ha-related-items.ts index 4bdcdda1e9..3d2cc72c27 100644 --- a/src/components/ha-related-items.ts +++ b/src/components/ha-related-items.ts @@ -4,9 +4,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, TemplateResult, } from "lit-element"; diff --git a/src/components/ha-relative-time.ts b/src/components/ha-relative-time.ts index a2f9e403ca..98b9228dec 100644 --- a/src/components/ha-relative-time.ts +++ b/src/components/ha-relative-time.ts @@ -1,12 +1,10 @@ import { customElement, - UpdatingElement, property, PropertyValues, + UpdatingElement, } from "lit-element"; - import relativeTime from "../common/datetime/relative_time"; - import type { HomeAssistant } from "../types"; @customElement("ha-relative-time") diff --git a/src/components/ha-selector/ha-selector-action.ts b/src/components/ha-selector/ha-selector-action.ts index c6e06e3a12..be91204c7f 100644 --- a/src/components/ha-selector/ha-selector-action.ts +++ b/src/components/ha-selector/ha-selector-action.ts @@ -6,10 +6,10 @@ import { LitElement, property, } from "lit-element"; -import { HomeAssistant } from "../../types"; -import { ActionSelector } from "../../data/selector"; import { Action } from "../../data/script"; +import { ActionSelector } from "../../data/selector"; import "../../panels/config/automation/action/ha-automation-action"; +import { HomeAssistant } from "../../types"; @customElement("ha-selector-action") export class HaActionSelector extends LitElement { diff --git a/src/components/ha-selector/ha-selector-area.ts b/src/components/ha-selector/ha-selector-area.ts index 903626440b..8023dc4844 100644 --- a/src/components/ha-selector/ha-selector-area.ts +++ b/src/components/ha-selector/ha-selector-area.ts @@ -5,12 +5,12 @@ import { LitElement, property, } from "lit-element"; -import { HomeAssistant } from "../../types"; -import { AreaSelector } from "../../data/selector"; -import "../ha-area-picker"; import { ConfigEntry, getConfigEntries } from "../../data/config_entries"; import { DeviceRegistryEntry } from "../../data/device_registry"; import { EntityRegistryEntry } from "../../data/entity_registry"; +import { AreaSelector } from "../../data/selector"; +import { HomeAssistant } from "../../types"; +import "../ha-area-picker"; @customElement("ha-selector-area") export class HaAreaSelector extends LitElement { diff --git a/src/components/ha-selector/ha-selector-device.ts b/src/components/ha-selector/ha-selector-device.ts index f3f2c1ff35..d9ec80655c 100644 --- a/src/components/ha-selector/ha-selector-device.ts +++ b/src/components/ha-selector/ha-selector-device.ts @@ -5,11 +5,11 @@ import { LitElement, property, } from "lit-element"; +import { ConfigEntry, getConfigEntries } from "../../data/config_entries"; +import { DeviceRegistryEntry } from "../../data/device_registry"; +import { DeviceSelector } from "../../data/selector"; import { HomeAssistant } from "../../types"; import "../device/ha-device-picker"; -import { DeviceRegistryEntry } from "../../data/device_registry"; -import { ConfigEntry, getConfigEntries } from "../../data/config_entries"; -import { DeviceSelector } from "../../data/selector"; @customElement("ha-selector-device") export class HaDeviceSelector extends LitElement { diff --git a/src/components/ha-selector/ha-selector-entity.ts b/src/components/ha-selector/ha-selector-entity.ts index e6bfba75b5..78c7003e1f 100644 --- a/src/components/ha-selector/ha-selector-entity.ts +++ b/src/components/ha-selector/ha-selector-entity.ts @@ -1,3 +1,4 @@ +import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; import { customElement, html, @@ -5,13 +6,12 @@ import { LitElement, property, } from "lit-element"; -import { HomeAssistant } from "../../types"; -import "../entity/ha-entity-picker"; -import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { subscribeEntityRegistry } from "../../data/entity_registry"; -import { SubscribeMixin } from "../../mixins/subscribe-mixin"; import { EntitySelector } from "../../data/selector"; +import { SubscribeMixin } from "../../mixins/subscribe-mixin"; +import { HomeAssistant } from "../../types"; +import "../entity/ha-entity-picker"; @customElement("ha-selector-entity") export class HaEntitySelector extends SubscribeMixin(LitElement) { diff --git a/src/components/ha-selector/ha-selector-number.ts b/src/components/ha-selector/ha-selector-number.ts index 15d1ff0e23..3a819cf9c5 100644 --- a/src/components/ha-selector/ha-selector-number.ts +++ b/src/components/ha-selector/ha-selector-number.ts @@ -1,3 +1,4 @@ +import "@polymer/paper-input/paper-input"; import { css, CSSResult, @@ -6,12 +7,11 @@ import { LitElement, property, } from "lit-element"; -import { HomeAssistant } from "../../types"; -import { NumberSelector } from "../../data/selector"; -import "@polymer/paper-input/paper-input"; -import "../ha-slider"; -import { fireEvent } from "../../common/dom/fire_event"; import { classMap } from "lit-html/directives/class-map"; +import { fireEvent } from "../../common/dom/fire_event"; +import { NumberSelector } from "../../data/selector"; +import { HomeAssistant } from "../../types"; +import "../ha-slider"; @customElement("ha-selector-number") export class HaNumberSelector extends LitElement { diff --git a/src/components/ha-selector/ha-selector-object.ts b/src/components/ha-selector/ha-selector-object.ts new file mode 100644 index 0000000000..29159e3e8f --- /dev/null +++ b/src/components/ha-selector/ha-selector-object.ts @@ -0,0 +1,37 @@ +import { customElement, html, LitElement, property } from "lit-element"; +import { fireEvent } from "../../common/dom/fire_event"; +import { HomeAssistant } from "../../types"; +import "../ha-yaml-editor"; + +@customElement("ha-selector-object") +export class HaObjectSelector extends LitElement { + @property() public hass!: HomeAssistant; + + @property() public value?: any; + + @property() public label?: string; + + protected render() { + return html``; + } + + private _handleChange(ev) { + const value = ev.target.value; + if (!ev.target.isValid) { + return; + } + if (this.value === value) { + return; + } + fireEvent(this, "value-changed", { value }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-selector-object": HaObjectSelector; + } +} diff --git a/src/components/ha-selector/ha-selector-target.ts b/src/components/ha-selector/ha-selector-target.ts index aa6d2cfdb5..23c383e647 100644 --- a/src/components/ha-selector/ha-selector-target.ts +++ b/src/components/ha-selector/ha-selector-target.ts @@ -1,3 +1,9 @@ +import "@material/mwc-list/mwc-list"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-tab-bar/mwc-tab-bar"; +import "@material/mwc-tab/mwc-tab"; +import "@polymer/paper-input/paper-input"; +import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResult, @@ -7,23 +13,17 @@ import { LitElement, property, } from "lit-element"; -import { HomeAssistant } from "../../types"; -import { TargetSelector } from "../../data/selector"; import { ConfigEntry, getConfigEntries } from "../../data/config_entries"; import { DeviceRegistryEntry } from "../../data/device_registry"; -import "../ha-target-picker"; -import "@material/mwc-list/mwc-list-item"; -import "@polymer/paper-input/paper-input"; -import "@material/mwc-list/mwc-list"; import { EntityRegistryEntry, subscribeEntityRegistry, } from "../../data/entity_registry"; +import { TargetSelector } from "../../data/selector"; import { Target } from "../../data/target"; -import "@material/mwc-tab-bar/mwc-tab-bar"; -import "@material/mwc-tab/mwc-tab"; -import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; import { SubscribeMixin } from "../../mixins/subscribe-mixin"; +import { HomeAssistant } from "../../types"; +import "../ha-target-picker"; @customElement("ha-selector-target") export class HaTargetSelector extends SubscribeMixin(LitElement) { diff --git a/src/components/ha-selector/ha-selector-text.ts b/src/components/ha-selector/ha-selector-text.ts new file mode 100644 index 0000000000..32fa638ff0 --- /dev/null +++ b/src/components/ha-selector/ha-selector-text.ts @@ -0,0 +1,50 @@ +import { customElement, html, LitElement, property } from "lit-element"; +import { fireEvent } from "../../common/dom/fire_event"; +import { HomeAssistant } from "../../types"; +import "@polymer/paper-input/paper-textarea"; +import "@polymer/paper-input/paper-input"; +import { StringSelector } from "../../data/selector"; + +@customElement("ha-selector-text") +export class HaTextSelector extends LitElement { + @property() public hass!: HomeAssistant; + + @property() public value?: any; + + @property() public label?: string; + + @property() public selector!: StringSelector; + + protected render() { + if (this.selector.text?.multiline) { + return html``; + } + return html``; + } + + private _handleChange(ev) { + const value = ev.target.value; + if (this.value === value) { + return; + } + fireEvent(this, "value-changed", { value }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-selector-text": HaTextSelector; + } +} diff --git a/src/components/ha-selector/ha-selector-time.ts b/src/components/ha-selector/ha-selector-time.ts index 8bfa3d1ec1..688b23dad3 100644 --- a/src/components/ha-selector/ha-selector-time.ts +++ b/src/components/ha-selector/ha-selector-time.ts @@ -1,7 +1,7 @@ import { customElement, html, LitElement, property } from "lit-element"; -import { HomeAssistant } from "../../types"; -import { TimeSelector } from "../../data/selector"; import { fireEvent } from "../../common/dom/fire_event"; +import { TimeSelector } from "../../data/selector"; +import { HomeAssistant } from "../../types"; import "../paper-time-input"; const test = new Date().toLocaleString(); diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts index 3c88b2da25..88d702c9d1 100644 --- a/src/components/ha-selector/ha-selector.ts +++ b/src/components/ha-selector/ha-selector.ts @@ -1,16 +1,17 @@ import { customElement, html, LitElement, property } from "lit-element"; import { dynamicElement } from "../../common/dom/dynamic-element-directive"; -import { HomeAssistant } from "../../types"; - -import "./ha-selector-entity"; -import "./ha-selector-device"; -import "./ha-selector-area"; -import "./ha-selector-target"; -import "./ha-selector-number"; -import "./ha-selector-boolean"; -import "./ha-selector-time"; -import "./ha-selector-action"; import { Selector } from "../../data/selector"; +import { HomeAssistant } from "../../types"; +import "./ha-selector-action"; +import "./ha-selector-area"; +import "./ha-selector-boolean"; +import "./ha-selector-device"; +import "./ha-selector-entity"; +import "./ha-selector-number"; +import "./ha-selector-target"; +import "./ha-selector-time"; +import "./ha-selector-object"; +import "./ha-selector-text"; @customElement("ha-selector") export class HaSelector extends LitElement { diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index 7a4791b967..ffc5d57090 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -772,9 +772,12 @@ class HaSidebar extends LitElement { border-bottom: 1px solid transparent; white-space: nowrap; font-weight: 400; - color: var(--primary-text-color); + color: var(--sidebar-menu-button-text-color, --primary-text-color); border-bottom: 1px solid var(--divider-color); - background-color: var(--primary-background-color); + background-color: var( + --sidebar-menu-button-background-color, + --primary-background-color + ); font-size: 20px; align-items: center; padding-left: calc(4px + env(safe-area-inset-left)); diff --git a/src/components/ha-svg-icon.ts b/src/components/ha-svg-icon.ts index 6e58ea5378..ecc95d0d44 100644 --- a/src/components/ha-svg-icon.ts +++ b/src/components/ha-svg-icon.ts @@ -16,7 +16,7 @@ export class HaSvgIcon extends LitElement { protected render(): SVGTemplateResult { return svg` - diff --git a/src/components/ha-tab.ts b/src/components/ha-tab.ts index 4e0f33758f..c887c6c440 100644 --- a/src/components/ha-tab.ts +++ b/src/components/ha-tab.ts @@ -1,21 +1,21 @@ +import type { Ripple } from "@material/mwc-ripple"; +import "@material/mwc-ripple/mwc-ripple"; +import { RippleHandlers } from "@material/mwc-ripple/ripple-handlers"; import { css, CSSResult, customElement, + eventOptions, + html, + internalProperty, LitElement, property, - internalProperty, - TemplateResult, - html, queryAsync, - eventOptions, + TemplateResult, } from "lit-element"; -import "@material/mwc-ripple/mwc-ripple"; -import type { Ripple } from "@material/mwc-ripple"; -import { RippleHandlers } from "@material/mwc-ripple/ripple-handlers"; +import { ifDefined } from "lit-html/directives/if-defined"; import "./ha-icon"; import "./ha-svg-icon"; -import { ifDefined } from "lit-html/directives/if-defined"; @customElement("ha-tab") export class HaTab extends LitElement { diff --git a/src/components/ha-tabs.ts b/src/components/ha-tabs.ts index f28f8d7766..60d724a247 100644 --- a/src/components/ha-tabs.ts +++ b/src/components/ha-tabs.ts @@ -1,6 +1,6 @@ -import "@polymer/paper-tabs/paper-tabs"; import type { PaperIconButtonElement } from "@polymer/paper-icon-button/paper-icon-button"; import type { PaperTabElement } from "@polymer/paper-tabs/paper-tab"; +import "@polymer/paper-tabs/paper-tabs"; import type { PaperTabsElement } from "@polymer/paper-tabs/paper-tabs"; import { customElement } from "lit-element"; import { Constructor } from "../types"; diff --git a/src/components/ha-target-picker.ts b/src/components/ha-target-picker.ts index 2588846a66..c324b6b9eb 100644 --- a/src/components/ha-target-picker.ts +++ b/src/components/ha-target-picker.ts @@ -1,3 +1,16 @@ +// @ts-ignore +import chipStyles from "@material/chips/dist/mdc.chips.min.css"; +import "@material/mwc-button/mwc-button"; +import "@material/mwc-icon-button/mwc-icon-button"; +import { + mdiClose, + mdiDevices, + mdiPlus, + mdiSofa, + mdiUnfoldMoreVertical, +} from "@mdi/js"; +import "@polymer/paper-tooltip/paper-tooltip"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResult, @@ -9,22 +22,12 @@ import { query, unsafeCSS, } from "lit-element"; -import { HomeAssistant } from "../types"; -// @ts-ignore -import chipStyles from "@material/chips/dist/mdc.chips.min.css"; -import { - mdiSofa, - mdiDevices, - mdiClose, - mdiPlus, - mdiUnfoldMoreVertical, -} from "@mdi/js"; -import "./ha-svg-icon"; -import "./ha-icon"; -import "@material/mwc-icon-button/mwc-icon-button"; import { classMap } from "lit-html/directives/class-map"; -import "@material/mwc-button/mwc-button"; -import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { fireEvent } from "../common/dom/fire_event"; +import { ensureArray } from "../common/ensure-array"; +import { computeDomain } from "../common/entity/compute_domain"; +import { computeStateName } from "../common/entity/compute_state_name"; +import { stateIcon } from "../common/entity/state_icon"; import { AreaRegistryEntry, subscribeAreaRegistry, @@ -38,19 +41,16 @@ import { EntityRegistryEntry, subscribeEntityRegistry, } from "../data/entity_registry"; -import { SubscribeMixin } from "../mixins/subscribe-mixin"; -import { computeStateName } from "../common/entity/compute_state_name"; -import { stateIcon } from "../common/entity/state_icon"; -import { fireEvent } from "../common/dom/fire_event"; -import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker"; -import { computeDomain } from "../common/entity/compute_domain"; import { Target } from "../data/target"; -import { ensureArray } from "../common/ensure-array"; -import "./entity/ha-entity-picker"; +import { SubscribeMixin } from "../mixins/subscribe-mixin"; +import { HomeAssistant } from "../types"; import "./device/ha-device-picker"; -import "./ha-area-picker"; +import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker"; +import "./entity/ha-entity-picker"; import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker"; -import "@polymer/paper-tooltip/paper-tooltip"; +import "./ha-area-picker"; +import "./ha-icon"; +import "./ha-svg-icon"; @customElement("ha-target-picker") export class HaTargetPicker extends SubscribeMixin(LitElement) { diff --git a/src/components/ha-time-input.ts b/src/components/ha-time-input.ts new file mode 100644 index 0000000000..e72e1a09d9 --- /dev/null +++ b/src/components/ha-time-input.ts @@ -0,0 +1,146 @@ +import { + customElement, + html, + LitElement, + property, + query, + TemplateResult, +} from "lit-element"; +import { fireEvent } from "../common/dom/fire_event"; +import "./paper-time-input"; + +export interface HaTimeData { + hours?: number; + minutes?: number; + seconds?: number; + milliseconds?: number; +} + +@customElement("ha-time-input") +class HaTimeInput extends LitElement { + @property() public data!: HaTimeData; + + @property() public label?: string; + + @property() public suffix?: string; + + @property({ type: Boolean }) public required?: boolean; + + @property({ type: Boolean }) public enableMillisecond?: boolean; + + @query("paper-time-input", true) private _input?: HTMLElement; + + public focus() { + if (this._input) { + this._input.focus(); + } + } + + protected render(): TemplateResult { + return html` + + `; + } + + private get _hours() { + return this.data && this.data.hours ? Number(this.data.hours) : 0; + } + + private get _minutes() { + return this.data && this.data.minutes ? Number(this.data.minutes) : 0; + } + + private get _seconds() { + return this.data && this.data.seconds ? Number(this.data.seconds) : 0; + } + + private get _milliseconds() { + return this.data && this.data.milliseconds + ? Number(this.data.milliseconds) + : 0; + } + + private _parseDuration(value) { + return value.toString().padStart(2, "0"); + } + + private _parseDurationMillisec(value) { + return value.toString().padStart(3, "0"); + } + + private _hourChanged(ev) { + this._durationChanged(ev, "hours"); + } + + private _minChanged(ev) { + this._durationChanged(ev, "minutes"); + } + + private _secChanged(ev) { + this._durationChanged(ev, "seconds"); + } + + private _millisecChanged(ev) { + this._durationChanged(ev, "milliseconds"); + } + + private _durationChanged(ev, unit) { + let value = Number(ev.detail.value); + + if (value === this[`_${unit}`]) { + return; + } + + let hours = this._hours; + let minutes = this._minutes; + + if (unit === "seconds" && value > 59) { + minutes += Math.floor(value / 60); + value %= 60; + } + + if (unit === "minutes" && value > 59) { + hours += Math.floor(value / 60); + value %= 60; + } + + fireEvent(this, "value-changed", { + value: { + hours, + minutes, + seconds: this._seconds, + milliseconds: this._milliseconds, + ...{ [unit]: value }, + }, + }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-time-input": HaTimeInput; + } +} diff --git a/src/components/ha-water_heater-control.js b/src/components/ha-water_heater-control.js index ace62634d0..9a3403b36d 100644 --- a/src/components/ha-water_heater-control.js +++ b/src/components/ha-water_heater-control.js @@ -1,9 +1,9 @@ import "@polymer/iron-flex-layout/iron-flex-layout-classes"; -import "./ha-icon-button"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; import { EventsMixin } from "../mixins/events-mixin"; +import "./ha-icon-button"; /* * @appliesMixin EventsMixin diff --git a/src/components/ha-yaml-editor.ts b/src/components/ha-yaml-editor.ts index 4d2f4c2c59..473a7dd41a 100644 --- a/src/components/ha-yaml-editor.ts +++ b/src/components/ha-yaml-editor.ts @@ -2,9 +2,9 @@ import { safeDump, safeLoad } from "js-yaml"; import { customElement, html, + internalProperty, LitElement, property, - internalProperty, query, TemplateResult, } from "lit-element"; diff --git a/src/components/map/ha-locations-editor.ts b/src/components/map/ha-locations-editor.ts index 8b67b9ff2e..184813c32e 100644 --- a/src/components/map/ha-locations-editor.ts +++ b/src/components/map/ha-locations-editor.ts @@ -21,8 +21,8 @@ import { import { fireEvent } from "../../common/dom/fire_event"; import { LeafletModuleType, - setupLeafletMap, replaceTileLayer, + setupLeafletMap, } from "../../common/dom/setup-leaflet-map"; import { defaultRadiusColor } from "../../data/zone"; import { HomeAssistant } from "../../types"; diff --git a/src/components/map/ha-map.ts b/src/components/map/ha-map.ts index b4bb36ea25..98113ab100 100644 --- a/src/components/map/ha-map.ts +++ b/src/components/map/ha-map.ts @@ -1,4 +1,3 @@ -import "../ha-icon-button"; import { Circle, Layer, Map, Marker, TileLayer } from "leaflet"; import { css, @@ -12,14 +11,15 @@ import { } from "lit-element"; import { LeafletModuleType, - setupLeafletMap, replaceTileLayer, + setupLeafletMap, } from "../../common/dom/setup-leaflet-map"; import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { computeStateName } from "../../common/entity/compute_state_name"; import { debounce } from "../../common/util/debounce"; import "../../panels/map/ha-entity-marker"; import { HomeAssistant } from "../../types"; +import "../ha-icon-button"; @customElement("ha-map") class HaMap extends LitElement { diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index f1fbfab1d6..4bcd1960f1 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -1,5 +1,4 @@ import "@material/mwc-button/mwc-button"; -import "../ha-fab"; import "@material/mwc-list/mwc-list"; import "@material/mwc-list/mwc-list-item"; import { mdiArrowLeft, mdiClose, mdiPlay, mdiPlus } from "@mdi/js"; @@ -43,6 +42,7 @@ import "../entity/ha-entity-picker"; import "../ha-button-menu"; import "../ha-card"; import "../ha-circular-progress"; +import "../ha-fab"; import "../ha-paper-dropdown-menu"; import "../ha-svg-icon"; diff --git a/src/components/paper-time-input.js b/src/components/paper-time-input.js index 7ec7cc64ad..944360227d 100644 --- a/src/components/paper-time-input.js +++ b/src/components/paper-time-input.js @@ -103,6 +103,10 @@ export class PaperTimeInput extends PolymerElement { [hidden] { display: none !important; } + + #millisec { + width: 38px; + } @@ -167,6 +171,28 @@ export class PaperTimeInput extends PolymerElement { always-float-label$="[[alwaysFloatInputLabels]]" disabled="[[disabled]]" hidden$="[[!enableSecond]]" + > + : + + + + @@ -263,6 +289,13 @@ export class PaperTimeInput extends PolymerElement { type: String, notify: true, }, + /** + * milli second + */ + millisec: { + type: String, + notify: true, + }, /** * Suffix for the hour input */ @@ -284,6 +317,13 @@ export class PaperTimeInput extends PolymerElement { type: String, value: "", }, + /** + * Suffix for the milli sec input + */ + millisecLabel: { + type: String, + value: "", + }, /** * show the sec field */ @@ -291,6 +331,13 @@ export class PaperTimeInput extends PolymerElement { type: Boolean, value: false, }, + /** + * show the milli sec field + */ + enableMillisecond: { + type: Boolean, + value: false, + }, /** * limit hours input */ @@ -313,7 +360,7 @@ export class PaperTimeInput extends PolymerElement { type: String, notify: true, readOnly: true, - computed: "_computeTime(min, hour, sec, amPm)", + computed: "_computeTime(min, hour, sec, millisec, amPm)", }, }; } @@ -332,6 +379,10 @@ export class PaperTimeInput extends PolymerElement { if (this.enableSecond && !this.$.sec.validate()) { valid = false; } + // Validate milli second field + if (this.enableMillisecond && !this.$.millisec.validate()) { + valid = false; + } // Validate AM PM if 12 hour time if (this.format === 12 && !this.$.dropdown.validate()) { valid = false; @@ -342,17 +393,27 @@ export class PaperTimeInput extends PolymerElement { /** * Create time string */ - _computeTime(min, hour, sec, amPm) { + _computeTime(min, hour, sec, millisec, amPm) { let str; - if (hour || min || (sec && this.enableSecond)) { + if ( + hour || + min || + (sec && this.enableSecond) || + (millisec && this.enableMillisecond) + ) { hour = hour || "00"; min = min || "00"; sec = sec || "00"; + millisec = millisec || "000"; str = hour + ":" + min; // add sec field if (this.enableSecond && sec) { str = str + ":" + sec; } + // add milli sec field + if (this.enableMillisecond && millisec) { + str = str + ":" + millisec; + } // No ampm on 24 hr time if (this.format === 12) { str = str + " " + amPm; @@ -366,6 +427,15 @@ export class PaperTimeInput extends PolymerElement { ev.target.inputElement.inputElement.select(); } + /** + * Format milli sec + */ + _formatMillisec() { + if (this.millisec.toString().length === 1) { + this.millisec = this.millisec.toString().padStart(3, "0"); + } + } + /** * Format sec */ diff --git a/src/components/state-history-charts.js b/src/components/state-history-charts.js deleted file mode 100644 index 3127f00fac..0000000000 --- a/src/components/state-history-charts.js +++ /dev/null @@ -1,111 +0,0 @@ -import "./ha-circular-progress"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import LocalizeMixin from "../mixins/localize-mixin"; -import "./state-history-chart-line"; -import "./state-history-chart-timeline"; - -class StateHistoryCharts extends LocalizeMixin(PolymerElement) { - static get template() { - return html` - - - - - - - - - `; - } - - static get properties() { - return { - hass: Object, - historyData: { - type: Object, - value: null, - }, - names: Object, - - isLoadingData: Boolean, - - endTime: { - type: Object, - }, - - upToNow: Boolean, - noSingle: Boolean, - }; - } - - _computeIsSingleLineChart(data, noSingle) { - return !noSingle && data && data.length === 1; - } - - _computeIsEmpty(isLoadingData, historyData) { - const historyDataEmpty = - !historyData || - !historyData.timeline || - !historyData.line || - (historyData.timeline.length === 0 && historyData.line.length === 0); - return !isLoadingData && historyDataEmpty; - } - - _computeIsLoading(isLoading) { - return isLoading && !this.historyData; - } - - _computeEndTime(endTime, upToNow) { - // We don't really care about the value of historyData, but if it change we want to update - // endTime. - return upToNow ? new Date() : endTime; - } -} -customElements.define("state-history-charts", StateHistoryCharts); diff --git a/src/components/state-history-charts.ts b/src/components/state-history-charts.ts new file mode 100644 index 0000000000..a8faeb80d9 --- /dev/null +++ b/src/components/state-history-charts.ts @@ -0,0 +1,116 @@ +import "./ha-circular-progress"; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, +} from "lit-element"; +import "./state-history-chart-line"; +import "./state-history-chart-timeline"; +import { isComponentLoaded } from "../common/config/is_component_loaded"; +import type { HomeAssistant } from "../types"; +import { HistoryResult } from "../data/history"; + +@customElement("state-history-charts") +class StateHistoryCharts extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public historyData!: HistoryResult; + + @property({ type: Boolean }) public names = false; + + @property({ attribute: false }) public endTime?: Date; + + @property({ type: Boolean }) public upToNow = false; + + @property({ type: Boolean, attribute: "no-single" }) public noSingle = false; + + @property({ type: Boolean }) public isLoadingData = false; + + protected render(): TemplateResult { + if (!isComponentLoaded(this.hass, "history")) { + return html`
+ ${this.hass.localize("ui.components.history_charts.history_disabled")} +
`; + } + + if (this.isLoadingData && !this.historyData) { + return html`
+ ${this.hass.localize("ui.components.history_charts.loading_history")} +
`; + } + + if (this._isHistoryEmpty()) { + return html`
+ ${this.hass.localize("ui.components.history_charts.no_history_found")} +
`; + } + + const computedEndTime = this.upToNow + ? new Date() + : this.endTime || new Date(); + + return html` + ${this.historyData.timeline.length + ? html` + + ` + : html``} + ${this.historyData.line.map( + (line) => html` + + ` + )} + `; + } + + private _isHistoryEmpty(): boolean { + const historyDataEmpty = + !this.historyData || + !this.historyData.timeline || + !this.historyData.line || + (this.historyData.timeline.length === 0 && + this.historyData.line.length === 0); + return !this.isLoadingData && historyDataEmpty; + } + + static get styles(): CSSResult { + return css` + :host { + display: block; + /* height of single timeline chart = 58px */ + min-height: 58px; + } + .info { + text-align: center; + line-height: 58px; + color: var(--secondary-text-color); + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "state-history-charts": StateHistoryCharts; + } +} diff --git a/src/components/user/ha-user-picker.ts b/src/components/user/ha-user-picker.ts index 6b3e865c05..e8cd417bc6 100644 --- a/src/components/user/ha-user-picker.ts +++ b/src/components/user/ha-user-picker.ts @@ -1,5 +1,4 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light"; -import "../ha-icon-button"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-item-body"; @@ -17,6 +16,7 @@ import { fireEvent } from "../../common/dom/fire_event"; import { compare } from "../../common/string/compare"; import { fetchUsers, User } from "../../data/user"; import { HomeAssistant } from "../../types"; +import "../ha-icon-button"; import "./ha-user-badge"; class HaUserPicker extends LitElement { diff --git a/src/components/user/ha-users-picker.ts b/src/components/user/ha-users-picker.ts index d4fac81d48..36a8012596 100644 --- a/src/components/user/ha-users-picker.ts +++ b/src/components/user/ha-users-picker.ts @@ -1,3 +1,4 @@ +import { mdiClose } from "@mdi/js"; import { css, CSSResult, @@ -7,14 +8,13 @@ import { property, TemplateResult, } from "lit-element"; +import { guard } from "lit-html/directives/guard"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../common/dom/fire_event"; +import { fetchUsers, User } from "../../data/user"; import type { PolymerChangedEvent } from "../../polymer-types"; import type { HomeAssistant } from "../../types"; -import { fetchUsers, User } from "../../data/user"; import "./ha-user-picker"; -import { mdiClose } from "@mdi/js"; -import memoizeOne from "memoize-one"; -import { guard } from "lit-html/directives/guard"; @customElement("ha-users-picker") class HaUsersPickerLight extends LitElement { diff --git a/src/data/area_registry.ts b/src/data/area_registry.ts index d59a7ff69d..563af65b47 100644 --- a/src/data/area_registry.ts +++ b/src/data/area_registry.ts @@ -1,8 +1,8 @@ import { Connection, createCollection } from "home-assistant-js-websocket"; +import { Store } from "home-assistant-js-websocket/dist/store"; import { compare } from "../common/string/compare"; import { debounce } from "../common/util/debounce"; import { HomeAssistant } from "../types"; -import { Store } from "home-assistant-js-websocket/dist/store"; export interface AreaRegistryEntry { area_id: string; diff --git a/src/data/automation.ts b/src/data/automation.ts index 272416abb0..058ea73223 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -157,6 +157,7 @@ export interface StateCondition { entity_id: string; attribute?: string; state: string | number; + for?: string | number | ForDict; } export interface NumericStateCondition { diff --git a/src/data/calendar.ts b/src/data/calendar.ts index fc27c71253..0080a2bc5c 100644 --- a/src/data/calendar.ts +++ b/src/data/calendar.ts @@ -1,7 +1,7 @@ -import type { HomeAssistant, CalendarEvent } from "../types"; -import { computeDomain } from "../common/entity/compute_domain"; import { HA_COLOR_PALETTE } from "../common/const"; +import { computeDomain } from "../common/entity/compute_domain"; import { computeStateName } from "../common/entity/compute_state_name"; +import type { CalendarEvent, HomeAssistant } from "../types"; export interface Calendar { entity_id: string; diff --git a/src/data/cloud.ts b/src/data/cloud.ts index e261805f50..3aa285be0d 100644 --- a/src/data/cloud.ts +++ b/src/data/cloud.ts @@ -41,6 +41,7 @@ export interface CloudPreferences { }; alexa_report_state: boolean; google_report_state: boolean; + tts_default_voice: [string, string]; } export type CloudStatusLoggedIn = CloudStatusBase & { @@ -113,6 +114,7 @@ export const updateCloudPref = ( google_report_state?: CloudPreferences["google_report_state"]; google_default_expose?: CloudPreferences["google_default_expose"]; google_secure_devices_pin?: CloudPreferences["google_secure_devices_pin"]; + tts_default_voice?: CloudPreferences["tts_default_voice"]; } ) => hass.callWS({ @@ -144,3 +146,10 @@ export const updateCloudAlexaEntityConfig = ( entity_id: entityId, ...values, }); + +export interface CloudTTSInfo { + languages: Array<[string, string]>; +} + +export const getCloudTTSInfo = (hass: HomeAssistant) => + hass.callWS({ type: "cloud/tts/info" }); diff --git a/src/data/config_flow.ts b/src/data/config_flow.ts index d322f16ec9..6a3a8ca8db 100644 --- a/src/data/config_flow.ts +++ b/src/data/config_flow.ts @@ -7,6 +7,7 @@ import { domainToName } from "./integration"; export const DISCOVERY_SOURCES = [ "unignore", + "dhcp", "homekit", "ssdp", "zeroconf", @@ -51,8 +52,12 @@ export const handleConfigFlowStep = ( HEADERS ); -export const ignoreConfigFlow = (hass: HomeAssistant, flowId: string) => - hass.callWS({ type: "config_entries/ignore_flow", flow_id: flowId }); +export const ignoreConfigFlow = ( + hass: HomeAssistant, + flowId: string, + title: string +) => + hass.callWS({ type: "config_entries/ignore_flow", flow_id: flowId, title }); export const deleteConfigFlow = (hass: HomeAssistant, flowId: string) => hass.callApi("DELETE", `config/config_entries/flow/${flowId}`); diff --git a/src/data/device_automation.ts b/src/data/device_automation.ts index 5fd7714f7f..8319f8772b 100644 --- a/src/data/device_automation.ts +++ b/src/data/device_automation.ts @@ -1,6 +1,6 @@ import { computeStateName } from "../common/entity/compute_state_name"; -import { HomeAssistant } from "../types"; import { HaFormSchema } from "../components/ha-form/ha-form"; +import { HomeAssistant } from "../types"; export interface DeviceAutomation { device_id: string; diff --git a/src/data/device_registry.ts b/src/data/device_registry.ts index 7d52f8dea9..8fc1807586 100644 --- a/src/data/device_registry.ts +++ b/src/data/device_registry.ts @@ -71,7 +71,7 @@ export const updateDeviceRegistryEntry = ( ...updates, }); -const fetchDeviceRegistry = (conn) => +export const fetchDeviceRegistry = (conn) => conn.sendMessagePromise({ type: "config/device_registry/list", }); diff --git a/src/data/entity.ts b/src/data/entity.ts index f8dc2efe42..0ade5b2d15 100644 --- a/src/data/entity.ts +++ b/src/data/entity.ts @@ -27,6 +27,7 @@ export const ENTITY_COMPONENT_DOMAINS = [ "lock", "mailbox", "media_player", + "number", "person", "plant", "remember_the_milk", diff --git a/src/data/hassio/addon.ts b/src/data/hassio/addon.ts index 902548f174..5e05ae05bc 100644 --- a/src/data/hassio/addon.ts +++ b/src/data/hassio/addon.ts @@ -61,6 +61,7 @@ export interface HassioAddonDetails extends HassioAddonInfo { privileged: any; protected: boolean; rating: "1-6"; + schema: Record; services_role: string[]; slug: string; startup: "initialize" | "system" | "services" | "application" | "once"; diff --git a/src/data/iconsets.ts b/src/data/iconsets.ts index 941f84cd74..2e6df41914 100644 --- a/src/data/iconsets.ts +++ b/src/data/iconsets.ts @@ -1,6 +1,6 @@ +import { clear, get, set, Store } from "idb-keyval"; import { iconMetadata } from "../resources/icon-metadata"; import { IconMeta } from "../types"; -import { get, set, clear, Store } from "idb-keyval"; export interface Icons { [key: string]: string; diff --git a/src/data/media-player.ts b/src/data/media-player.ts index 84a7d03034..95b5156c33 100644 --- a/src/data/media-player.ts +++ b/src/data/media-player.ts @@ -20,9 +20,9 @@ import type { HassEntityAttributeBase, HassEntityBase, } from "home-assistant-js-websocket"; +import { supportsFeature } from "../common/entity/supports-feature"; import type { HomeAssistant } from "../types"; import { UNAVAILABLE_STATES } from "./entity"; -import { supportsFeature } from "../common/entity/supports-feature"; interface MediaPlayerEntityAttributes extends HassEntityAttributeBase { media_content_type?: any; diff --git a/src/data/ozw.ts b/src/data/ozw.ts index 0e5d73565a..b8af5798f1 100644 --- a/src/data/ozw.ts +++ b/src/data/ozw.ts @@ -73,6 +73,14 @@ export interface OZWDeviceConfig { help: string; } +export interface OZWMigrationData { + migration_device_map: Record; + zwave_entity_ids: string[]; + ozw_entity_ids: string[]; + migration_entity_map: Record; + migrated: boolean; +} + export const nodeQueryStages = [ "ProtocolInfo", "Probe", @@ -147,7 +155,7 @@ export const fetchOZWNetworkStatus = ( ): Promise => hass.callWS({ type: "ozw/network_status", - ozw_instance: ozw_instance, + ozw_instance, }); export const fetchOZWNetworkStatistics = ( @@ -156,7 +164,7 @@ export const fetchOZWNetworkStatistics = ( ): Promise => hass.callWS({ type: "ozw/network_statistics", - ozw_instance: ozw_instance, + ozw_instance, }); export const fetchOZWNodes = ( @@ -165,7 +173,7 @@ export const fetchOZWNodes = ( ): Promise => hass.callWS({ type: "ozw/get_nodes", - ozw_instance: ozw_instance, + ozw_instance, }); export const fetchOZWNodeStatus = ( @@ -175,8 +183,8 @@ export const fetchOZWNodeStatus = ( ): Promise => hass.callWS({ type: "ozw/node_status", - ozw_instance: ozw_instance, - node_id: node_id, + ozw_instance, + node_id, }); export const fetchOZWNodeMetadata = ( @@ -186,8 +194,8 @@ export const fetchOZWNodeMetadata = ( ): Promise => hass.callWS({ type: "ozw/node_metadata", - ozw_instance: ozw_instance, - node_id: node_id, + ozw_instance, + node_id, }); export const fetchOZWNodeConfig = ( @@ -197,8 +205,8 @@ export const fetchOZWNodeConfig = ( ): Promise => hass.callWS({ type: "ozw/get_config_parameters", - ozw_instance: ozw_instance, - node_id: node_id, + ozw_instance, + node_id, }); export const refreshNodeInfo = ( @@ -208,6 +216,15 @@ export const refreshNodeInfo = ( ): Promise => hass.callWS({ type: "ozw/refresh_node_info", - ozw_instance: ozw_instance, - node_id: node_id, + ozw_instance, + node_id, + }); + +export const migrateZwave = ( + hass: HomeAssistant, + dry_run = true +): Promise => + hass.callWS({ + type: "ozw/migrate_zwave", + dry_run, }); diff --git a/src/data/script.ts b/src/data/script.ts index a7cfa7506a..e528754f5e 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -45,8 +45,15 @@ export interface DeviceAction { entity_id: string; } +export interface DelayActionParts { + milliseconds?: number; + seconds?: number; + minutes?: number; + hours?: number; + days?: number; +} export interface DelayAction { - delay: number; + delay: number | Partial; } export interface SceneAction { diff --git a/src/data/selector.ts b/src/data/selector.ts index 82749ae90c..e045ce8b88 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -6,7 +6,9 @@ export type Selector = | NumberSelector | BooleanSelector | TimeSelector - | ActionSelector; + | ActionSelector + | StringSelector + | ObjectSelector; export interface EntitySelector { entity: { @@ -82,3 +84,14 @@ export interface ActionSelector { // eslint-disable-next-line @typescript-eslint/ban-types action: {}; } + +export interface StringSelector { + text: { + multiline: boolean; + }; +} + +export interface ObjectSelector { + // eslint-disable-next-line @typescript-eslint/ban-types + object: {}; +} diff --git a/src/data/shopping-list.ts b/src/data/shopping-list.ts index d3dbdd8327..1036cde3f1 100644 --- a/src/data/shopping-list.ts +++ b/src/data/shopping-list.ts @@ -38,3 +38,12 @@ export const addItem = ( type: "shopping_list/items/add", name, }); + +export const reorderItems = ( + hass: HomeAssistant, + itemIds: [string] +): Promise => + hass.callWS({ + type: "shopping_list/items/reorder", + item_ids: itemIds, + }); diff --git a/src/data/supervisor/core.ts b/src/data/supervisor/core.ts new file mode 100644 index 0000000000..611fbabd36 --- /dev/null +++ b/src/data/supervisor/core.ts @@ -0,0 +1,10 @@ +import { HomeAssistant } from "../../types"; +import { HassioResponse } from "../hassio/common"; + +export const restartCore = async (hass: HomeAssistant) => { + await hass.callService("homeassistant", "restart"); +}; + +export const updateCore = async (hass: HomeAssistant) => { + await hass.callApi>("POST", `hassio/core/update`); +}; diff --git a/src/data/tag.ts b/src/data/tag.ts index 870777f7cb..b3d4c7bd86 100644 --- a/src/data/tag.ts +++ b/src/data/tag.ts @@ -1,5 +1,5 @@ -import { HomeAssistant } from "../types"; import { HassEventBase } from "home-assistant-js-websocket"; +import { HomeAssistant } from "../types"; export const EVENT_TAG_SCANNED = "tag_scanned"; diff --git a/src/data/tts.ts b/src/data/tts.ts new file mode 100644 index 0000000000..23404087b9 --- /dev/null +++ b/src/data/tts.ts @@ -0,0 +1,12 @@ +import { HomeAssistant } from "../types"; + +export const convertTextToSpeech = ( + hass: HomeAssistant, + data: { + platform: string; + message: string; + cache?: boolean; + language?: string; + options?: Record; + } +) => hass.callApi<{ url: string }>("POST", "tts_get_url", data); diff --git a/src/data/zwave.ts b/src/data/zwave.ts index 77740c8051..34c336322e 100644 --- a/src/data/zwave.ts +++ b/src/data/zwave.ts @@ -42,6 +42,11 @@ export interface ZWaveAttributes { wake_up_interval?: number; } +export interface ZWaveMigrationConfig { + usb_path: string; + network_key: string; +} + export const ZWAVE_NETWORK_STATE_STOPPED = 0; export const ZWAVE_NETWORK_STATE_FAILED = 1; export const ZWAVE_NETWORK_STATE_STARTED = 5; @@ -55,6 +60,20 @@ export const fetchNetworkStatus = ( type: "zwave/network_status", }); +export const startOzwConfigFlow = ( + hass: HomeAssistant +): Promise<{ flow_id: string }> => + hass.callWS({ + type: "zwave/start_ozw_config_flow", + }); + +export const fetchMigrationConfig = ( + hass: HomeAssistant +): Promise => + hass.callWS({ + type: "zwave/get_migration_config", + }); + export const fetchValues = (hass: HomeAssistant, nodeId: number) => hass.callApi("GET", `zwave/values/${nodeId}`); diff --git a/src/data/zwave_js.ts b/src/data/zwave_js.ts new file mode 100644 index 0000000000..8dd77a3335 --- /dev/null +++ b/src/data/zwave_js.ts @@ -0,0 +1,80 @@ +import { HomeAssistant } from "../types"; +import { DeviceRegistryEntry } from "./device_registry"; + +export interface ZWaveJSNodeIdentifiers { + home_id: string; + node_id: number; +} +export interface ZWaveJSNetwork { + client: ZWaveJSClient; + controller: ZWaveJSController; +} + +export interface ZWaveJSClient { + state: string; + ws_server_url: string; + server_version: string; + driver_version: string; +} + +export interface ZWaveJSController { + home_id: string; + nodes: number[]; +} + +export interface ZWaveJSNode { + node_id: number; + ready: boolean; + status: number; +} + +export enum NodeStatus { + Unknown, + Asleep, + Awake, + Dead, + Alive, +} + +export const nodeStatus = ["unknown", "asleep", "awake", "dead", "alive"]; + +export const fetchNetworkStatus = ( + hass: HomeAssistant, + entry_id: string +): Promise => + hass.callWS({ + type: "zwave_js/network_status", + entry_id, + }); + +export const fetchNodeStatus = ( + hass: HomeAssistant, + entry_id: string, + node_id: number +): Promise => + hass.callWS({ + type: "zwave_js/node_status", + entry_id, + node_id, + }); + +export const getIdentifiersFromDevice = function ( + device: DeviceRegistryEntry +): ZWaveJSNodeIdentifiers | undefined { + if (!device) { + return undefined; + } + + const zwaveJSIdentifier = device.identifiers.find( + (identifier) => identifier[0] === "zwave_js" + ); + if (!zwaveJSIdentifier) { + return undefined; + } + + const identifiers = zwaveJSIdentifier[1].split("-"); + return { + node_id: parseInt(identifiers[1]), + home_id: identifiers[0], + }; +}; diff --git a/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts b/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts index 54ab1c7f41..ece523c8fa 100644 --- a/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts +++ b/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts @@ -4,16 +4,17 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; -import "../../components/ha-dialog"; -import "../../components/ha-circular-progress"; -import "../../components/ha-switch"; -import "../../components/ha-formfield"; import { fireEvent } from "../../common/dom/fire_event"; +import { computeRTLDirection } from "../../common/util/compute_rtl"; +import "../../components/ha-circular-progress"; +import "../../components/ha-dialog"; +import "../../components/ha-formfield"; +import "../../components/ha-switch"; import type { HaSwitch } from "../../components/ha-switch"; import { getConfigEntrySystemOptions, @@ -22,7 +23,6 @@ import { import { haStyleDialog } from "../../resources/styles"; import type { HomeAssistant } from "../../types"; import { ConfigEntrySystemOptionsDialogParams } from "./show-dialog-config-entry-system-options"; -import { computeRTLDirection } from "../../common/util/compute_rtl"; @customElement("dialog-config-entry-system-options") class DialogConfigEntrySystemOptions extends LitElement { diff --git a/src/dialogs/config-flow/step-flow-pick-handler.ts b/src/dialogs/config-flow/step-flow-pick-handler.ts index a95f31637b..774c9778b0 100644 --- a/src/dialogs/config-flow/step-flow-pick-handler.ts +++ b/src/dialogs/config-flow/step-flow-pick-handler.ts @@ -6,9 +6,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import { classMap } from "lit-html/directives/class-map"; @@ -20,10 +20,10 @@ import { LocalizeFunc } from "../../common/translations/localize"; import "../../components/ha-icon-next"; import { domainToName } from "../../data/integration"; import { HomeAssistant } from "../../types"; +import { brandsUrl } from "../../util/brands-url"; import { documentationUrl } from "../../util/documentation-url"; import { FlowConfig } from "./show-dialog-data-entry-flow"; import { configFlowContentStyles } from "./styles"; -import { brandsUrl } from "../../util/brands-url"; interface HandlerObj { name: string; diff --git a/src/dialogs/generic/dialog-box.ts b/src/dialogs/generic/dialog-box.ts index 059fff683b..f76c7ae959 100644 --- a/src/dialogs/generic/dialog-box.ts +++ b/src/dialogs/generic/dialog-box.ts @@ -57,7 +57,7 @@ class DialogBox extends LitElement { open ?scrimClickAction=${confirmPrompt} ?escapeKeyAction=${confirmPrompt} - @closed=${this._dialogClosed} + @closing=${this._dialogClosed} defaultAction="ignore" .heading=${this._params.title ? this._params.title diff --git a/src/dialogs/image-cropper-dialog/image-cropper-dialog.ts b/src/dialogs/image-cropper-dialog/image-cropper-dialog.ts index c2ab0ca148..82dd0bc958 100644 --- a/src/dialogs/image-cropper-dialog/image-cropper-dialog.ts +++ b/src/dialogs/image-cropper-dialog/image-cropper-dialog.ts @@ -15,11 +15,11 @@ import { TemplateResult, unsafeCSS, } from "lit-element"; +import { classMap } from "lit-html/directives/class-map"; import "../../components/ha-dialog"; import { haStyleDialog } from "../../resources/styles"; import type { HomeAssistant } from "../../types"; import { HaImageCropperDialogParams } from "./show-image-cropper-dialog"; -import { classMap } from "lit-html/directives/class-map"; @customElement("image-cropper-dialog") export class HaImagecropperDialog extends LitElement { diff --git a/src/dialogs/make-dialog-manager.ts b/src/dialogs/make-dialog-manager.ts index 7e975ca204..a7c496d960 100644 --- a/src/dialogs/make-dialog-manager.ts +++ b/src/dialogs/make-dialog-manager.ts @@ -87,6 +87,10 @@ export const showDialog = async ( dialogElement.showDialog(dialogParams); }; +export const replaceDialog = () => { + history.replaceState({ ...history.state, replaced: true }, ""); +}; + export const closeDialog = async (dialogTag: string): Promise => { if (!(dialogTag in LOADED)) { return true; diff --git a/src/dialogs/more-info/controls/more-info-automation.ts b/src/dialogs/more-info/controls/more-info-automation.ts index 6a1a789b99..283ccc6084 100644 --- a/src/dialogs/more-info/controls/more-info-automation.ts +++ b/src/dialogs/more-info/controls/more-info-automation.ts @@ -11,8 +11,8 @@ import { } from "lit-element"; import "../../../components/ha-relative-time"; import { triggerAutomation } from "../../../data/automation"; -import { HomeAssistant } from "../../../types"; import { UNAVAILABLE_STATES } from "../../../data/entity"; +import { HomeAssistant } from "../../../types"; @customElement("more-info-automation") class MoreInfoAutomation extends LitElement { @@ -57,7 +57,9 @@ class MoreInfoAutomation extends LitElement { } .actions { margin: 8px 0; - text-align: right; + display: flex; + flex-wrap: wrap; + justify-content: center; } `; } diff --git a/src/dialogs/more-info/controls/more-info-camera.ts b/src/dialogs/more-info/controls/more-info-camera.ts index 5dc4a004ce..64808f950f 100644 --- a/src/dialogs/more-info/controls/more-info-camera.ts +++ b/src/dialogs/more-info/controls/more-info-camera.ts @@ -11,13 +11,14 @@ import { TemplateResult, } from "lit-element"; import { supportsFeature } from "../../../common/entity/supports-feature"; +import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import "../../../components/ha-camera-stream"; import { + CameraEntity, CameraPreferences, CAMERA_SUPPORT_STREAM, fetchCameraPrefs, updateCameraPrefs, - CameraEntity, } from "../../../data/camera"; import type { HomeAssistant } from "../../../types"; @@ -81,7 +82,7 @@ class MoreInfoCamera extends LitElement { if ( curEntityId && - this.hass!.config.components.includes("stream") && + isComponentLoaded(this.hass!, "stream") && supportsFeature(this.stateObj!, CAMERA_SUPPORT_STREAM) ) { // Fetch in background while we set up the video. diff --git a/src/dialogs/more-info/controls/more-info-configurator.js b/src/dialogs/more-info/controls/more-info-configurator.js index 4fb706631d..da7a9e6f97 100644 --- a/src/dialogs/more-info/controls/more-info-configurator.js +++ b/src/dialogs/more-info/controls/more-info-configurator.js @@ -2,10 +2,10 @@ import "@material/mwc-button"; import "@polymer/iron-flex-layout/iron-flex-layout-classes"; import "@polymer/iron-input/iron-input"; import "@polymer/paper-input/paper-input"; -import "../../../components/ha-circular-progress"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; +import "../../../components/ha-circular-progress"; import "../../../components/ha-markdown"; class MoreInfoConfigurator extends PolymerElement { diff --git a/src/dialogs/more-info/controls/more-info-counter.ts b/src/dialogs/more-info/controls/more-info-counter.ts index 6439386425..330f504f50 100644 --- a/src/dialogs/more-info/controls/more-info-counter.ts +++ b/src/dialogs/more-info/controls/more-info-counter.ts @@ -9,6 +9,7 @@ import { property, TemplateResult, } from "lit-element"; +import { UNAVAILABLE_STATES } from "../../../data/entity"; import { HomeAssistant } from "../../../types"; @customElement("more-info-counter") @@ -22,21 +23,29 @@ class MoreInfoCounter extends LitElement { return html``; } + const disabled = UNAVAILABLE_STATES.includes(this.stateObj!.state); + return html`
${this.hass!.localize("ui.card.counter.actions.increment")} ${this.hass!.localize("ui.card.counter.actions.decrement")} - + ${this.hass!.localize("ui.card.counter.actions.reset")}
@@ -53,8 +62,7 @@ class MoreInfoCounter extends LitElement { static get styles(): CSSResult { return css` .actions { - margin: 0; - padding-top: 20px; + margin: 8px 0; display: flex; flex-wrap: wrap; justify-content: center; diff --git a/src/dialogs/more-info/controls/more-info-fan.js b/src/dialogs/more-info/controls/more-info-fan.js index e7e1b9c810..fde009e367 100644 --- a/src/dialogs/more-info/controls/more-info-fan.js +++ b/src/dialogs/more-info/controls/more-info-fan.js @@ -1,5 +1,4 @@ import "@polymer/iron-flex-layout/iron-flex-layout-classes"; -import "../../../components/ha-icon-button"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; import { html } from "@polymer/polymer/lib/utils/html-tag"; @@ -7,6 +6,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; import { attributeClassNames } from "../../../common/entity/attribute_class_names"; import "../../../components/ha-attributes"; +import "../../../components/ha-icon-button"; import "../../../components/ha-paper-dropdown-menu"; import "../../../components/ha-switch"; import { EventsMixin } from "../../../mixins/events-mixin"; diff --git a/src/dialogs/more-info/controls/more-info-group.ts b/src/dialogs/more-info/controls/more-info-group.ts index da06adf935..615bf3314d 100644 --- a/src/dialogs/more-info/controls/more-info-group.ts +++ b/src/dialogs/more-info/controls/more-info-group.ts @@ -1,10 +1,10 @@ import { HassEntity } from "home-assistant-js-websocket"; import { + css, + CSSResult, + internalProperty, LitElement, property, - CSSResult, - css, - internalProperty, PropertyValues, } from "lit-element"; import { html, TemplateResult } from "lit-html"; @@ -14,8 +14,8 @@ import { GroupEntity } from "../../../data/group"; import "../../../state-summary/state-card-content"; import { HomeAssistant } from "../../../types"; import { - importMoreInfoControl, domainMoreInfoType, + importMoreInfoControl, } from "../state_more_info_control"; class MoreInfoGroup extends LitElement { diff --git a/src/dialogs/more-info/controls/more-info-input_datetime.js b/src/dialogs/more-info/controls/more-info-input_datetime.js index b2e8c4453e..fad03c408a 100644 --- a/src/dialogs/more-info/controls/more-info-input_datetime.js +++ b/src/dialogs/more-info/controls/more-info-input_datetime.js @@ -3,8 +3,8 @@ import "@polymer/paper-input/paper-input"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; -import "../../../components/ha-date-input"; import { attributeClassNames } from "../../../common/entity/attribute_class_names"; +import "../../../components/ha-date-input"; import "../../../components/ha-relative-time"; import "../../../components/paper-time-input"; diff --git a/src/dialogs/more-info/controls/more-info-light.ts b/src/dialogs/more-info/controls/more-info-light.ts index 9296454f72..2ac9af776d 100644 --- a/src/dialogs/more-info/controls/more-info-light.ts +++ b/src/dialogs/more-info/controls/more-info-light.ts @@ -19,12 +19,12 @@ import "../../../components/ha-icon-button"; import "../../../components/ha-labeled-slider"; import "../../../components/ha-paper-dropdown-menu"; import { + LightEntity, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_WHITE_VALUE, - LightEntity, } from "../../../data/light"; import type { HomeAssistant } from "../../../types"; diff --git a/src/dialogs/more-info/controls/more-info-media_player.ts b/src/dialogs/more-info/controls/more-info-media_player.ts index 48e9432a96..95ebc74d26 100644 --- a/src/dialogs/more-info/controls/more-info-media_player.ts +++ b/src/dialogs/more-info/controls/more-info-media_player.ts @@ -27,6 +27,7 @@ import { UNAVAILABLE, UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity"; import { computeMediaControls, MediaPickedEvent, + MediaPlayerEntity, SUPPORT_BROWSE_MEDIA, SUPPORT_PLAY_MEDIA, SUPPORT_SELECT_SOUND_MODE, @@ -34,7 +35,6 @@ import { SUPPORT_VOLUME_BUTTONS, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - MediaPlayerEntity, } from "../../../data/media-player"; import { HomeAssistant } from "../../../types"; diff --git a/src/dialogs/more-info/controls/more-info-person.ts b/src/dialogs/more-info/controls/more-info-person.ts index 84fa6a733b..4caa2ea3d3 100644 --- a/src/dialogs/more-info/controls/more-info-person.ts +++ b/src/dialogs/more-info/controls/more-info-person.ts @@ -75,7 +75,7 @@ class MoreInfoPerson extends LitElement { justify-content: space-between; } .actions { - margin: 36px 0 8px 0; + margin: 8px 0; text-align: right; } ha-map { diff --git a/src/dialogs/more-info/controls/more-info-script.ts b/src/dialogs/more-info/controls/more-info-script.ts index 735d41429f..2f4b93eefe 100644 --- a/src/dialogs/more-info/controls/more-info-script.ts +++ b/src/dialogs/more-info/controls/more-info-script.ts @@ -1,5 +1,7 @@ import { HassEntity } from "home-assistant-js-websocket"; import { + css, + CSSResult, customElement, html, LitElement, @@ -21,10 +23,12 @@ class MoreInfoScript extends LitElement { } return html` -
- ${this.hass.localize( - "ui.dialogs.more_info_control.script.last_triggered" - )}: +
+
+ ${this.hass.localize( + "ui.dialogs.more_info_control.script.last_triggered" + )}: +
${this.stateObj.attributes.last_triggered ? html` `; } + + static get styles(): CSSResult { + return css` + .flex { + display: flex; + justify-content: space-between; + } + `; + } } declare global { diff --git a/src/dialogs/more-info/controls/more-info-timer.ts b/src/dialogs/more-info/controls/more-info-timer.ts index 3ffa9c5b16..80f8819009 100644 --- a/src/dialogs/more-info/controls/more-info-timer.ts +++ b/src/dialogs/more-info/controls/more-info-timer.ts @@ -76,8 +76,7 @@ class MoreInfoTimer extends LitElement { static get styles(): CSSResult { return css` .actions { - margin: 0; - padding-top: 20px; + margin: 8px 0; display: flex; flex-wrap: wrap; justify-content: center; diff --git a/src/dialogs/more-info/controls/more-info-vacuum.ts b/src/dialogs/more-info/controls/more-info-vacuum.ts index 8fbc1a1dd3..1e862c474a 100644 --- a/src/dialogs/more-info/controls/more-info-vacuum.ts +++ b/src/dialogs/more-info/controls/more-info-vacuum.ts @@ -1,5 +1,3 @@ -import "../../../components/ha-icon-button"; -import "../../../components/ha-icon"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; import { @@ -13,6 +11,8 @@ import { } from "lit-element"; import { supportsFeature } from "../../../common/entity/supports-feature"; import "../../../components/ha-attributes"; +import "../../../components/ha-icon"; +import "../../../components/ha-icon-button"; import "../../../components/ha-paper-dropdown-menu"; import { VacuumEntity, diff --git a/src/dialogs/more-info/controls/more-info-weather.ts b/src/dialogs/more-info/controls/more-info-weather.ts index 4d3c38ca90..d750957fbc 100644 --- a/src/dialogs/more-info/controls/more-info-weather.ts +++ b/src/dialogs/more-info/controls/more-info-weather.ts @@ -1,18 +1,3 @@ -import "../../../components/ha-svg-icon"; -import { HassEntity } from "home-assistant-js-websocket"; -import { - css, - CSSResult, - customElement, - LitElement, - property, - PropertyValues, -} from "lit-element"; -import { html, TemplateResult } from "lit-html"; -import { HomeAssistant } from "../../../types"; - -import { getWind, getWeatherUnit } from "../../../data/weather"; - import { mdiAlertCircleOutline, mdiEye, @@ -34,7 +19,22 @@ import { mdiWeatherWindy, mdiWeatherWindyVariant, } from "@mdi/js"; +import { HassEntity } from "home-assistant-js-websocket"; +import { + css, + CSSResult, + customElement, + LitElement, + property, + PropertyValues, +} from "lit-element"; +import { html, TemplateResult } from "lit-html"; +import { formatDateWeekday } from "../../../common/datetime/format_date"; +import { formatTimeWeekday } from "../../../common/datetime/format_time"; import { formatNumber } from "../../../common/string/format_number"; +import "../../../components/ha-svg-icon"; +import { getWeatherUnit, getWind } from "../../../data/weather"; +import { HomeAssistant } from "../../../types"; const weatherIcons = { "clear-night": mdiWeatherNight, @@ -182,14 +182,20 @@ class MoreInfoWeather extends LitElement { ${!this._showValue(item.templow) ? html`
- ${this.computeDateTime(item.datetime)} + ${formatTimeWeekday( + new Date(item.datetime), + this.hass.language + )}
` : ""} ${this._showValue(item.templow) ? html`
- ${this.computeDate(item.datetime)} + ${formatDateWeekday( + new Date(item.datetime), + this.hass.language + )}
${formatNumber(item.templow, this.hass!.language)} @@ -255,23 +261,6 @@ class MoreInfoWeather extends LitElement { `; } - private computeDate(data) { - const date = new Date(data); - return date.toLocaleDateString(this.hass.language, { - weekday: "long", - month: "short", - day: "numeric", - }); - } - - private computeDateTime(data) { - const date = new Date(data); - return date.toLocaleDateString(this.hass.language, { - weekday: "long", - hour: "numeric", - }); - } - private _showValue(item: string): boolean { return typeof item !== "undefined" && item !== null; } diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 3feedb9a50..5e58c4e013 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -20,22 +20,22 @@ import { import { fireEvent } from "../../common/dom/fire_event"; import { computeDomain } from "../../common/entity/compute_domain"; import { computeStateName } from "../../common/entity/compute_state_name"; - import { navigate } from "../../common/navigate"; import "../../components/ha-dialog"; import "../../components/ha-header-bar"; import "../../components/ha-svg-icon"; import { removeEntityRegistryEntry } from "../../data/entity_registry"; +import { CONTINUOUS_DOMAINS } from "../../data/logbook"; import { showEntityEditorDialog } from "../../panels/config/entities/show-dialog-entity-editor"; import { haStyleDialog } from "../../resources/styles"; import "../../state-summary/state-card-content"; import { HomeAssistant } from "../../types"; import { showConfirmationDialog } from "../generic/show-dialog-box"; +import "./controls/more-info-default"; import "./ha-more-info-history"; import "./ha-more-info-logbook"; -import "./controls/more-info-default"; -import { CONTINUOUS_DOMAINS } from "../../data/logbook"; import "./more-info-content"; +import { replaceDialog } from "../make-dialog-manager"; const DOMAINS_NO_INFO = ["camera", "configurator"]; /** @@ -294,6 +294,7 @@ export class MoreInfoDialog extends LitElement { } private _gotoSettings() { + replaceDialog(); showEntityEditorDialog(this, { entity_id: this._entityId!, }); diff --git a/src/dialogs/more-info/more-info-content.ts b/src/dialogs/more-info/more-info-content.ts index 47570226fa..d08ae9507a 100644 --- a/src/dialogs/more-info/more-info-content.ts +++ b/src/dialogs/more-info/more-info-content.ts @@ -1,10 +1,9 @@ import { HassEntity } from "home-assistant-js-websocket"; import { property, PropertyValues, UpdatingElement } from "lit-element"; - -import { HomeAssistant } from "../../types"; import dynamicContentUpdater from "../../common/dom/dynamic_content_updater"; -import { stateMoreInfoType } from "./state_more_info_control"; import { importMoreInfoControl } from "../../panels/lovelace/custom-card-helpers"; +import { HomeAssistant } from "../../types"; +import { stateMoreInfoType } from "./state_more_info_control"; class MoreInfoContent extends UpdatingElement { @property({ attribute: false }) public hass?: HomeAssistant; diff --git a/src/dialogs/notifications/notification-drawer.js b/src/dialogs/notifications/notification-drawer.js index d15ac99857..37a9582749 100644 --- a/src/dialogs/notifications/notification-drawer.js +++ b/src/dialogs/notifications/notification-drawer.js @@ -63,8 +63,8 @@ export class HuiNotificationDrawer extends EventsMixin(
[[localize('ui.notification_drawer.title')]]
-
diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts index 90ab5b654e..4db3b69260 100644 --- a/src/dialogs/quick-bar/ha-quick-bar.ts +++ b/src/dialogs/quick-bar/ha-quick-bar.ts @@ -18,11 +18,13 @@ import { ifDefined } from "lit-html/directives/if-defined"; import { styleMap } from "lit-html/directives/style-map"; import { scroll } from "lit-virtualizer"; import memoizeOne from "memoize-one"; +import { canShowPage } from "../../common/config/can_show_page"; import { componentsWithService } from "../../common/config/components_with_service"; import { fireEvent } from "../../common/dom/fire_event"; import { computeDomain } from "../../common/entity/compute_domain"; import { computeStateName } from "../../common/entity/compute_state_name"; import { domainIcon } from "../../common/entity/domain_icon"; +import { navigate } from "../../common/navigate"; import "../../common/search/search-input"; import { compare } from "../../common/string/compare"; import { @@ -34,6 +36,9 @@ import "../../components/ha-circular-progress"; import "../../components/ha-dialog"; import "../../components/ha-header-bar"; import { domainToName } from "../../data/integration"; +import { getPanelIcon, getPanelNameTranslationKey } from "../../data/panel"; +import { PageNavigation } from "../../layouts/hass-tabs-subpage"; +import { configSections } from "../../panels/config/ha-panel-config"; import { haStyleDialog } from "../../resources/styles"; import { HomeAssistant } from "../../types"; import { @@ -41,11 +46,6 @@ import { showConfirmationDialog, } from "../generic/show-dialog-box"; import { QuickBarParams } from "./show-dialog-quick-bar"; -import { navigate } from "../../common/navigate"; -import { configSections } from "../../panels/config/ha-panel-config"; -import { PageNavigation } from "../../layouts/hass-tabs-subpage"; -import { canShowPage } from "../../common/config/can_show_page"; -import { getPanelIcon, getPanelNameTranslationKey } from "../../data/panel"; const DEFAULT_NAVIGATION_ICON = "hass:arrow-right-circle"; const DEFAULT_SERVER_ICON = "hass:server"; diff --git a/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts b/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts index 305d3206c8..31c573e52d 100644 --- a/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts +++ b/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts @@ -1,6 +1,5 @@ import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable"; import type { PaperDialogScrollableElement } from "@polymer/paper-dialog-scrollable/paper-dialog-scrollable"; -import "../../components/ha-icon-button"; import "@polymer/paper-input/paper-input"; import type { PaperInputElement } from "@polymer/paper-input/paper-input"; import { @@ -8,9 +7,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, query, TemplateResult, @@ -20,6 +19,7 @@ import { fireEvent } from "../../common/dom/fire_event"; import { SpeechRecognition } from "../../common/dom/speech-recognition"; import { uid } from "../../common/util/uid"; import "../../components/dialog/ha-paper-dialog"; +import "../../components/ha-icon-button"; import { AgentInfo, getAgentInfo, diff --git a/src/entrypoints/app.ts b/src/entrypoints/app.ts index a4dd8580d4..39c5f4c493 100644 --- a/src/entrypoints/app.ts +++ b/src/entrypoints/app.ts @@ -1,7 +1,7 @@ import { setPassiveTouchGestures } from "@polymer/polymer/lib/utils/settings"; -import "../resources/roboto"; -import "../resources/ha-style"; import "../layouts/home-assistant"; +import "../resources/ha-style"; +import "../resources/roboto"; import "../util/legacy-support"; setPassiveTouchGestures(true); diff --git a/src/entrypoints/authorize.ts b/src/entrypoints/authorize.ts index 734551583b..45857e5998 100644 --- a/src/entrypoints/authorize.ts +++ b/src/entrypoints/authorize.ts @@ -1,11 +1,11 @@ // Compat needs to be first import import "../resources/compatibility"; -import "../resources/safari-14-attachshadow-patch"; import "@polymer/polymer/lib/elements/dom-if"; import "@polymer/polymer/lib/elements/dom-repeat"; import "../auth/ha-authorize"; import "../resources/ha-style"; import "../resources/roboto"; +import "../resources/safari-14-attachshadow-patch"; /* polyfill for paper-dropdown */ setTimeout( diff --git a/src/entrypoints/core.ts b/src/entrypoints/core.ts index aec93bb259..4da6a8fb89 100644 --- a/src/entrypoints/core.ts +++ b/src/entrypoints/core.ts @@ -1,6 +1,5 @@ // Compat needs to be first import import "../resources/compatibility"; -import "../resources/safari-14-attachshadow-patch"; import { Auth, Connection, @@ -24,6 +23,7 @@ import { subscribePanels } from "../data/ws-panels"; import { subscribeThemes } from "../data/ws-themes"; import { subscribeUser } from "../data/ws-user"; import type { ExternalAuth } from "../external_app/external_auth"; +import "../resources/safari-14-attachshadow-patch"; import { HomeAssistant } from "../types"; declare global { diff --git a/src/entrypoints/custom-panel.ts b/src/entrypoints/custom-panel.ts index bbb9320c41..0f25084f37 100644 --- a/src/entrypoints/custom-panel.ts +++ b/src/entrypoints/custom-panel.ts @@ -1,10 +1,11 @@ +// Compat needs to be first import import "../resources/compatibility"; -import "../resources/safari-14-attachshadow-patch"; import { PolymerElement } from "@polymer/polymer"; import { fireEvent } from "../common/dom/fire_event"; import { loadJS } from "../common/dom/load_resource"; import { webComponentsSupported } from "../common/feature-detect/support-web-components"; import { CustomPanelInfo } from "../data/panel_custom"; +import "../resources/safari-14-attachshadow-patch"; import { createCustomPanelElement } from "../util/custom-panel/create-custom-panel-element"; import { loadCustomPanel } from "../util/custom-panel/load-custom-panel"; import { setCustomPanelProperties } from "../util/custom-panel/set-custom-panel-properties"; diff --git a/src/entrypoints/onboarding.ts b/src/entrypoints/onboarding.ts index 56054746e8..9c085ed82f 100644 --- a/src/entrypoints/onboarding.ts +++ b/src/entrypoints/onboarding.ts @@ -1,9 +1,9 @@ // Compat needs to be first import import "../resources/compatibility"; -import "../resources/safari-14-attachshadow-patch"; import "../onboarding/ha-onboarding"; import "../resources/ha-style"; import "../resources/roboto"; +import "../resources/safari-14-attachshadow-patch"; declare global { interface Window { diff --git a/src/entrypoints/service_worker.ts b/src/entrypoints/service_worker.ts index 5ecb5f723f..cd536e7d18 100644 --- a/src/entrypoints/service_worker.ts +++ b/src/entrypoints/service_worker.ts @@ -2,14 +2,14 @@ // eslint-disable-next-line spaced-comment /// /* eslint-env serviceworker */ -import { - CacheFirst, - StaleWhileRevalidate, - NetworkOnly, -} from "workbox-strategies"; +import { cacheNames } from "workbox-core"; import { cleanupOutdatedCaches, precacheAndRoute } from "workbox-precaching"; import { registerRoute } from "workbox-routing"; -import { cacheNames } from "workbox-core"; +import { + CacheFirst, + NetworkOnly, + StaleWhileRevalidate, +} from "workbox-strategies"; // Clean up caches from older workboxes and old service workers. // Will help with cleaning up Workbox v4 stuff diff --git a/src/layouts/ha-init-page.ts b/src/layouts/ha-init-page.ts index 6ceb2cafb3..e0fa8384c5 100644 --- a/src/layouts/ha-init-page.ts +++ b/src/layouts/ha-init-page.ts @@ -1,7 +1,7 @@ import "@material/mwc-button"; import { css, CSSResult, html, LitElement, property } from "lit-element"; -import { removeInitSkeleton } from "../util/init-skeleton"; import "../components/ha-circular-progress"; +import { removeInitSkeleton } from "../util/init-skeleton"; class HaInitPage extends LitElement { @property({ type: Boolean }) public error = false; @@ -19,9 +19,9 @@ class HaInitPage extends LitElement {

It is possible that you are seeing this screen because your Home Assistant is not currently connected. You can - ask it to come online via - the Remote UI portalNaba Casa account page.

` diff --git a/src/layouts/hass-error-screen.ts b/src/layouts/hass-error-screen.ts index fafa6e6fd3..db0b3a671e 100644 --- a/src/layouts/hass-error-screen.ts +++ b/src/layouts/hass-error-screen.ts @@ -8,8 +8,8 @@ import { property, TemplateResult, } from "lit-element"; -import "./hass-subpage"; import { HomeAssistant } from "../types"; +import "./hass-subpage"; @customElement("hass-error-screen") class HassErrorScreen extends LitElement { diff --git a/src/layouts/hass-loading-screen.ts b/src/layouts/hass-loading-screen.ts index 8683d78630..ae7d41e8ea 100644 --- a/src/layouts/hass-loading-screen.ts +++ b/src/layouts/hass-loading-screen.ts @@ -9,8 +9,8 @@ import { TemplateResult, } from "lit-element"; import "../components/ha-circular-progress"; -import "../components/ha-menu-button"; import "../components/ha-icon-button-arrow-prev"; +import "../components/ha-menu-button"; import { haStyle } from "../resources/styles"; import { HomeAssistant } from "../types"; diff --git a/src/layouts/hass-subpage.ts b/src/layouts/hass-subpage.ts index 69a42f2fff..fa5cdbf3c0 100644 --- a/src/layouts/hass-subpage.ts +++ b/src/layouts/hass-subpage.ts @@ -2,16 +2,16 @@ import { css, CSSResult, customElement, + eventOptions, html, LitElement, property, TemplateResult, - eventOptions, } from "lit-element"; import { classMap } from "lit-html/directives/class-map"; -import "../components/ha-menu-button"; -import "../components/ha-icon-button-arrow-prev"; import { restoreScroll } from "../common/decorators/restore-scroll"; +import "../components/ha-icon-button-arrow-prev"; +import "../components/ha-menu-button"; import { HomeAssistant } from "../types"; @customElement("hass-subpage") diff --git a/src/layouts/home-assistant.ts b/src/layouts/home-assistant.ts index a813384793..1ec55ca9aa 100644 --- a/src/layouts/home-assistant.ts +++ b/src/layouts/home-assistant.ts @@ -1,23 +1,23 @@ import "@polymer/app-route/app-location"; import { + customElement, html, internalProperty, PropertyValues, - customElement, } from "lit-element"; import { navigate } from "../common/navigate"; import { getStorageDefaultPanelUrlPath } from "../data/panel"; import "../resources/custom-card-support"; import { HassElement } from "../state/hass-element"; +import QuickBarMixin from "../state/quick-bar-mixin"; import { HomeAssistant, Route } from "../types"; +import { storeState } from "../util/ha-pref-storage"; import { registerServiceWorker, supportsServiceWorker, } from "../util/register-service-worker"; import "./ha-init-page"; import "./home-assistant-main"; -import { storeState } from "../util/ha-pref-storage"; -import QuickBarMixin from "../state/quick-bar-mixin"; @customElement("home-assistant") export class HomeAssistantAppEl extends QuickBarMixin(HassElement) { diff --git a/src/managers/notification-manager.ts b/src/managers/notification-manager.ts index a2db48db89..a282adfd78 100644 --- a/src/managers/notification-manager.ts +++ b/src/managers/notification-manager.ts @@ -3,9 +3,9 @@ import { css, CSSResult, html, + internalProperty, LitElement, property, - internalProperty, query, TemplateResult, } from "lit-element"; diff --git a/src/panels/calendar/ha-full-calendar.ts b/src/panels/calendar/ha-full-calendar.ts index 019642fcbc..633ed239fb 100644 --- a/src/panels/calendar/ha-full-calendar.ts +++ b/src/panels/calendar/ha-full-calendar.ts @@ -114,7 +114,7 @@ export class HAFullCalendar extends LitElement { class="today" @click=${this._handleToday} >${this.hass.localize( - "ui.panel.calendar.today" + "ui.components.calendar.today" )} ${this.hass.localize( - "ui.panel.calendar.today" + "ui.components.calendar.today" )}
- ${this.hass.localize("ui.panel.calendar.my_calendars")} + ${this.hass.localize("ui.components.calendar.my_calendars")}
${this._calendars.map( (selCal) => diff --git a/src/panels/config/areas/dialog-area-registry-detail.ts b/src/panels/config/areas/dialog-area-registry-detail.ts index 7e1590d010..e0c5c48467 100644 --- a/src/panels/config/areas/dialog-area-registry-detail.ts +++ b/src/panels/config/areas/dialog-area-registry-detail.ts @@ -5,19 +5,19 @@ import { css, CSSResult, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import { fireEvent } from "../../../common/dom/fire_event"; +import { navigate } from "../../../common/navigate"; import "../../../components/ha-dialog"; import { AreaRegistryEntryMutableParams } from "../../../data/area_registry"; import { PolymerChangedEvent } from "../../../polymer-types"; import { haStyleDialog } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import { AreaRegistryDetailDialogParams } from "./show-dialog-area-registry-detail"; -import { navigate } from "../../../common/navigate"; class DialogAreaDetail extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; diff --git a/src/panels/config/areas/ha-config-areas-dashboard.ts b/src/panels/config/areas/ha-config-areas-dashboard.ts index 66cea0e01b..ad00f8e871 100644 --- a/src/panels/config/areas/ha-config-areas-dashboard.ts +++ b/src/panels/config/areas/ha-config-areas-dashboard.ts @@ -1,4 +1,3 @@ -import "../../../components/ha-fab"; import { mdiPlus } from "@mdi/js"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; @@ -18,6 +17,7 @@ import { DataTableColumnContainer, RowClickedEvent, } from "../../../components/data-table/ha-data-table"; +import "../../../components/ha-fab"; import "../../../components/ha-icon-button"; import "../../../components/ha-svg-icon"; import { diff --git a/src/panels/config/areas/ha-config-areas.ts b/src/panels/config/areas/ha-config-areas.ts index 1c00eb4f98..fc9ead4062 100644 --- a/src/panels/config/areas/ha-config-areas.ts +++ b/src/panels/config/areas/ha-config-areas.ts @@ -1,8 +1,8 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { customElement, - property, internalProperty, + property, PropertyValues, } from "lit-element"; import { compare } from "../../../common/string/compare"; diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index 4cc20c8c04..c13b6d4905 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -15,17 +15,19 @@ import { LitElement, property, PropertyValues, + query, } from "lit-element"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-button-menu"; import "../../../../components/ha-card"; import "../../../../components/ha-svg-icon"; +import type { HaYamlEditor } from "../../../../components/ha-yaml-editor"; import type { Action } from "../../../../data/script"; import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; -import { handleStructError } from "../../../lovelace/common/structs/handle-errors"; +import { handleStructError } from "../../../../common/structs/handle-errors"; import "./types/ha-automation-action-choose"; import "./types/ha-automation-action-condition"; import "./types/ha-automation-action-delay"; @@ -103,6 +105,8 @@ export default class HaAutomationActionRow extends LitElement { @internalProperty() private _yamlMode = false; + @query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor; + protected updated(changedProperties: PropertyValues) { if (!changedProperties.has("action")) { return; @@ -111,6 +115,10 @@ export default class HaAutomationActionRow extends LitElement { if (!this._uiModeAvailable && !this._yamlMode) { this._yamlMode = true; } + + if (this._yamlMode && this._yamlEditor) { + this._yamlEditor.setValue(this.action); + } } protected render() { @@ -187,7 +195,7 @@ export default class HaAutomationActionRow extends LitElement {
    ${this._warnings.map((warning) => html`
  • ${warning}
  • `)}
- You can still edit your config in yaml. + You can still edit your config in YAML.
` : ""} ${yamlMode @@ -243,7 +251,7 @@ export default class HaAutomationActionRow extends LitElement { } private _handleUiModeNotAvailable(ev: CustomEvent) { - this._warnings = handleStructError(ev.detail); + this._warnings = handleStructError(this.hass, ev.detail).warnings; if (!this._yamlMode) { this._yamlMode = true; } diff --git a/src/panels/config/automation/action/types/ha-automation-action-delay.ts b/src/panels/config/automation/action/types/ha-automation-action-delay.ts index 9b44078768..b8e7cee0a5 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-delay.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-delay.ts @@ -1,10 +1,12 @@ import "@polymer/paper-input/paper-input"; import { customElement, html, LitElement, property } from "lit-element"; +import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/entity/ha-entity-picker"; +import { HaFormTimeData } from "../../../../../components/ha-form/ha-form"; import "../../../../../components/ha-service-picker"; import { DelayAction } from "../../../../../data/script"; import { HomeAssistant } from "../../../../../types"; -import { ActionElement, handleChangeEvent } from "../ha-automation-action-row"; +import { ActionElement } from "../ha-automation-action-row"; @customElement("ha-automation-action-delay") export class HaDelayAction extends LitElement implements ActionElement { @@ -17,22 +19,46 @@ export class HaDelayAction extends LitElement implements ActionElement { } protected render() { - const { delay } = this.action; + let data: HaFormTimeData = {}; + + if (typeof this.action.delay !== "object") { + const parts = this.action.delay?.toString().split(":") || []; + data = { + hours: Number(parts[0]), + minutes: Number(parts[1]), + seconds: Number(parts[2]), + milliseconds: Number(parts[3]), + }; + } else { + const { days, minutes, seconds, milliseconds } = this.action.delay; + let { hours } = this.action.delay || 0; + hours = (hours || 0) + (days || 0) * 24; + data = { + hours: hours, + minutes: minutes, + seconds: seconds, + milliseconds: milliseconds, + }; + } return html` - + > `; } - private _valueChanged(ev: CustomEvent): void { - handleChangeEvent(this, ev); + private _valueChanged(ev: CustomEvent) { + ev.stopPropagation(); + const value = ev.detail.value; + if (!value) { + return; + } + fireEvent(this, "value-changed", { + value: { ...this.action, delay: value }, + }); } } diff --git a/src/panels/config/automation/action/types/ha-automation-action-device_id.ts b/src/panels/config/automation/action/types/ha-automation-action-device_id.ts index b75c5cce7b..051b3a8889 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-device_id.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-device_id.ts @@ -1,9 +1,9 @@ import { customElement, html, + internalProperty, LitElement, property, - internalProperty, } from "lit-element"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; @@ -13,8 +13,8 @@ import "../../../../../components/ha-form/ha-form"; import { DeviceAction, deviceAutomationsEqual, - fetchDeviceActionCapabilities, DeviceCapabilities, + fetchDeviceActionCapabilities, } from "../../../../../data/device_automation"; import { HomeAssistant } from "../../../../../types"; diff --git a/src/panels/config/automation/action/types/ha-automation-action-event.ts b/src/panels/config/automation/action/types/ha-automation-action-event.ts index 238e72287e..81fc2c10da 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-event.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-event.ts @@ -7,6 +7,7 @@ import { query, } from "lit-element"; import { html } from "lit-html"; +import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/entity/ha-entity-picker"; import "../../../../../components/ha-service-picker"; import "../../../../../components/ha-yaml-editor"; @@ -51,7 +52,7 @@ export class HaEventAction extends LitElement implements ActionElement { )} name="event" .value=${event} - @value-changed=${this._valueChanged} + @value-changed=${this._eventChanged} > ` : html`${blueprint?.metadata.description - ? html`

- ${blueprint.metadata.description} -

` + ? html`` : ""} ${blueprint?.metadata?.input && Object.keys(blueprint.metadata.input).length @@ -165,15 +168,16 @@ export class HaBlueprintAutomationEditor extends LitElement { .selector=${value.selector} .key=${key} .value=${(this.config.use_blueprint.input && - this.config.use_blueprint.input[key]) || + this.config.use_blueprint.input[key]) ?? value?.default} @value-changed=${this._inputChanged} >` : html``} @@ -267,9 +271,6 @@ export class HaBlueprintAutomationEditor extends LitElement { .padding { padding: 16px; } - .pre-line { - white-space: pre-line; - } .blueprint-picker-container { padding: 16px; } diff --git a/src/panels/config/automation/condition/ha-automation-condition-editor.ts b/src/panels/config/automation/condition/ha-automation-condition-editor.ts index 9f3f0d91f1..42dd2c34d8 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-editor.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-editor.ts @@ -3,18 +3,19 @@ import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox"; import { + CSSResult, customElement, html, LitElement, property, - CSSResult, } from "lit-element"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-card"; -import type { Condition } from "../../../../data/automation"; -import type { HomeAssistant } from "../../../../types"; import "../../../../components/ha-yaml-editor"; +import type { Condition } from "../../../../data/automation"; +import { haStyle } from "../../../../resources/styles"; +import type { HomeAssistant } from "../../../../types"; import "./types/ha-automation-condition-and"; import "./types/ha-automation-condition-device"; import "./types/ha-automation-condition-not"; @@ -25,7 +26,6 @@ import "./types/ha-automation-condition-sun"; import "./types/ha-automation-condition-template"; import "./types/ha-automation-condition-time"; import "./types/ha-automation-condition-zone"; -import { haStyle } from "../../../../resources/styles"; const OPTIONS = [ "device", diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-device.ts b/src/panels/config/automation/condition/types/ha-automation-condition-device.ts index c60face55a..4470724548 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-device.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-device.ts @@ -1,22 +1,22 @@ import { customElement, html, + internalProperty, LitElement, property, - internalProperty, } from "lit-element"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/device/ha-device-condition-picker"; import "../../../../../components/device/ha-device-picker"; import "../../../../../components/ha-form/ha-form"; import { deviceAutomationsEqual, + DeviceCapabilities, DeviceCondition, fetchDeviceConditionCapabilities, - DeviceCapabilities, } from "../../../../../data/device_automation"; import { HomeAssistant } from "../../../../../types"; -import memoizeOne from "memoize-one"; @customElement("ha-automation-condition-device") export class HaDeviceCondition extends LitElement { diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts index 2b42b53630..d0f18b1ab7 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts @@ -2,7 +2,7 @@ import "@polymer/paper-input/paper-input"; import { customElement, html, LitElement, property } from "lit-element"; import "../../../../../components/entity/ha-entity-attribute-picker"; import "../../../../../components/entity/ha-entity-picker"; -import { StateCondition } from "../../../../../data/automation"; +import { ForDict, StateCondition } from "../../../../../data/automation"; import { HomeAssistant } from "../../../../../types"; import { ConditionElement, @@ -21,6 +21,23 @@ export class HaStateCondition extends LitElement implements ConditionElement { protected render() { const { entity_id, attribute, state } = this.condition; + let forTime = this.condition.for; + + if ( + forTime && + ((forTime as ForDict).hours || + (forTime as ForDict).minutes || + (forTime as ForDict).seconds) + ) { + // If the trigger was defined using the yaml dict syntax, convert it to + // the equivalent string format + let { hours = 0, minutes = 0, seconds = 0 } = forTime as ForDict; + hours = hours.toString().padStart(2, "0"); + minutes = minutes.toString().padStart(2, "0"); + seconds = seconds.toString().padStart(2, "0"); + + forTime = `${hours}:${minutes}:${seconds}`; + } return html` + `; } diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-template.ts b/src/panels/config/automation/condition/types/ha-automation-condition-template.ts index e226d9534a..6f8fe2329e 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-template.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-template.ts @@ -1,5 +1,5 @@ -import { customElement, html, LitElement, property } from "lit-element"; import "@polymer/paper-input/paper-textarea"; +import { customElement, html, LitElement, property } from "lit-element"; import { TemplateCondition } from "../../../../../data/automation"; import { HomeAssistant } from "../../../../../types"; import { handleChangeEvent } from "../ha-automation-condition-row"; diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-time.ts b/src/panels/config/automation/condition/types/ha-automation-condition-time.ts index 0180a7494f..87b22ef223 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-time.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-time.ts @@ -1,25 +1,25 @@ import { Radio } from "@material/mwc-radio"; import "@polymer/paper-input/paper-input"; import { + css, + CSSResult, customElement, html, internalProperty, LitElement, property, - CSSResult, - css, } from "lit-element"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import { computeRTLDirection } from "../../../../../common/util/compute_rtl"; import "../../../../../components/ha-formfield"; import "../../../../../components/ha-radio"; +import { HaSwitch } from "../../../../../components/ha-switch"; import { TimeCondition } from "../../../../../data/automation"; import { HomeAssistant } from "../../../../../types"; import { ConditionElement, handleChangeEvent, } from "../ha-automation-condition-row"; -import { HaSwitch } from "../../../../../components/ha-switch"; -import { computeRTLDirection } from "../../../../../common/util/compute_rtl"; -import { fireEvent } from "../../../../../common/dom/fire_event"; const includeDomains = ["input_datetime"]; diff --git a/src/panels/config/automation/dialog-new-automation.ts b/src/panels/config/automation/dialog-new-automation.ts index cf9a5c965d..3815eee8b0 100644 --- a/src/panels/config/automation/dialog-new-automation.ts +++ b/src/panels/config/automation/dialog-new-automation.ts @@ -1,27 +1,27 @@ import "@material/mwc-button"; -import "../../../components/ha-circular-progress"; import { css, CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; -import "../../../components/ha-dialog"; -import { haStyle, haStyleDialog } from "../../../resources/styles"; -import type { HomeAssistant } from "../../../types"; -import { fireEvent } from "../../../common/dom/fire_event"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; +import { fireEvent } from "../../../common/dom/fire_event"; +import "../../../components/ha-blueprint-picker"; +import "../../../components/ha-card"; +import "../../../components/ha-circular-progress"; +import "../../../components/ha-dialog"; import { AutomationConfig, showAutomationEditor, } from "../../../data/automation"; +import { haStyle, haStyleDialog } from "../../../resources/styles"; +import type { HomeAssistant } from "../../../types"; import { showThingtalkDialog } from "./thingtalk/show-dialog-thingtalk"; -import "../../../components/ha-card"; -import "../../../components/ha-blueprint-picker"; @customElement("ha-dialog-new-automation") class DialogNewAutomation extends LitElement { diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index fed4eb04a5..9349c17610 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -1,4 +1,5 @@ -import "../../../components/ha-fab"; +import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; +import "@material/mwc-list/mwc-list-item"; import { mdiCheck, mdiContentDuplicate, @@ -10,8 +11,6 @@ import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light"; import "@polymer/paper-input/paper-textarea"; -import "@material/mwc-list/mwc-list-item"; -import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import { css, CSSResult, @@ -20,19 +19,19 @@ import { LitElement, property, PropertyValues, - TemplateResult, query, + TemplateResult, } from "lit-element"; import { classMap } from "lit-html/directives/class-map"; import { navigate } from "../../../common/navigate"; +import { copyToClipboard } from "../../../common/util/copy-clipboard"; import "../../../components/ha-button-menu"; import "../../../components/ha-card"; +import "../../../components/ha-fab"; import "../../../components/ha-icon-button"; import "../../../components/ha-svg-icon"; import "../../../components/ha-yaml-editor"; -import { showToast } from "../../../util/toast"; import type { HaYamlEditor } from "../../../components/ha-yaml-editor"; -import { copyToClipboard } from "../../../common/util/copy-clipboard"; import { AutomationConfig, AutomationEntity, @@ -50,15 +49,16 @@ import "../../../layouts/hass-tabs-subpage"; import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; +import { showToast } from "../../../util/toast"; import "../ha-config-section"; import { configSections } from "../ha-panel-config"; import "./action/ha-automation-action"; import { HaDeviceAction } from "./action/types/ha-automation-action-device_id"; +import "./blueprint-automation-editor"; import "./condition/ha-automation-condition"; +import "./manual-automation-editor"; import "./trigger/ha-automation-trigger"; import { HaDeviceTrigger } from "./trigger/types/ha-automation-trigger-device"; -import "./manual-automation-editor"; -import "./blueprint-automation-editor"; declare global { interface HTMLElementTagNameMap { @@ -355,6 +355,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { ...baseConfig, ...initData, } as AutomationConfig; + this._entityId = undefined; } if ( @@ -395,9 +396,12 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { return cleanConfig; } - private async _copyYaml() { + private async _copyYaml(): Promise { if (this._editor?.yaml) { - copyToClipboard(this._editor.yaml); + await copyToClipboard(this._editor.yaml); + showToast(this, { + message: this.hass.localize("ui.common.copied_clipboard"), + }); } } @@ -499,9 +503,9 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { } }, (errors) => { - this._errors = errors.body.message; + this._errors = errors.body.message || errors.error || errors.body; showToast(this, { - message: errors.body.message, + message: errors.body.message || errors.error || errors.body, }); throw errors; } diff --git a/src/panels/config/automation/ha-automation-picker.ts b/src/panels/config/automation/ha-automation-picker.ts index 616c4b4b2f..d6ef6d9667 100644 --- a/src/panels/config/automation/ha-automation-picker.ts +++ b/src/panels/config/automation/ha-automation-picker.ts @@ -1,6 +1,5 @@ -import "../../../components/ha-fab"; import "@material/mwc-icon-button"; -import { mdiPlus, mdiHelpCircle } from "@mdi/js"; +import { mdiHelpCircle, mdiPlus } from "@mdi/js"; import "@polymer/paper-tooltip/paper-tooltip"; import { CSSResult, @@ -12,23 +11,24 @@ import { } from "lit-element"; import { ifDefined } from "lit-html/directives/if-defined"; import memoizeOne from "memoize-one"; +import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { formatDateTime } from "../../../common/datetime/format_date_time"; import { fireEvent } from "../../../common/dom/fire_event"; import { computeStateName } from "../../../common/entity/compute_state_name"; +import { navigate } from "../../../common/navigate"; import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table"; -import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import "../../../components/entity/ha-entity-toggle"; +import "../../../components/ha-fab"; import "../../../components/ha-svg-icon"; import { AutomationEntity, triggerAutomation } from "../../../data/automation"; import { UNAVAILABLE_STATES } from "../../../data/entity"; +import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-tabs-subpage-data-table"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; -import { configSections } from "../ha-panel-config"; import { documentationUrl } from "../../../util/documentation-url"; +import { configSections } from "../ha-panel-config"; import { showNewAutomationDialog } from "./show-dialog-new-automation"; -import { navigate } from "../../../common/navigate"; -import { isComponentLoaded } from "../../../common/config/is_component_loaded"; @customElement("ha-automation-picker") class HaAutomationPicker extends LitElement { diff --git a/src/panels/config/automation/ha-config-automation.ts b/src/panels/config/automation/ha-config-automation.ts index 7b0953d0e5..39322da66e 100644 --- a/src/panels/config/automation/ha-config-automation.ts +++ b/src/panels/config/automation/ha-config-automation.ts @@ -2,6 +2,7 @@ import { HassEntities } from "home-assistant-js-websocket"; import { customElement, property, PropertyValues } from "lit-element"; import memoizeOne from "memoize-one"; import { computeStateDomain } from "../../../common/entity/compute_state_domain"; +import { debounce } from "../../../common/util/debounce"; import { AutomationEntity } from "../../../data/automation"; import { HassRouterPage, @@ -10,7 +11,6 @@ import { import { HomeAssistant } from "../../../types"; import "./ha-automation-editor"; import "./ha-automation-picker"; -import { debounce } from "../../../common/util/debounce"; const equal = (a: AutomationEntity[], b: AutomationEntity[]): boolean => { if (a.length !== b.length) { diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index a3ad1219db..21a9e1514e 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -1,3 +1,8 @@ +import "@material/mwc-button/mwc-button"; +import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light"; +import "@polymer/paper-input/paper-textarea"; +import { PaperListboxElement } from "@polymer/paper-listbox"; +import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResult, @@ -6,6 +11,9 @@ import { property, } from "lit-element"; import { html } from "lit-html"; +import { fireEvent } from "../../../common/dom/fire_event"; +import "../../../components/entity/ha-entity-toggle"; +import "../../../components/ha-card"; import { Condition, ManualAutomationConfig, @@ -13,21 +21,13 @@ import { triggerAutomation, } from "../../../data/automation"; import { Action, MODES, MODES_MAX } from "../../../data/script"; +import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; import "../ha-config-section"; -import "../../../components/ha-card"; -import "@polymer/paper-input/paper-textarea"; -import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light"; -import "../../../components/entity/ha-entity-toggle"; -import "@material/mwc-button/mwc-button"; -import "./trigger/ha-automation-trigger"; -import "./condition/ha-automation-condition"; import "./action/ha-automation-action"; -import { fireEvent } from "../../../common/dom/fire_event"; -import { PaperListboxElement } from "@polymer/paper-listbox"; -import { haStyle } from "../../../resources/styles"; -import { HassEntity } from "home-assistant-js-websocket"; +import "./condition/ha-automation-condition"; +import "./trigger/ha-automation-trigger"; @customElement("manual-automation-editor") export class HaManualAutomationEditor extends LitElement { @@ -80,7 +80,7 @@ export class HaManualAutomationEditor extends LitElement { html` -

- ${this._result.blueprint.metadata.description} -

+ ${this._result.validation_errors ? html`

@@ -211,15 +212,8 @@ class DialogImportBlueprint extends LitElement { } } - static get styles(): CSSResult[] { - return [ - haStyleDialog, - css` - .pre-line { - white-space: pre-line; - } - `, - ]; + static get styles(): CSSResult { + return haStyleDialog; } } diff --git a/src/panels/config/blueprint/ha-blueprint-overview.ts b/src/panels/config/blueprint/ha-blueprint-overview.ts index eb4cdce809..14f86ca4da 100644 --- a/src/panels/config/blueprint/ha-blueprint-overview.ts +++ b/src/panels/config/blueprint/ha-blueprint-overview.ts @@ -1,6 +1,5 @@ -import "../../../components/ha-fab"; import "@material/mwc-icon-button"; -import { mdiHelpCircle, mdiDelete, mdiRobot, mdiDownload } from "@mdi/js"; +import { mdiDelete, mdiDownload, mdiHelpCircle, mdiRobot } from "@mdi/js"; import "@polymer/paper-tooltip/paper-tooltip"; import { CSSResult, @@ -11,26 +10,27 @@ import { TemplateResult, } from "lit-element"; import memoizeOne from "memoize-one"; +import { fireEvent } from "../../../common/dom/fire_event"; import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table"; -import { - showAlertDialog, - showConfirmationDialog, -} from "../../../dialogs/generic/show-dialog-box"; import "../../../components/entity/ha-entity-toggle"; +import "../../../components/ha-fab"; import "../../../components/ha-svg-icon"; -import "../../../layouts/hass-tabs-subpage-data-table"; -import { haStyle } from "../../../resources/styles"; -import { HomeAssistant, Route } from "../../../types"; -import { configSections } from "../ha-panel-config"; -import { documentationUrl } from "../../../util/documentation-url"; +import { showAutomationEditor } from "../../../data/automation"; import { BlueprintMetaData, Blueprints, deleteBlueprint, } from "../../../data/blueprint"; +import { + showAlertDialog, + showConfirmationDialog, +} from "../../../dialogs/generic/show-dialog-box"; +import "../../../layouts/hass-tabs-subpage-data-table"; +import { haStyle } from "../../../resources/styles"; +import { HomeAssistant, Route } from "../../../types"; +import { documentationUrl } from "../../../util/documentation-url"; +import { configSections } from "../ha-panel-config"; import { showAddBlueprintDialog } from "./show-dialog-import-blueprint"; -import { showAutomationEditor } from "../../../data/automation"; -import { fireEvent } from "../../../common/dom/fire_event"; interface BlueprintMetaDataPath extends BlueprintMetaData { path: string; diff --git a/src/panels/config/blueprint/ha-config-blueprint.ts b/src/panels/config/blueprint/ha-config-blueprint.ts index 12e3bbc35f..efe7e82645 100644 --- a/src/panels/config/blueprint/ha-config-blueprint.ts +++ b/src/panels/config/blueprint/ha-config-blueprint.ts @@ -1,11 +1,11 @@ import { customElement, property, PropertyValues } from "lit-element"; +import { Blueprints, fetchBlueprints } from "../../../data/blueprint"; import { HassRouterPage, RouterOptions, } from "../../../layouts/hass-router-page"; -import "./ha-blueprint-overview"; import { HomeAssistant } from "../../../types"; -import { Blueprints, fetchBlueprints } from "../../../data/blueprint"; +import "./ha-blueprint-overview"; declare global { // for fire event diff --git a/src/panels/config/cloud/account/cloud-account.js b/src/panels/config/cloud/account/cloud-account.js index 2359d24067..139921206d 100644 --- a/src/panels/config/cloud/account/cloud-account.js +++ b/src/panels/config/cloud/account/cloud-account.js @@ -4,18 +4,19 @@ import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; import { formatDateTime } from "../../../../common/datetime/format_date_time"; +import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import "../../../../components/buttons/ha-call-api-button"; import "../../../../components/ha-card"; import { fetchCloudSubscriptionInfo } from "../../../../data/cloud"; import "../../../../layouts/hass-subpage"; import { EventsMixin } from "../../../../mixins/events-mixin"; import LocalizeMixin from "../../../../mixins/localize-mixin"; -import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import "../../../../styles/polymer-ha-style"; import "../../ha-config-section"; import "./cloud-alexa-pref"; import "./cloud-google-pref"; import "./cloud-remote-pref"; +import "./cloud-tts-pref"; import "./cloud-webhooks"; /* @@ -122,7 +123,7 @@ class CloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) { target="_blank" rel="noreferrer" > - [[localize('ui.panel.config.cloud.account.integrations_link_all_features')]] .

@@ -133,6 +134,12 @@ class CloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) { dir="[[_rtlDirection]]" > + +
- + ${this.hass!.localize( "ui.panel.config.cloud.account.alexa.sync_entities" )} diff --git a/src/panels/config/cloud/account/cloud-tts-pref.ts b/src/panels/config/cloud/account/cloud-tts-pref.ts new file mode 100644 index 0000000000..828590f47d --- /dev/null +++ b/src/panels/config/cloud/account/cloud-tts-pref.ts @@ -0,0 +1,268 @@ +import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light"; +import "@polymer/paper-item/paper-item"; +import "@polymer/paper-listbox/paper-listbox"; +import "@material/mwc-button"; +import { mdiPlayCircleOutline } from "@mdi/js"; +import { + css, + CSSResult, + customElement, + html, + internalProperty, + LitElement, + property, + TemplateResult, +} from "lit-element"; +import "../../../../components/ha-card"; +import "../../../../components/ha-switch"; +import "../../../../components/ha-svg-icon"; +import { + CloudStatusLoggedIn, + CloudTTSInfo, + getCloudTTSInfo, + updateCloudPref, +} from "../../../../data/cloud"; +import type { HomeAssistant } from "../../../../types"; +import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box"; +import { translationMetadata } from "../../../../resources/translations-metadata"; +import { caseInsensitiveCompare } from "../../../../common/string/compare"; +import memoizeOne from "memoize-one"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { showTryTtsDialog } from "./show-dialog-cloud-tts-try"; + +@customElement("cloud-tts-pref") +export class CloudTTSPref extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public cloudStatus?: CloudStatusLoggedIn; + + @internalProperty() private savingPreferences = false; + + @internalProperty() private ttsInfo?: CloudTTSInfo; + + protected render(): TemplateResult { + if (!this.cloudStatus) { + return html``; + } + + const languages = this.getLanguages(this.ttsInfo); + const defaultVoice = this.cloudStatus.prefs.tts_default_voice; + const genders = this.getSupportedGenders(defaultVoice[0], this.ttsInfo); + const defaultLangEntryIndex = languages.findIndex( + ([lang]) => lang === defaultVoice[0] + ); + const defaultGenderEntryIndex = genders.findIndex( + ([gender]) => gender === defaultVoice[1] + ); + + return html` + +
+ + +  ${this.hass.localize("ui.panel.config.cloud.account.tts.try")} + +
+
+ ${this.hass.localize( + "ui.panel.config.cloud.account.tts.info", + "service", + '"tts.cloud_say"' + )} +

+ + + + ${languages.map( + ([key, label]) => + html`${label}` + )} + + + + + + ${genders.map( + ([key, label]) => + html`${label}` + )} + + +
+
+ `; + } + + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + getCloudTTSInfo(this.hass).then((info) => { + this.ttsInfo = info; + }); + } + + protected updated(changedProps) { + super.updated(changedProps); + if (changedProps.has("cloudStatus")) { + this.savingPreferences = false; + } + } + + private getLanguages = memoizeOne((info?: CloudTTSInfo) => { + const languages: Array<[string, string]> = []; + + if (!info) { + return languages; + } + + const seen = new Set(); + for (const [lang] of info.languages) { + if (seen.has(lang)) { + continue; + } + seen.add(lang); + + let label = lang; + + if (lang in translationMetadata.translations) { + label = translationMetadata.translations[lang].nativeName; + } else { + const [langFamily, dialect] = lang.split("-"); + if (langFamily in translationMetadata.translations) { + label = `${translationMetadata.translations[langFamily].nativeName}`; + + if (langFamily.toLowerCase() !== dialect.toLowerCase()) { + label += ` (${dialect})`; + } + } + } + + languages.push([lang, label]); + } + return languages.sort((a, b) => caseInsensitiveCompare(a[1], b[1])); + }); + + private getSupportedGenders = memoizeOne( + (language: string, info?: CloudTTSInfo) => { + const genders: Array<[string, string]> = []; + + if (!info) { + return genders; + } + + for (const [curLang, gender] of info.languages) { + if (curLang === language) { + genders.push([ + gender, + this.hass.localize(`ui.panel.config.cloud.account.tts.${gender}`) || + gender, + ]); + } + } + + return genders.sort((a, b) => caseInsensitiveCompare(a[1], b[1])); + } + ); + + private _openTryDialog() { + showTryTtsDialog(this, { + defaultVoice: this.cloudStatus!.prefs.tts_default_voice, + }); + } + + async _handleLanguageChange(ev) { + this.savingPreferences = true; + const langLabel = ev.currentTarget.value; + const languages = this.getLanguages(this.ttsInfo); + const language = languages.find((item) => item[1] === langLabel)![0]; + + const curGender = this.cloudStatus!.prefs.tts_default_voice[1]; + const genders = this.getSupportedGenders(language, this.ttsInfo); + const newGender = genders.find((item) => item[0] === curGender) + ? curGender + : genders[0][0]; + + try { + await updateCloudPref(this.hass, { + tts_default_voice: [language, newGender], + }); + fireEvent(this, "ha-refresh-cloud-status"); + } catch (err) { + this.savingPreferences = false; + // eslint-disable-next-line no-console + console.error(err); + showAlertDialog(this, { + text: `Unable to save default language. ${err}`, + warning: true, + }); + } + } + + async _handleGenderChange(ev) { + this.savingPreferences = true; + const language = this.cloudStatus!.prefs.tts_default_voice[0]; + const genderLabel = ev.currentTarget.value; + const genders = this.getSupportedGenders(language, this.ttsInfo); + const gender = genders.find((item) => item[1] === genderLabel)![0]; + + try { + await updateCloudPref(this.hass, { + tts_default_voice: [language, gender], + }); + fireEvent(this, "ha-refresh-cloud-status"); + } catch (err) { + this.savingPreferences = false; + // eslint-disable-next-line no-console + console.error(err); + showAlertDialog(this, { + text: `Unable to save default gender. ${err}`, + warning: true, + }); + } + } + + static get styles(): CSSResult { + return css` + a { + color: var(--primary-color); + } + .example { + position: absolute; + right: 16px; + top: 16px; + } + :host([dir="rtl"]) .example { + right: auto; + left: 24px; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "cloud-tts-pref": CloudTTSPref; + } +} diff --git a/src/panels/config/cloud/account/cloud-webhooks.ts b/src/panels/config/cloud/account/cloud-webhooks.ts index fbbd1bfc82..57f4ee24ac 100644 --- a/src/panels/config/cloud/account/cloud-webhooks.ts +++ b/src/panels/config/cloud/account/cloud-webhooks.ts @@ -14,6 +14,7 @@ import "../../../../components/ha-card"; import "../../../../components/ha-circular-progress"; import "../../../../components/ha-settings-row"; import "../../../../components/ha-switch"; +import { isComponentLoaded } from "../../../../common/config/is_component_loaded"; import { CloudStatusLoggedIn, CloudWebhook, @@ -202,7 +203,7 @@ export class CloudWebhooks extends LitElement { } private async _fetchData() { - this._localHooks = this.hass!.config.components.includes("webhook") + this._localHooks = isComponentLoaded(this.hass!, "webhook") ? await fetchWebhooks(this.hass!) : []; } diff --git a/src/panels/config/cloud/account/dialog-cloud-tts-try.ts b/src/panels/config/cloud/account/dialog-cloud-tts-try.ts new file mode 100644 index 0000000000..0fb9891aeb --- /dev/null +++ b/src/panels/config/cloud/account/dialog-cloud-tts-try.ts @@ -0,0 +1,201 @@ +import "@material/mwc-button"; +import { + css, + CSSResult, + customElement, + html, + internalProperty, + LitElement, + property, + query, + TemplateResult, +} from "lit-element"; +import { HomeAssistant } from "../../../../types"; +import { TryTtsDialogParams } from "./show-dialog-cloud-tts-try"; +import { haStyleDialog } from "../../../../resources/styles"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { convertTextToSpeech } from "../../../../data/tts"; +import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box"; +import "@polymer/paper-input/paper-textarea"; +import "../../../../components/ha-paper-dropdown-menu"; +import { computeStateDomain } from "../../../../common/entity/compute_state_domain"; +import { computeStateName } from "../../../../common/entity/compute_state_name"; +import "@polymer/paper-item/paper-item"; +import "@polymer/paper-listbox/paper-listbox"; +import { supportsFeature } from "../../../../common/entity/supports-feature"; +import { SUPPORT_PLAY_MEDIA } from "../../../../data/media-player"; +import { createCloseHeading } from "../../../../components/ha-dialog"; +import { mdiPlayCircleOutline } from "@mdi/js"; +import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox"; +import type { PaperTextareaElement } from "@polymer/paper-input/paper-textarea"; +import { LocalStorage } from "../../../../common/decorators/local-storage"; + +@customElement("dialog-cloud-try-tts") +export class DialogTryTts extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @internalProperty() private _loadingExample = false; + + @internalProperty() private _params?: TryTtsDialogParams; + + @query("#target") private _targetInput?: PaperListboxElement; + + @query("#message") private _messageInput?: PaperTextareaElement; + + @LocalStorage("cloudTtsTryMessage") private _message?: string; + + @LocalStorage("cloudTtsTryTarget") private _target?: string; + + public showDialog(params: TryTtsDialogParams) { + this._params = params; + } + + public closeDialog() { + this._params = undefined; + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + protected render(): TemplateResult { + if (!this._params) { + return html``; + } + return html` + +
+ + + + + + + ${this.hass.localize( + "ui.panel.config.cloud.account.tts.dialog.target_browser" + )} + + ${Object.values(this.hass.states) + .filter( + (entity) => + computeStateDomain(entity) === "media_player" && + supportsFeature(entity, SUPPORT_PLAY_MEDIA) + ) + .map( + (entity) => html` + + ${computeStateName(entity)} + + ` + )} + + +
+ + +  ${this.hass.localize( + "ui.panel.config.cloud.account.tts.dialog.play" + )} + +
+ `; + } + + private async _playExample() { + const target = String(this._targetInput?.selected); + const message = this._messageInput?.value; + + if (!message || !target) { + return; + } + + this._message = message; + this._target = target; + + if (target === "browser") { + this._playBrowser(message); + } else { + this.hass.callService("tts", "cloud_say", { + entity_id: target, + message, + }); + } + } + + private async _playBrowser(message: string) { + this._loadingExample = true; + + const language = this._params!.defaultVoice[0]; + const gender = this._params!.defaultVoice[1]; + + let url; + try { + const result = await convertTextToSpeech(this.hass, { + platform: "cloud", + message, + language, + options: { gender }, + }); + url = result.url; + } catch (err) { + this._loadingExample = false; + showAlertDialog(this, { + text: `Unable to load example. ${err.error || err.body || err}`, + warning: true, + }); + return; + } + const audio = new Audio(url); + audio.addEventListener("canplaythrough", () => { + audio.play(); + }); + audio.addEventListener("playing", () => { + this._loadingExample = false; + }); + } + + static get styles(): CSSResult[] { + return [ + haStyleDialog, + css` + ha-dialog { + --mdc-dialog-max-width: 500px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-cloud-try-tts": DialogTryTts; + } +} diff --git a/src/panels/config/cloud/account/show-dialog-cloud-tts-try.ts b/src/panels/config/cloud/account/show-dialog-cloud-tts-try.ts new file mode 100644 index 0000000000..e1afa19d3d --- /dev/null +++ b/src/panels/config/cloud/account/show-dialog-cloud-tts-try.ts @@ -0,0 +1,18 @@ +import { fireEvent } from "../../../../common/dom/fire_event"; + +export interface TryTtsDialogParams { + defaultVoice: [string, string]; +} + +export const loadTryTtsDialog = () => import("./dialog-cloud-tts-try"); + +export const showTryTtsDialog = ( + element: HTMLElement, + dialogParams: TryTtsDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-cloud-try-tts", + dialogImport: loadTryTtsDialog, + dialogParams, + }); +}; diff --git a/src/panels/config/cloud/dialog-manage-cloudhook/dialog-manage-cloudhook.ts b/src/panels/config/cloud/dialog-manage-cloudhook/dialog-manage-cloudhook.ts index 9357e45c75..3a1c744c41 100644 --- a/src/panels/config/cloud/dialog-manage-cloudhook/dialog-manage-cloudhook.ts +++ b/src/panels/config/cloud/dialog-manage-cloudhook/dialog-manage-cloudhook.ts @@ -6,8 +6,8 @@ import { css, CSSResult, html, - LitElement, internalProperty, + LitElement, } from "lit-element"; import "../../../../components/dialog/ha-paper-dialog"; import type { HaPaperDialog } from "../../../../components/dialog/ha-paper-dialog"; diff --git a/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts b/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts index e0ad918fe5..7dd4b18354 100644 --- a/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts +++ b/src/panels/config/cloud/google-assistant/cloud-google-assistant.ts @@ -237,7 +237,7 @@ class CloudGoogleAssistant extends LitElement { } return html` - ${ diff --git a/src/panels/config/cloud/ha-config-cloud.ts b/src/panels/config/cloud/ha-config-cloud.ts index d8b512e39e..c62aa80cb3 100644 --- a/src/panels/config/cloud/ha-config-cloud.ts +++ b/src/panels/config/cloud/ha-config-cloud.ts @@ -1,5 +1,5 @@ import { PolymerElement } from "@polymer/polymer"; -import { customElement, property, internalProperty } from "lit-element"; +import { customElement, internalProperty, property } from "lit-element"; import { navigate } from "../../../common/navigate"; import { CloudStatus } from "../../../data/cloud"; import { diff --git a/src/panels/config/cloud/login/cloud-login.js b/src/panels/config/cloud/login/cloud-login.js index f2cdeb3350..e1ad4aceee 100644 --- a/src/panels/config/cloud/login/cloud-login.js +++ b/src/panels/config/cloud/login/cloud-login.js @@ -1,5 +1,4 @@ import "@material/mwc-button"; -import "../../../../components/ha-icon-button"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; @@ -7,8 +6,10 @@ import "@polymer/paper-ripple/paper-ripple"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; +import { computeRTL } from "../../../../common/util/compute_rtl"; import "../../../../components/buttons/ha-progress-button"; import "../../../../components/ha-card"; +import "../../../../components/ha-icon-button"; import "../../../../components/ha-icon-next"; import "../../../../layouts/hass-subpage"; import { EventsMixin } from "../../../../mixins/events-mixin"; @@ -16,7 +17,6 @@ import LocalizeMixin from "../../../../mixins/localize-mixin"; import NavigateMixin from "../../../../mixins/navigate-mixin"; import "../../../../styles/polymer-ha-style"; import "../../ha-config-section"; -import { computeRTL } from "../../../../common/util/compute_rtl"; /* * @appliesMixin NavigateMixin @@ -75,6 +75,15 @@ class CloudLogin extends LocalizeMixin( right: auto; left: 8px; } + .login-form { + display: flex; + flex-direction: column; + } + .pwd-forgot-link { + color: var(--secondary-text-color) !important; + text-align: right !important; + align-self: flex-end; + }
@@ -91,9 +100,8 @@ class CloudLogin extends LocalizeMixin( target="_blank" rel="noreferrer" > - Nabu Casa, Inc - - [[localize('ui.panel.config.cloud.login.introduction2a')]] + Nabu Casa, Inc[[localize('ui.panel.config.cloud.login.introduction2a')]]

[[localize('ui.panel.config.cloud.login.introduction3')]] @@ -122,7 +130,7 @@ class CloudLogin extends LocalizeMixin( -

+
[[localize('ui.panel.config.cloud.login.sign_in')]] -
diff --git a/src/panels/config/core/ha-config-core-form.ts b/src/panels/config/core/ha-config-core-form.ts index 6cb04f6692..406ca0d00b 100644 --- a/src/panels/config/core/ha-config-core-form.ts +++ b/src/panels/config/core/ha-config-core-form.ts @@ -8,9 +8,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import { UNIT_C } from "../../../common/const"; diff --git a/src/panels/config/core/ha-config-core.js b/src/panels/config/core/ha-config-core.js index 75d59b5aa5..5b41675898 100644 --- a/src/panels/config/core/ha-config-core.js +++ b/src/panels/config/core/ha-config-core.js @@ -1,9 +1,9 @@ import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; -import "../../../components/ha-icon-button"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; +import "../../../components/ha-icon-button"; import "../../../layouts/hass-tabs-subpage"; import LocalizeMixin from "../../../mixins/localize-mixin"; import "../../../styles/polymer-ha-style"; diff --git a/src/panels/config/core/ha-config-name-form.ts b/src/panels/config/core/ha-config-name-form.ts index d8315c6104..d29cba9d8b 100644 --- a/src/panels/config/core/ha-config-name-form.ts +++ b/src/panels/config/core/ha-config-name-form.ts @@ -6,9 +6,9 @@ import "@polymer/paper-radio-group/paper-radio-group"; import { customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import "../../../components/ha-card"; diff --git a/src/panels/config/core/ha-config-section-core.js b/src/panels/config/core/ha-config-section-core.js index 63888c047d..d91dda88f1 100644 --- a/src/panels/config/core/ha-config-section-core.js +++ b/src/panels/config/core/ha-config-section-core.js @@ -3,7 +3,6 @@ import "@polymer/paper-input/paper-input"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; -import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import "../../../components/buttons/ha-call-service-button"; import "../../../components/ha-card"; import LocalizeMixin from "../../../mixins/localize-mixin"; diff --git a/src/panels/config/core/ha-config-url-form.ts b/src/panels/config/core/ha-config-url-form.ts index e63b256e0c..549ccfa4d0 100644 --- a/src/panels/config/core/ha-config-url-form.ts +++ b/src/panels/config/core/ha-config-url-form.ts @@ -6,9 +6,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import "../../../components/ha-card"; diff --git a/src/panels/config/customize/ha-config-customize.js b/src/panels/config/customize/ha-config-customize.js deleted file mode 100644 index fe0c1f898b..0000000000 --- a/src/panels/config/customize/ha-config-customize.js +++ /dev/null @@ -1,111 +0,0 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import { computeStateDomain } from "../../../common/entity/compute_state_domain"; -import { computeStateName } from "../../../common/entity/compute_state_name"; -import { sortStatesByName } from "../../../common/entity/states_sort_by_name"; -import "../../../layouts/hass-tabs-subpage"; -import LocalizeMixin from "../../../mixins/localize-mixin"; -import "../../../styles/polymer-ha-style"; -import "../ha-config-section"; -import "../ha-entity-config"; -import { configSections } from "../ha-panel-config"; -import "./ha-form-customize"; -import { documentationUrl } from "../../../util/documentation-url"; - -/* - * @appliesMixin LocalizeMixin - */ -class HaConfigCustomize extends LocalizeMixin(PolymerElement) { - static get template() { - return html` - - -
- - - [[localize('ui.panel.config.customize.picker.header')]] - - - [[localize('ui.panel.config.customize.picker.introduction')]] -
- - [[localize("ui.panel.config.customize.picker.documentation")]] - -
- - -
-
-
- `; - } - - static get properties() { - return { - hass: Object, - isWide: Boolean, - narrow: Boolean, - route: Object, - showAdvanced: Boolean, - entities: { - type: Array, - computed: "computeEntities(hass)", - }, - - entityConfig: { - type: Object, - value: { - component: "ha-form-customize", - computeSelectCaption: (stateObj) => - computeStateName(stateObj) + - " (" + - computeStateDomain(stateObj) + - ")", - }, - }, - }; - } - - computeClasses(isWide) { - return isWide ? "content" : "content narrow"; - } - - _backTapped() { - history.back(); - } - - _computeTabs() { - return configSections.advanced; - } - - computeEntities(hass) { - return Object.keys(hass.states) - .map((key) => hass.states[key]) - .sort(sortStatesByName); - } - - _computeDocumentationUrl(hass) { - return documentationUrl( - hass, - "/docs/configuration/customizing-devices/#customization-using-the-ui" - ); - } -} -customElements.define("ha-config-customize", HaConfigCustomize); diff --git a/src/panels/config/customize/ha-config-customize.ts b/src/panels/config/customize/ha-config-customize.ts new file mode 100644 index 0000000000..c1802cc6aa --- /dev/null +++ b/src/panels/config/customize/ha-config-customize.ts @@ -0,0 +1,91 @@ +import { + css, + CSSResult, + html, + LitElement, + property, + TemplateResult, +} from "lit-element"; +import "../../../components/ha-card"; +import "../../../layouts/hass-loading-screen"; +import "../../../layouts/hass-tabs-subpage"; +import { HomeAssistant, Route } from "../../../types"; +import { documentationUrl } from "../../../util/documentation-url"; +import "../ha-config-section"; +import "../ha-entity-config"; +import { configSections } from "../ha-panel-config"; +import "./ha-form-customize"; + +class HaConfigCustomize extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public isWide?: boolean; + + @property() public narrow?: boolean; + + @property() public route!: Route; + + @property() private _selectedEntityId = ""; + + protected render(): TemplateResult { + return html` + + + + + ${this.hass.localize("ui.panel.config.customize.picker.header")} + + + ${this.hass.localize( + "ui.panel.config.customize.picker.introduction" + )} +
+ + ${this.hass.localize( + "ui.panel.config.customize.picker.documentation" + )} + +
+ + +
+
+ + `; + } + + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + + if (!this.route.path.includes("/edit/")) { + return; + } + const routeSegments = this.route.path.split("/edit/"); + this._selectedEntityId = routeSegments.length > 1 ? routeSegments[1] : ""; + } + + static get styles(): CSSResult { + return css` + a { + color: var(--primary-color); + } + `; + } +} +customElements.define("ha-config-customize", HaConfigCustomize); diff --git a/src/panels/config/customize/ha-customize-attribute.js b/src/panels/config/customize/ha-customize-attribute.js index 634e083f63..d47bb42ce5 100644 --- a/src/panels/config/customize/ha-customize-attribute.js +++ b/src/panels/config/customize/ha-customize-attribute.js @@ -1,7 +1,7 @@ -import "../../../components/ha-icon-button"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; +import "../../../components/ha-icon-button"; import hassAttributeUtil from "../../../util/hass-attributes-util"; import "../ha-form-style"; import "./types/ha-customize-array"; diff --git a/src/panels/config/customize/ha-form-customize.js b/src/panels/config/customize/ha-form-customize.js index 4df81153df..5e22d6b5d9 100644 --- a/src/panels/config/customize/ha-form-customize.js +++ b/src/panels/config/customize/ha-form-customize.js @@ -12,7 +12,7 @@ import hassAttributeUtil from "../../../util/hass-attributes-util"; import "../ha-form-style"; import "./ha-form-customize-attributes"; -class HaFormCustomize extends LocalizeMixin(PolymerElement) { +export class HaFormCustomize extends LocalizeMixin(PolymerElement) { static get template() { return html` - -
-
- - - - - -
- -
- - -
-
-
-
- SAVE - -
-
- `; - } - - static get properties() { - return { - hass: { - type: Object, - observer: "hassChanged", - }, - - label: { - type: String, - value: "Device", - }, - - entities: { - type: Array, - observer: "entitiesChanged", - }, - - allowDelete: { - type: Boolean, - value: false, - }, - - selectedEntity: { - type: Number, - value: -1, - observer: "entityChanged", - }, - - formState: { - type: String, - // no-devices, loading, saving, editing - value: "no-devices", - }, - - config: { - type: Object, - }, - }; - } - - connectedCallback() { - super.connectedCallback(); - this.formEl = document.createElement(this.config.component); - this.formEl.hass = this.hass; - this.$.form.appendChild(this.formEl); - this.entityChanged(this.selectedEntity); - } - - computeSelectCaption(stateObj) { - return this.config.computeSelectCaption - ? this.config.computeSelectCaption(stateObj) - : computeStateName(stateObj); - } - - computeShowNoDevices(formState) { - return formState === "no-devices"; - } - - computeShowSpinner(formState) { - return formState === "loading" || formState === "saving"; - } - - computeShowPlaceholder(formState) { - return formState !== "editing"; - } - - computeShowForm(formState) { - return formState === "editing"; - } - - hassChanged(hass) { - if (this.formEl) { - this.formEl.hass = hass; - } - } - - entitiesChanged(entities, oldEntities) { - if (entities.length === 0) { - this.formState = "no-devices"; - return; - } - if (!oldEntities) { - this.selectedEntity = 0; - return; - } - - const oldEntityId = oldEntities[this.selectedEntity].entity_id; - - const newIndex = entities.findIndex(function (ent) { - return ent.entity_id === oldEntityId; - }); - - if (newIndex === -1) { - this.selectedEntity = 0; - } else if (newIndex !== this.selectedEntity) { - // Entity moved index - this.selectedEntity = newIndex; - } - } - - entityChanged(index) { - if (!this.entities || !this.formEl) return; - const entity = this.entities[index]; - if (!entity) return; - - this.formState = "loading"; - // eslint-disable-next-line @typescript-eslint/no-this-alias - const el = this; - this.formEl.loadEntity(entity).then(function () { - el.formState = "editing"; - }); - } - - saveEntity() { - this.formState = "saving"; - // eslint-disable-next-line @typescript-eslint/no-this-alias - const el = this; - this.formEl.saveEntity().then(function () { - el.formState = "editing"; - }); - } -} - -customElements.define("ha-entity-config", HaEntityConfig); diff --git a/src/panels/config/ha-entity-config.ts b/src/panels/config/ha-entity-config.ts new file mode 100644 index 0000000000..9fb1d804dd --- /dev/null +++ b/src/panels/config/ha-entity-config.ts @@ -0,0 +1,124 @@ +import "@material/mwc-button"; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; +import "../../components/buttons/ha-progress-button"; +import "../../components/entity/ha-entity-picker"; +import "../../components/ha-card"; +import "../../components/ha-circular-progress"; +import { haStyle } from "../../resources/styles"; +import "../../styles/polymer-ha-style"; +import type { HomeAssistant } from "../../types"; +import { HaFormCustomize } from "./customize/ha-form-customize"; + +@customElement("ha-entity-config") +export class HaEntityConfig extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public selectedEntityId!: string; + + // False if no entity is selected or currently saving or loading + @property() private _formEditState = false; + + @query("#form") private _form!: HaFormCustomize; + + protected render(): TemplateResult { + return html` + +
+ + + +
+ + +
+
+
+ + ${this.hass.localize("ui.common.save")} + +
+
+ `; + } + + protected updated(changedProps: PropertyValues) { + super.updated(changedProps); + if ( + changedProps.has("selectedEntityId") && + changedProps.get("selectedEntityId") !== this.selectedEntityId + ) { + this._selectEntity(this.selectedEntityId); + this.requestUpdate(); + } + } + + private _selectedEntityChanged(ev) { + this._selectEntity(ev.target.value); + } + + private async _selectEntity(entityId?: string) { + if (!this._form || !entityId) return; + const entity = this.hass.states[entityId]; + if (!entity) return; + + this._formEditState = false; + await this._form.loadEntity(entity); + this._formEditState = true; + } + + private async _saveEntity(ev) { + if (!this._formEditState) return; + this._formEditState = false; + const button = ev.target; + button.progress = true; + + try { + await this._form.saveEntity(); + this._formEditState = true; + button.actionSuccess(); + } catch { + button.actionError(); + } finally { + button.progress = false; + } + } + + static get styles(): CSSResult[] { + return [ + haStyle, + css` + ha-card { + direction: ltr; + } + + .form-placeholder { + height: 96px; + } + + .hidden { + display: none; + } + `, + ]; + } +} diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index 25a4adf551..bc101ca782 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -1,10 +1,31 @@ +import { + mdiAccount, + mdiBadgeAccountHorizontal, + mdiDevices, + mdiHomeAssistant, + mdiInformation, + mdiMapMarkerRadius, + mdiMathLog, + mdiNfcVariant, + mdiPalette, + mdiPaletteSwatch, + mdiPencil, + mdiPuzzle, + mdiRobot, + mdiScriptText, + mdiServer, + mdiShape, + mdiSofa, + mdiTools, + mdiViewDashboard, +} from "@mdi/js"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; import { PolymerElement } from "@polymer/polymer"; import { customElement, - property, internalProperty, + property, PropertyValues, } from "lit-element"; import { isComponentLoaded } from "../../common/config/is_component_loaded"; @@ -14,27 +35,6 @@ import "../../layouts/hass-loading-screen"; import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page"; import { PageNavigation } from "../../layouts/hass-tabs-subpage"; import { HomeAssistant, Route } from "../../types"; -import { - mdiPuzzle, - mdiDevices, - mdiShape, - mdiSofa, - mdiRobot, - mdiPalette, - mdiScriptText, - mdiTools, - mdiViewDashboard, - mdiAccount, - mdiMapMarkerRadius, - mdiBadgeAccountHorizontal, - mdiHomeAssistant, - mdiServer, - mdiInformation, - mdiMathLog, - mdiPencil, - mdiNfcVariant, - mdiPaletteSwatch, -} from "@mdi/js"; declare global { // for fire event @@ -293,9 +293,9 @@ class HaPanelConfig extends HassRouterPage { ), }, zwave: { - tag: "ha-config-zwave", + tag: "zwave-config-router", load: () => - import("./integrations/integration-panels/zwave/ha-config-zwave"), + import("./integrations/integration-panels/zwave/zwave-config-router"), }, mqtt: { tag: "mqtt-config-panel", @@ -307,6 +307,13 @@ class HaPanelConfig extends HassRouterPage { load: () => import("./integrations/integration-panels/ozw/ozw-config-router"), }, + zwave_js: { + tag: "zwave_js-config-router", + load: () => + import( + "./integrations/integration-panels/zwave_js/zwave_js-config-router" + ), + }, }, }; diff --git a/src/panels/config/helpers/const.ts b/src/panels/config/helpers/const.ts index 5835f63c28..7115338351 100644 --- a/src/panels/config/helpers/const.ts +++ b/src/panels/config/helpers/const.ts @@ -1,9 +1,9 @@ +import { Counter } from "../../../data/counter"; import { InputBoolean } from "../../../data/input_boolean"; import { InputDateTime } from "../../../data/input_datetime"; import { InputNumber } from "../../../data/input_number"; import { InputSelect } from "../../../data/input_select"; import { InputText } from "../../../data/input_text"; -import { Counter } from "../../../data/counter"; import { Timer } from "../../../data/timer"; export const HELPER_DOMAINS = [ diff --git a/src/panels/config/helpers/dialog-helper-detail.ts b/src/panels/config/helpers/dialog-helper-detail.ts index 8a314af8d7..b4eeed86be 100644 --- a/src/panels/config/helpers/dialog-helper-detail.ts +++ b/src/panels/config/helpers/dialog-helper-detail.ts @@ -17,22 +17,22 @@ import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { dynamicElement } from "../../../common/dom/dynamic-element-directive"; import { domainIcon } from "../../../common/entity/domain_icon"; import "../../../components/ha-dialog"; +import { createCounter } from "../../../data/counter"; import { createInputBoolean } from "../../../data/input_boolean"; import { createInputDateTime } from "../../../data/input_datetime"; import { createInputNumber } from "../../../data/input_number"; import { createInputSelect } from "../../../data/input_select"; import { createInputText } from "../../../data/input_text"; -import { createCounter } from "../../../data/counter"; import { createTimer } from "../../../data/timer"; import { haStyleDialog } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import { Helper } from "./const"; +import "./forms/ha-counter-form"; import "./forms/ha-input_boolean-form"; import "./forms/ha-input_datetime-form"; import "./forms/ha-input_number-form"; import "./forms/ha-input_select-form"; import "./forms/ha-input_text-form"; -import "./forms/ha-counter-form"; import "./forms/ha-timer-form"; const HELPERS = { diff --git a/src/panels/config/helpers/forms/ha-counter-form.ts b/src/panels/config/helpers/forms/ha-counter-form.ts index 723d9f134e..278d33edd3 100644 --- a/src/panels/config/helpers/forms/ha-counter-form.ts +++ b/src/panels/config/helpers/forms/ha-counter-form.ts @@ -44,8 +44,8 @@ class HaCounterForm extends LitElement { if (item) { this._name = item.name || ""; this._icon = item.icon || ""; - this._maximum = item.maximum; - this._minimum = item.minimum; + this._maximum = item.maximum ?? undefined; + this._minimum = item.minimum ?? undefined; this._restore = item.restore ?? true; this._step = item.step ?? 1; this._initial = item.initial ?? 0; @@ -163,7 +163,9 @@ class HaCounterForm extends LitElement { const configValue = target.configValue; const value = target.type === "number" - ? Number(ev.detail.value) + ? ev.detail.value !== "" + ? Number(ev.detail.value) + : undefined : target.localName === "ha-switch" ? (ev.target as HaSwitch).checked : ev.detail.value; diff --git a/src/panels/config/helpers/forms/ha-input_boolean-form.ts b/src/panels/config/helpers/forms/ha-input_boolean-form.ts index 54b1fa2b3a..90656b861d 100644 --- a/src/panels/config/helpers/forms/ha-input_boolean-form.ts +++ b/src/panels/config/helpers/forms/ha-input_boolean-form.ts @@ -4,9 +4,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import { fireEvent } from "../../../../common/dom/fire_event"; diff --git a/src/panels/config/helpers/forms/ha-input_datetime-form.ts b/src/panels/config/helpers/forms/ha-input_datetime-form.ts index 07dfb95eb9..09a1efc4e7 100644 --- a/src/panels/config/helpers/forms/ha-input_datetime-form.ts +++ b/src/panels/config/helpers/forms/ha-input_datetime-form.ts @@ -6,9 +6,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import { fireEvent } from "../../../../common/dom/fire_event"; diff --git a/src/panels/config/helpers/forms/ha-input_select-form.ts b/src/panels/config/helpers/forms/ha-input_select-form.ts index 657df02826..46a59bc3d1 100644 --- a/src/panels/config/helpers/forms/ha-input_select-form.ts +++ b/src/panels/config/helpers/forms/ha-input_select-form.ts @@ -1,5 +1,4 @@ import "@material/mwc-button/mwc-button"; -import "../../../../components/ha-icon-button"; import "@polymer/paper-input/paper-input"; import type { PaperInputElement } from "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; @@ -9,13 +8,14 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, query, TemplateResult, } from "lit-element"; import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/ha-icon-button"; import "../../../../components/ha-icon-input"; import type { InputSelect } from "../../../../data/input_select"; import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; diff --git a/src/panels/config/helpers/forms/ha-input_text-form.ts b/src/panels/config/helpers/forms/ha-input_text-form.ts index 98db37b536..5c738b9dd8 100644 --- a/src/panels/config/helpers/forms/ha-input_text-form.ts +++ b/src/panels/config/helpers/forms/ha-input_text-form.ts @@ -6,9 +6,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import { fireEvent } from "../../../../common/dom/fire_event"; diff --git a/src/panels/config/helpers/forms/ha-timer-form.ts b/src/panels/config/helpers/forms/ha-timer-form.ts index 9757b76b94..1b81f2d481 100644 --- a/src/panels/config/helpers/forms/ha-timer-form.ts +++ b/src/panels/config/helpers/forms/ha-timer-form.ts @@ -3,14 +3,14 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-icon-input"; -import { Timer, DurationDict } from "../../../../data/timer"; +import { DurationDict, Timer } from "../../../../data/timer"; import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; diff --git a/src/panels/config/helpers/ha-config-helpers.ts b/src/panels/config/helpers/ha-config-helpers.ts index d22a37da25..8262e9bcb7 100644 --- a/src/panels/config/helpers/ha-config-helpers.ts +++ b/src/panels/config/helpers/ha-config-helpers.ts @@ -1,4 +1,3 @@ -import "../../../components/ha-fab"; import { mdiPlus } from "@mdi/js"; import "@polymer/paper-checkbox/paper-checkbox"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; @@ -23,6 +22,7 @@ import { DataTableColumnContainer, RowClickedEvent, } from "../../../components/data-table/ha-data-table"; +import "../../../components/ha-fab"; import "../../../components/ha-icon"; import "../../../components/ha-svg-icon"; import "../../../layouts/hass-loading-screen"; diff --git a/src/panels/config/info/ha-config-info.ts b/src/panels/config/info/ha-config-info.ts index d527c7868b..c1dd606e57 100644 --- a/src/panels/config/info/ha-config-info.ts +++ b/src/panels/config/info/ha-config-info.ts @@ -6,13 +6,13 @@ import { property, TemplateResult, } from "lit-element"; +import "../../../layouts/hass-tabs-subpage"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; +import { documentationUrl } from "../../../util/documentation-url"; +import { configSections } from "../ha-panel-config"; import "./integrations-card"; import "./system-health-card"; -import { configSections } from "../ha-panel-config"; -import "../../../layouts/hass-tabs-subpage"; -import { documentationUrl } from "../../../util/documentation-url"; const JS_TYPE = __BUILD__; const JS_VERSION = __VERSION__; diff --git a/src/panels/config/info/integrations-card.ts b/src/panels/config/info/integrations-card.ts index f10b106c22..b7e10b3e24 100644 --- a/src/panels/config/info/integrations-card.ts +++ b/src/panels/config/info/integrations-card.ts @@ -3,9 +3,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import memoizeOne from "memoize-one"; diff --git a/src/panels/config/info/system-health-card.ts b/src/panels/config/info/system-health-card.ts index 95947cce0d..bda8d409fb 100644 --- a/src/panels/config/info/system-health-card.ts +++ b/src/panels/config/info/system-health-card.ts @@ -15,6 +15,7 @@ import { } from "lit-element"; import { formatDateTime } from "../../../common/datetime/format_date_time"; import { copyToClipboard } from "../../../common/util/copy-clipboard"; +import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import "../../../components/ha-button-menu"; import "../../../components/ha-card"; import "../../../components/ha-circular-progress"; @@ -179,7 +180,7 @@ class SystemHealthCard extends LitElement { this.hass!.loadBackendTranslation("system_health"); - if (!this.hass!.config.components.includes("system_health")) { + if (!isComponentLoaded(this.hass!, "system_health")) { this._info = { system_health: { info: { @@ -197,7 +198,7 @@ class SystemHealthCard extends LitElement { }); } - private _copyInfo(ev: CustomEvent): void { + private async _copyInfo(ev: CustomEvent): Promise { const github = ev.detail.index === 1; let haContent: string | undefined; const domainParts: string[] = []; @@ -250,13 +251,15 @@ class SystemHealthCard extends LitElement { } } - copyToClipboard( + await copyToClipboard( `${github ? "## " : ""}System Health\n${haContent}\n\n${domainParts.join( "\n\n" )}` ); - showToast(this, { message: this.hass.localize("ui.common.copied") }); + showToast(this, { + message: this.hass.localize("ui.common.copied_clipboard"), + }); } static get styles(): CSSResult { diff --git a/src/panels/config/integrations/ha-config-integrations.ts b/src/panels/config/integrations/ha-config-integrations.ts index d448668b46..ddcfaccb7e 100644 --- a/src/panels/config/integrations/ha-config-integrations.ts +++ b/src/panels/config/integrations/ha-config-integrations.ts @@ -1,4 +1,3 @@ -import "../../../components/ha-fab"; import "@material/mwc-icon-button"; import "@material/mwc-list/mwc-list-item"; import { mdiDotsVertical, mdiPlus } from "@mdi/js"; @@ -25,6 +24,7 @@ import { LocalizeFunc } from "../../../common/translations/localize"; import { nextRender } from "../../../common/util/render-status"; import "../../../components/ha-button-menu"; import "../../../components/ha-card"; +import "../../../components/ha-fab"; import "../../../components/ha-svg-icon"; import { ConfigEntry, @@ -60,6 +60,7 @@ import "../../../layouts/hass-tabs-subpage"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; +import { brandsUrl } from "../../../util/brands-url"; import { configSections } from "../ha-panel-config"; import "./ha-integration-card"; import type { @@ -67,7 +68,6 @@ import type { ConfigEntryUpdatedEvent, HaIntegrationCard, } from "./ha-integration-card"; -import { brandsUrl } from "../../../util/brands-url"; interface DataEntryFlowProgressExtended extends DataEntryFlowProgress { localized_title?: string; @@ -338,7 +338,11 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { />

- ${item.localized_domain_name} + ${// In 2020.2 we added support for item.title. All ignored entries before + // that have title "Ignored" so we fallback to localized domain name. + item.title === "Ignored" + ? item.localized_domain_name + : item.title}

{ return [ diff --git a/src/panels/config/integrations/integration-panels/ozw/ozw-node-config.ts b/src/panels/config/integrations/integration-panels/ozw/ozw-node-config.ts index 3cb884f0d7..c7d9bee401 100644 --- a/src/panels/config/integrations/integration-panels/ozw/ozw-node-config.ts +++ b/src/panels/config/integrations/integration-panels/ozw/ozw-node-config.ts @@ -4,8 +4,8 @@ import { CSSResultArray, customElement, html, - LitElement, internalProperty, + LitElement, property, TemplateResult, } from "lit-element"; @@ -13,21 +13,21 @@ import { navigate } from "../../../../../common/navigate"; import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/ha-card"; import "../../../../../components/ha-icon-next"; +import { + fetchOZWNodeConfig, + fetchOZWNodeMetadata, + fetchOZWNodeStatus, + OZWDevice, + OZWDeviceConfig, + OZWDeviceMetaDataResponse, +} from "../../../../../data/ozw"; +import { ERR_NOT_FOUND } from "../../../../../data/websocket_api"; import "../../../../../layouts/hass-tabs-subpage"; import { haStyle } from "../../../../../resources/styles"; import type { HomeAssistant, Route } from "../../../../../types"; import "../../../ha-config-section"; -import { - fetchOZWNodeStatus, - fetchOZWNodeMetadata, - fetchOZWNodeConfig, - OZWDevice, - OZWDeviceMetaDataResponse, - OZWDeviceConfig, -} from "../../../../../data/ozw"; -import { ERR_NOT_FOUND } from "../../../../../data/websocket_api"; -import { showOZWRefreshNodeDialog } from "./show-dialog-ozw-refresh-node"; import { ozwNodeTabs } from "./ozw-node-router"; +import { showOZWRefreshNodeDialog } from "./show-dialog-ozw-refresh-node"; @customElement("ozw-node-config") class OZWNodeConfig extends LitElement { diff --git a/src/panels/config/integrations/integration-panels/ozw/ozw-node-dashboard.ts b/src/panels/config/integrations/integration-panels/ozw/ozw-node-dashboard.ts index 38f94c906f..9e39aa5882 100644 --- a/src/panels/config/integrations/integration-panels/ozw/ozw-node-dashboard.ts +++ b/src/panels/config/integrations/integration-panels/ozw/ozw-node-dashboard.ts @@ -4,8 +4,8 @@ import { CSSResultArray, customElement, html, - LitElement, internalProperty, + LitElement, property, TemplateResult, } from "lit-element"; @@ -13,19 +13,19 @@ import { navigate } from "../../../../../common/navigate"; import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/ha-card"; import "../../../../../components/ha-icon-next"; -import "../../../../../layouts/hass-tabs-subpage"; -import { haStyle } from "../../../../../resources/styles"; -import type { HomeAssistant, Route } from "../../../../../types"; -import "../../../ha-config-section"; import { - fetchOZWNodeStatus, fetchOZWNodeMetadata, + fetchOZWNodeStatus, OZWDevice, OZWDeviceMetaDataResponse, } from "../../../../../data/ozw"; import { ERR_NOT_FOUND } from "../../../../../data/websocket_api"; -import { showOZWRefreshNodeDialog } from "./show-dialog-ozw-refresh-node"; +import "../../../../../layouts/hass-tabs-subpage"; +import { haStyle } from "../../../../../resources/styles"; +import type { HomeAssistant, Route } from "../../../../../types"; +import "../../../ha-config-section"; import { ozwNodeTabs } from "./ozw-node-router"; +import { showOZWRefreshNodeDialog } from "./show-dialog-ozw-refresh-node"; @customElement("ozw-node-dashboard") class OZWNodeDashboard extends LitElement { @@ -141,11 +141,15 @@ class OZWNodeDashboard extends LitElement { ${this._metadata.metadata.ResetHelp}
- -
- ${this._metadata.metadata.WakeupHelp} -
-
+ ${this._metadata.metadata.WakeupHelp + ? html` + +
+ ${this._metadata.metadata.WakeupHelp} +
+
+ ` + : ``} ` : ``} ` @@ -199,6 +203,10 @@ class OZWNodeDashboard extends LitElement { margin-top: 24px; } + .content:last-child { + margin-bottom: 24px; + } + .sectionHeader { position: relative; padding-right: 40px; diff --git a/src/panels/config/integrations/integration-panels/zha/dialog-zha-cluster.ts b/src/panels/config/integrations/integration-panels/zha/dialog-zha-cluster.ts index 9b1b093ff6..3632eb748f 100644 --- a/src/panels/config/integrations/integration-panels/zha/dialog-zha-cluster.ts +++ b/src/panels/config/integrations/integration-panels/zha/dialog-zha-cluster.ts @@ -2,32 +2,32 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, - TemplateResult, PropertyValues, + TemplateResult, } from "lit-element"; +import { HASSDomEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/ha-code-editor"; import { createCloseHeading } from "../../../../../components/ha-dialog"; -import { haStyleDialog } from "../../../../../resources/styles"; -import { HomeAssistant } from "../../../../../types"; -import { ZHADeviceZigbeeInfoDialogParams } from "./show-dialog-zha-device-zigbee-info"; import { - ZHADevice, Cluster, - ZHAGroup, fetchBindableDevices, fetchGroups, + ZHADevice, + ZHAGroup, } from "../../../../../data/zha"; +import { haStyleDialog } from "../../../../../resources/styles"; +import { HomeAssistant } from "../../../../../types"; +import { sortZHADevices, sortZHAGroups } from "./functions"; +import { ZHADeviceZigbeeInfoDialogParams } from "./show-dialog-zha-device-zigbee-info"; import { ZHAClusterSelectedParams } from "./types"; import "./zha-cluster-attributes"; import "./zha-cluster-commands"; import "./zha-clusters"; import "./zha-device-binding"; import "./zha-group-binding"; -import { HASSDomEvent } from "../../../../../common/dom/fire_event"; -import { sortZHADevices, sortZHAGroups } from "./functions"; @customElement("dialog-zha-cluster") class DialogZHACluster extends LitElement { diff --git a/src/panels/config/integrations/integration-panels/zha/dialog-zha-device-zigbee-info.ts b/src/panels/config/integrations/integration-panels/zha/dialog-zha-device-zigbee-info.ts index c3af80c7fc..d0fc98269f 100644 --- a/src/panels/config/integrations/integration-panels/zha/dialog-zha-device-zigbee-info.ts +++ b/src/panels/config/integrations/integration-panels/zha/dialog-zha-device-zigbee-info.ts @@ -2,9 +2,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import "../../../../../components/ha-code-editor"; diff --git a/src/panels/config/integrations/integration-panels/zha/zha-add-devices-page.ts b/src/panels/config/integrations/integration-panels/zha/zha-add-devices-page.ts index 7f56d95d9e..eceb2fa034 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-add-devices-page.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-add-devices-page.ts @@ -1,30 +1,30 @@ import "@material/mwc-button"; -import "../../../../../components/ha-icon-button"; -import "../../../../../components/ha-circular-progress"; +import { IronAutogrowTextareaElement } from "@polymer/iron-autogrow-textarea"; +import "@polymer/paper-input/paper-textarea"; import { css, CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, - TemplateResult, PropertyValues, + TemplateResult, } from "lit-element"; +import "../../../../../components/ha-circular-progress"; +import "../../../../../components/ha-icon-button"; import "../../../../../components/ha-service-description"; -import "@polymer/paper-input/paper-textarea"; -import "../../../../../layouts/hass-tabs-subpage"; -import { haStyle } from "../../../../../resources/styles"; -import { HomeAssistant, Route } from "../../../../../types"; -import "./zha-device-pairing-status-card"; -import { zhaTabs } from "./zha-config-dashboard"; -import { IronAutogrowTextareaElement } from "@polymer/iron-autogrow-textarea"; import { DEVICE_MESSAGE_TYPES, LOG_OUTPUT, ZHADevice, } from "../../../../../data/zha"; +import "../../../../../layouts/hass-tabs-subpage"; +import { haStyle } from "../../../../../resources/styles"; +import { HomeAssistant, Route } from "../../../../../types"; +import { zhaTabs } from "./zha-config-dashboard"; +import "./zha-device-pairing-status-card"; @customElement("zha-add-devices-page") class ZHAAddDevicesPage extends LitElement { diff --git a/src/panels/config/integrations/integration-panels/zha/zha-add-group-page.ts b/src/panels/config/integrations/integration-panels/zha/zha-add-group-page.ts index 0887ccca34..37dd1d6bbc 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-add-group-page.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-add-group-page.ts @@ -1,26 +1,26 @@ import "@material/mwc-button"; import "@polymer/paper-input/paper-input"; import type { PaperInputElement } from "@polymer/paper-input/paper-input"; -import "../../../../../components/ha-circular-progress"; import { css, CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, query, } from "lit-element"; import type { HASSDomEvent } from "../../../../../common/dom/fire_event"; import { navigate } from "../../../../../common/navigate"; import type { SelectionChangedEvent } from "../../../../../components/data-table/ha-data-table"; +import "../../../../../components/ha-circular-progress"; import { addGroup, fetchGroupableDevices, - ZHAGroup, ZHADeviceEndpoint, + ZHAGroup, } from "../../../../../data/zha"; import "../../../../../layouts/hass-error-screen"; import "../../../../../layouts/hass-subpage"; diff --git a/src/panels/config/integrations/integration-panels/zha/zha-cluster-attributes.ts b/src/panels/config/integrations/integration-panels/zha/zha-cluster-attributes.ts index b2bb1c76b5..7727d50593 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-cluster-attributes.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-cluster-attributes.ts @@ -1,6 +1,5 @@ import "@material/mwc-button"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "../../../../../components/ha-icon-button"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; @@ -8,14 +7,15 @@ import { css, CSSResult, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, TemplateResult, } from "lit-element"; import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/ha-card"; +import "../../../../../components/ha-icon-button"; import "../../../../../components/ha-service-description"; import { Attribute, diff --git a/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts b/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts index 39fc7727e2..994071a6a4 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts @@ -1,5 +1,4 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "../../../../../components/ha-icon-button"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; @@ -7,14 +6,15 @@ import { css, CSSResult, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, TemplateResult, } from "lit-element"; import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/ha-card"; +import "../../../../../components/ha-icon-button"; import "../../../../../components/ha-service-description"; import { Cluster, diff --git a/src/panels/config/integrations/integration-panels/zha/zha-clusters-data-table.ts b/src/panels/config/integrations/integration-panels/zha/zha-clusters-data-table.ts index 55b6683fce..111e5456af 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-clusters-data-table.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-clusters-data-table.ts @@ -7,6 +7,7 @@ import { TemplateResult, } from "lit-element"; import memoizeOne from "memoize-one"; +import { computeRTLDirection } from "../../../../../common/util/compute_rtl"; import "../../../../../components/data-table/ha-data-table"; import type { DataTableColumnContainer, @@ -15,7 +16,6 @@ import type { import type { Cluster } from "../../../../../data/zha"; import type { HomeAssistant } from "../../../../../types"; import { formatAsPaddedHex } from "./functions"; -import { computeRTLDirection } from "../../../../../common/util/compute_rtl"; export interface ClusterRowData extends Cluster { cluster?: Cluster; diff --git a/src/panels/config/integrations/integration-panels/zha/zha-clusters.ts b/src/panels/config/integrations/integration-panels/zha/zha-clusters.ts index 1f8db25cd0..02649ff358 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-clusters.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-clusters.ts @@ -1,20 +1,20 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "../../../../../components/ha-icon-button"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResult, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, TemplateResult, } from "lit-element"; import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/ha-card"; +import "../../../../../components/ha-icon-button"; import "../../../../../components/ha-service-description"; import { Cluster, diff --git a/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard-router.ts b/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard-router.ts index 866ec1317c..5a52b8d5a2 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard-router.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard-router.ts @@ -1,10 +1,10 @@ import { customElement, property } from "lit-element"; +import { navigate } from "../../../../../common/navigate"; import { HassRouterPage, RouterOptions, } from "../../../../../layouts/hass-router-page"; import { HomeAssistant } from "../../../../../types"; -import { navigate } from "../../../../../common/navigate"; @customElement("zha-config-dashboard-router") class ZHAConfigDashboardRouter extends HassRouterPage { diff --git a/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts b/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts index 29d956ceb4..a1fcbf1bbf 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts @@ -1,6 +1,7 @@ +import "@material/mwc-button/mwc-button"; +import { mdiFolderMultipleOutline, mdiLan, mdiNetwork, mdiPlus } from "@mdi/js"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; -import "../../../../../components/ha-fab"; import { css, CSSResultArray, @@ -10,16 +11,15 @@ import { property, TemplateResult, } from "lit-element"; +import { computeRTL } from "../../../../../common/util/compute_rtl"; import "../../../../../components/ha-card"; +import "../../../../../components/ha-fab"; import "../../../../../components/ha-icon-next"; +import "../../../../../layouts/hass-tabs-subpage"; +import type { PageNavigation } from "../../../../../layouts/hass-tabs-subpage"; import { haStyle } from "../../../../../resources/styles"; import type { HomeAssistant, Route } from "../../../../../types"; import "../../../ha-config-section"; -import { mdiNetwork, mdiFolderMultipleOutline, mdiPlus, mdiLan } from "@mdi/js"; -import "../../../../../layouts/hass-tabs-subpage"; -import type { PageNavigation } from "../../../../../layouts/hass-tabs-subpage"; -import { computeRTL } from "../../../../../common/util/compute_rtl"; -import "@material/mwc-button/mwc-button"; export const zhaTabs: PageNavigation[] = [ { diff --git a/src/panels/config/integrations/integration-panels/zha/zha-device-binding.ts b/src/panels/config/integrations/integration-panels/zha/zha-device-binding.ts index ee3435feca..ce5d13a8e0 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-device-binding.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-device-binding.ts @@ -1,6 +1,5 @@ import "@material/mwc-button/mwc-button"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "../../../../../components/ha-icon-button"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; import { @@ -8,14 +7,15 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, TemplateResult, } from "lit-element"; import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/ha-card"; +import "../../../../../components/ha-icon-button"; import "../../../../../components/ha-service-description"; import { bindDevices, unbindDevices, ZHADevice } from "../../../../../data/zha"; import { haStyle } from "../../../../../resources/styles"; diff --git a/src/panels/config/integrations/integration-panels/zha/zha-device-endpoint-data-table.ts b/src/panels/config/integrations/integration-panels/zha/zha-device-endpoint-data-table.ts index 3644b9d497..1364f6e2a9 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-device-endpoint-data-table.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-device-endpoint-data-table.ts @@ -1,26 +1,26 @@ import { + css, + CSSResult, customElement, html, LitElement, property, query, TemplateResult, - css, - CSSResult, } from "lit-element"; import memoizeOne from "memoize-one"; +import { computeRTLDirection } from "../../../../../common/util/compute_rtl"; import "../../../../../components/data-table/ha-data-table"; import type { DataTableColumnContainer, - HaDataTable, DataTableRowData, + HaDataTable, } from "../../../../../components/data-table/ha-data-table"; import type { ZHADeviceEndpoint, ZHAEntityReference, } from "../../../../../data/zha"; import type { HomeAssistant } from "../../../../../types"; -import { computeRTLDirection } from "../../../../../common/util/compute_rtl"; export interface DeviceEndpointRowData extends DataTableRowData { id: string; diff --git a/src/panels/config/integrations/integration-panels/zha/zha-device-pairing-status-card.ts b/src/panels/config/integrations/integration-panels/zha/zha-device-pairing-status-card.ts index c6b4181e85..d3422dd828 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-device-pairing-status-card.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-device-pairing-status-card.ts @@ -5,13 +5,15 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; +import { classMap } from "lit-html/directives/class-map"; import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/entity/state-badge"; +import "../../../../../components/ha-area-picker"; import "../../../../../components/ha-card"; import "../../../../../components/ha-service-description"; import { @@ -23,10 +25,8 @@ import { } from "../../../../../data/zha"; import { haStyle } from "../../../../../resources/styles"; import { HomeAssistant } from "../../../../../types"; -import "../../../../../components/ha-area-picker"; import { formatAsPaddedHex } from "./functions"; import "./zha-device-card"; -import { classMap } from "lit-html/directives/class-map"; @customElement("zha-device-pairing-status-card") class ZHADevicePairingStatusCard extends LitElement { @@ -49,19 +49,17 @@ class ZHADevicePairingStatusCard extends LitElement { class="discovered ${classMap({ initialized: this.device.pairing_status === INITIALIZED, })}" - >
-

+ >
+

${this.hass!.localize( `ui.panel.config.zha.device_pairing_card.${this.device.pairing_status}` )} -

-

+

+

${this.hass!.localize( `ui.panel.config.zha.device_pairing_card.${this.device.pairing_status}_status_text` )} -

+

${[INTERVIEW_COMPLETE, CONFIGURED].includes( diff --git a/src/panels/config/integrations/integration-panels/zha/zha-group-binding.ts b/src/panels/config/integrations/integration-panels/zha/zha-group-binding.ts index 56b710b014..41fbe0d83c 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-group-binding.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-group-binding.ts @@ -1,6 +1,5 @@ import "@material/mwc-button/mwc-button"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "../../../../../components/ha-icon-button"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; import { @@ -8,9 +7,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, query, TemplateResult, @@ -19,6 +18,7 @@ import type { HASSDomEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/buttons/ha-call-service-button"; import { SelectionChangedEvent } from "../../../../../components/data-table/ha-data-table"; import "../../../../../components/ha-card"; +import "../../../../../components/ha-icon-button"; import "../../../../../components/ha-service-description"; import { bindDeviceToGroup, diff --git a/src/panels/config/integrations/integration-panels/zha/zha-group-page.ts b/src/panels/config/integrations/integration-panels/zha/zha-group-page.ts index 1526c4c0d8..ae6b89f928 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-group-page.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-group-page.ts @@ -1,28 +1,28 @@ import "@material/mwc-button"; -import "../../../../../components/ha-icon-button"; -import "../../../../../components/ha-circular-progress"; import { css, CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, query, } from "lit-element"; import { HASSDomEvent } from "../../../../../common/dom/fire_event"; import { navigate } from "../../../../../common/navigate"; import { SelectionChangedEvent } from "../../../../../components/data-table/ha-data-table"; +import "../../../../../components/ha-circular-progress"; +import "../../../../../components/ha-icon-button"; import { addMembersToGroup, fetchGroup, fetchGroupableDevices, removeGroups, removeMembersFromGroup, - ZHAGroup, ZHADeviceEndpoint, + ZHAGroup, } from "../../../../../data/zha"; import "../../../../../layouts/hass-error-screen"; import "../../../../../layouts/hass-subpage"; diff --git a/src/panels/config/integrations/integration-panels/zha/zha-groups-dashboard.ts b/src/panels/config/integrations/integration-panels/zha/zha-groups-dashboard.ts index 909cb7aecf..4295161225 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-groups-dashboard.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-groups-dashboard.ts @@ -1,30 +1,30 @@ import "@material/mwc-button"; -import "../../../../../components/ha-fab"; -import "../../../../../components/ha-icon-button"; -import memoizeOne from "memoize-one"; +import { mdiPlus } from "@mdi/js"; import { + css, + CSSResultArray, customElement, html, LitElement, property, PropertyValues, TemplateResult, - CSSResultArray, - css, } from "lit-element"; +import memoizeOne from "memoize-one"; import { HASSDomEvent } from "../../../../../common/dom/fire_event"; import { navigate } from "../../../../../common/navigate"; import { DataTableColumnContainer, RowClickedEvent, } from "../../../../../components/data-table/ha-data-table"; -import { fetchGroups, ZHAGroup, ZHADevice } from "../../../../../data/zha"; +import "../../../../../components/ha-fab"; +import "../../../../../components/ha-icon-button"; +import { fetchGroups, ZHADevice, ZHAGroup } from "../../../../../data/zha"; import "../../../../../layouts/hass-tabs-subpage-data-table"; -import { HomeAssistant, Route } from "../../../../../types"; -import { sortZHAGroups, formatAsPaddedHex } from "./functions"; -import { zhaTabs } from "./zha-config-dashboard"; -import { mdiPlus } from "@mdi/js"; import { haStyle } from "../../../../../resources/styles"; +import { HomeAssistant, Route } from "../../../../../types"; +import { formatAsPaddedHex, sortZHAGroups } from "./functions"; +import { zhaTabs } from "./zha-config-dashboard"; export interface GroupRowData extends ZHAGroup { group?: GroupRowData; diff --git a/src/panels/config/integrations/integration-panels/zha/zha-network-visualization-page.ts b/src/panels/config/integrations/integration-panels/zha/zha-network-visualization-page.ts index 8f3dbd63a5..898924a1ab 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-network-visualization-page.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-network-visualization-page.ts @@ -9,12 +9,17 @@ import { PropertyValues, query, } from "lit-element"; - +import { Edge, EdgeOptions, Network, Node } from "vis-network"; import { navigate } from "../../../../../common/navigate"; +import "../../../../../common/search/search-input"; +import "../../../../../components/device/ha-device-picker"; +import "../../../../../components/ha-button-menu"; +import "../../../../../components/ha-svg-icon"; import { fetchDevices, ZHADevice } from "../../../../../data/zha"; import "../../../../../layouts/hass-subpage"; +import { PolymerChangedEvent } from "../../../../../polymer-types"; import type { HomeAssistant } from "../../../../../types"; -import { Network, Edge, Node, EdgeOptions } from "vis-network"; +import { formatAsPaddedHex } from "./functions"; @customElement("zha-network-visualization-page") export class ZHANetworkVisualizationPage extends LitElement { @@ -28,9 +33,21 @@ export class ZHANetworkVisualizationPage extends LitElement { @internalProperty() private _devices: Map = new Map(); + @internalProperty() + private _devicesByDeviceId: Map = new Map(); + + @internalProperty() + private _nodes: Node[] = []; + @internalProperty() private _network?: Network; + @internalProperty() + private _filter?: string; + + @internalProperty() + private _zoomedDeviceId?: string; + protected firstUpdated(changedProperties: PropertyValues): void { super.firstUpdated(changedProperties); if (this.hass) { @@ -91,6 +108,27 @@ export class ZHANetworkVisualizationPage extends LitElement { "ui.panel.config.zha.visualization.header" )} > +
+ + + +
`; @@ -101,15 +139,18 @@ export class ZHANetworkVisualizationPage extends LitElement { this._devices = new Map( devices.map((device: ZHADevice) => [device.ieee, device]) ); + this._devicesByDeviceId = new Map( + devices.map((device: ZHADevice) => [device.device_reg_id, device]) + ); this._updateDevices(devices); } private _updateDevices(devices: ZHADevice[]) { - const nodes: Node[] = []; + this._nodes = []; const edges: Edge[] = []; devices.forEach((device) => { - nodes.push({ + this._nodes.push({ id: device.ieee, label: this._buildLabel(device), shape: this._getShape(device), @@ -137,7 +178,7 @@ export class ZHANetworkVisualizationPage extends LitElement { } }); - this._network?.setData({ nodes: nodes, edges: edges }); + this._network?.setData({ nodes: this._nodes, edges: edges }); } private _getLQI(lqi: number): EdgeOptions["color"] { @@ -181,7 +222,7 @@ export class ZHANetworkVisualizationPage extends LitElement { label += `IEEE: ${device.ieee}`; label += `\nDevice Type: ${device.device_type.replace("_", " ")}`; if (device.nwk != null) { - label += `\nNWK: ${device.nwk}`; + label += `\nNWK: ${formatAsPaddedHex(device.nwk)}`; } if (device.manufacturer != null && device.model != null) { label += `\nDevice: ${device.manufacturer} ${device.model}`; @@ -194,6 +235,56 @@ export class ZHANetworkVisualizationPage extends LitElement { return label; } + private _handleSearchChange(ev: CustomEvent) { + this._filter = ev.detail.value; + const filterText = this._filter!.toLowerCase(); + if (!this._network) { + return; + } + if (this._filter) { + const filteredNodeIds: (string | number)[] = []; + this._nodes.forEach((node) => { + if (node.label && node.label.toLowerCase().includes(filterText)) { + filteredNodeIds.push(node.id!); + } + }); + this._zoomedDeviceId = ""; + this._zoomOut(); + this._network.selectNodes(filteredNodeIds, true); + } else { + this._network.unselectAll(); + } + } + + private _zoomToDevice(event: PolymerChangedEvent) { + event.stopPropagation(); + this._zoomedDeviceId = event.detail.value; + if (!this._network) { + return; + } + this._filter = ""; + if (!this._zoomedDeviceId) { + this._zoomOut(); + } else { + const device: ZHADevice | undefined = this._devicesByDeviceId.get( + this._zoomedDeviceId + ); + if (device) { + this._network.fit({ + nodes: [device.ieee], + animation: { duration: 500, easingFunction: "easeInQuad" }, + }); + } + } + } + + private _zoomOut() { + this._network!.fit({ + nodes: [], + animation: { duration: 500, easingFunction: "easeOutQuad" }, + }); + } + static get styles(): CSSResult[] { return [ css` @@ -208,6 +299,30 @@ export class ZHANetworkVisualizationPage extends LitElement { line-height: var(--paper-font-display1_-_line-height); opacity: var(--dark-primary-opacity); } + .table-header { + border-bottom: 1px solid --divider-color; + padding: 0 16px; + display: flex; + align-items: center; + height: var(--header-height); + } + .search-toolbar { + display: flex; + align-items: center; + color: var(--secondary-text-color); + padding: 0 16px; + } + search-input { + position: relative; + top: 2px; + flex: 1; + } + search-input.header { + left: -8px; + } + ha-device-picker { + flex: 1; + } `, ]; } diff --git a/src/panels/config/integrations/integration-panels/zwave/ha-config-zwave.js b/src/panels/config/integrations/integration-panels/zwave/ha-config-zwave.js index 9c9dc1fdfc..3835c47499 100644 --- a/src/panels/config/integrations/integration-panels/zwave/ha-config-zwave.js +++ b/src/panels/config/integrations/integration-panels/zwave/ha-config-zwave.js @@ -1,7 +1,6 @@ import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "../../../../../components/ha-icon-button"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; @@ -13,8 +12,9 @@ import { computeStateName } from "../../../../../common/entity/compute_state_nam import { sortStatesByName } from "../../../../../common/entity/states_sort_by_name"; import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/ha-card"; -import "../../../../../components/ha-menu-button"; +import "../../../../../components/ha-icon-button"; import "../../../../../components/ha-icon-button-arrow-prev"; +import "../../../../../components/ha-menu-button"; import "../../../../../components/ha-service-description"; import "../../../../../layouts/ha-app-layout"; import { EventsMixin } from "../../../../../mixins/events-mixin"; @@ -102,6 +102,36 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) { + + +
+

+ If you are experiencing problems with your Z-Wave devices, you + can migrate to the newer OZW integration, that is currently in + beta. +

+

+ Be aware that the future of OZW is not guaranteed, as the + development has stopped. +

+

+ If you are currently not experiencing issues with your Z-Wave + devices, we recommend you to wait for the successor of the OZW + integration, Z-Wave JS, that is in active development at the + moment. +

+
+ +
+
+ + import(/* webpackChunkName: "ha-config-zwave" */ "./ha-config-zwave"), + }, + migration: { + tag: "zwave-migration", + load: () => + import(/* webpackChunkName: "zwave-migration" */ "./zwave-migration"), + }, + }, + }; + + protected updatePageEl(el): void { + el.route = this.routeTail; + el.hass = this.hass; + el.isWide = this.isWide; + el.narrow = this.narrow; + el.configEntryId = this._configEntry; + + const searchParams = new URLSearchParams(window.location.search); + if (this._configEntry && !searchParams.has("config_entry")) { + searchParams.append("config_entry", this._configEntry); + navigate( + this, + `${this.routeTail.prefix}${ + this.routeTail.path + }?${searchParams.toString()}`, + true + ); + } + } +} + +declare global { + interface HTMLElementTagNameMap { + "zwave-config-router": ZWaveConfigRouter; + } +} diff --git a/src/panels/config/integrations/integration-panels/zwave/zwave-log.js b/src/panels/config/integrations/integration-panels/zwave/zwave-log.js index bf1bd5beb2..f65e6e406d 100755 --- a/src/panels/config/integrations/integration-panels/zwave/zwave-log.js +++ b/src/panels/config/integrations/integration-panels/zwave/zwave-log.js @@ -8,8 +8,8 @@ import isPwa from "../../../../../common/config/is_pwa"; import "../../../../../components/ha-card"; import { EventsMixin } from "../../../../../mixins/events-mixin"; import LocalizeMixin from "../../../../../mixins/localize-mixin"; -import "../../../ha-config-section"; import "../../../../../styles/polymer-ha-style"; +import "../../../ha-config-section"; let registeredDialog = false; diff --git a/src/panels/config/integrations/integration-panels/zwave/zwave-migration.ts b/src/panels/config/integrations/integration-panels/zwave/zwave-migration.ts new file mode 100644 index 0000000000..0dc8d750a4 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zwave/zwave-migration.ts @@ -0,0 +1,480 @@ +import "@polymer/app-layout/app-header/app-header"; +import "@polymer/app-layout/app-toolbar/app-toolbar"; +import "@material/mwc-button/mwc-button"; +import "../../../../../components/ha-icon-button"; +import "../../../../../components/ha-circular-progress"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + internalProperty, + TemplateResult, +} from "lit-element"; +import "../../../../../components/buttons/ha-call-api-button"; +import "../../../../../components/buttons/ha-call-service-button"; +import "../../../../../components/ha-card"; +import "../../../../../components/ha-icon"; +import { + fetchNetworkStatus, + ZWaveNetworkStatus, + ZWAVE_NETWORK_STATE_STOPPED, + fetchMigrationConfig, + ZWaveMigrationConfig, + startOzwConfigFlow, +} from "../../../../../data/zwave"; +import { haStyle } from "../../../../../resources/styles"; +import type { HomeAssistant, Route } from "../../../../../types"; +import "../../../ha-config-section"; +import "../../../../../layouts/hass-subpage"; +import { showConfigFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-config-flow"; +import { migrateZwave, OZWMigrationData } from "../../../../../data/ozw"; +import { navigate } from "../../../../../common/navigate"; +import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box"; +import { computeStateName } from "../../../../../common/entity/compute_state_name"; +import { + computeDeviceName, + DeviceRegistryEntry, + fetchDeviceRegistry, +} from "../../../../../data/device_registry"; + +@customElement("zwave-migration") +export class ZwaveMigration extends LitElement { + @property({ type: Object }) public hass!: HomeAssistant; + + @property({ type: Object }) public route!: Route; + + @property({ type: Boolean }) public narrow!: boolean; + + @property({ type: Boolean }) public isWide!: boolean; + + @internalProperty() private _networkStatus?: ZWaveNetworkStatus; + + @internalProperty() private _unsub?: Promise; + + @internalProperty() private _step = 0; + + @internalProperty() private _stoppingNetwork = false; + + @internalProperty() private _migrationConfig?: ZWaveMigrationConfig; + + @internalProperty() private _migrationData?: OZWMigrationData; + + @internalProperty() private _migratedZwaveEntities?: string[]; + + @internalProperty() private _deviceRegistry?: DeviceRegistryEntry[]; + + public disconnectedCallback(): void { + this._unsubscribe(); + } + + protected render(): TemplateResult { + return html` + + +
+ ${this.hass.localize("ui.panel.config.zwave.migration.ozw.header")} +
+ +
+ ${this.hass.localize( + "ui.panel.config.zwave.migration.ozw.introduction" + )} +
+ ${!this.hass.config.components.includes("mqtt") + ? html` + +
+

+ OpenZWave requires MQTT. Please setup an MQTT broker and + the MQTT integration to proceed with the migration. +

+
+
+ ` + : html` + ${this._step === 0 + ? html` + +
+

+ This wizard will walk through the following steps to + migrate from the legacy Z-Wave integration to + OpenZWave. +

+
    +
  1. Stop the Z-Wave network
  2. +
  3. + If running Home Assistant Core in Docker or in + Python venv: + Configure and start OZWDaemon +
  4. +
  5. Set up the OpenZWave integration
  6. +
  7. + Migrate entities and devices to the new + integration +
  8. +
  9. Remove legacy Z-Wave integration
  10. +
+

+ + Please take a backup or a snapshot of your + environment before proceeding. + +

+
+
+ + Continue + +
+
+ ` + : ``} + ${this._step === 1 + ? html` + +
+

+ We need to stop the Z-Wave network to perform the + migration. Home Assistant will not be able to + control Z-Wave devices while the network is stopped. +

+ ${this._stoppingNetwork + ? html` +
+ +

Stopping Z-Wave Network...

+
+ ` + : ``} +
+
+ + Stop Network + +
+
+ ` + : ``} + ${this._step === 2 + ? html` + +
+

+ Now it's time to set up the OZW integration. +

+ ${this.hass.config.components.includes("hassio") + ? html` +

+ The OZWDaemon runs in a Home Assistant addon + that will be setup next. Make sure to check + the checkbox for the addon. +

+ ` + : html` +

+ If you're using Home Assistant Core in Docker + or a Python venv, see the + + OZWDaemon readme + + for setup instructions. +

+

+ Here's the current Z-Wave configuration. + You'll need these values when setting up OZW + daemon. +

+ ${this._migrationConfig + ? html`
+ USB Path: + ${this._migrationConfig.usb_path}
+ Network Key: + ${this._migrationConfig.network_key} +
` + : ``} +

+ Once OZWDaemon is installed, running, and + connected to the MQTT broker click Continue to + set up the OpenZWave integration and migrate + your devices and entities. +

+ `} +
+
+ + Continue + +
+
+ ` + : ``} + ${this._step === 3 + ? html` + +
+

+ Now it's time to migrate your devices and entities + from the legacy Z-Wave integration to the OZW + integration, to make sure all your UI and + automations keep working. +

+ ${this._migrationData + ? html` +

Below is a list of what will be migrated.

+ ${this._migratedZwaveEntities!.length !== + this._migrationData.zwave_entity_ids.length + ? html`

+ Not all entities can be migrated! The + following entities will not be migrated + and might need manual adjustments to + your config: +

+
    + ${this._migrationData.zwave_entity_ids.map( + (entity_id) => + !this._migratedZwaveEntities!.includes( + entity_id + ) + ? html`
  • + ${computeStateName( + this.hass.states[entity_id] + )} + (${entity_id}) +
  • ` + : "" + )} +
` + : ""} + ${Object.keys( + this._migrationData.migration_device_map + ).length + ? html`

Devices that will be migrated:

+
    + ${Object.keys( + this._migrationData + .migration_device_map + ).map( + (device_id) => + html`
  • + ${this._computeDeviceName( + device_id + )} +
  • ` + )} +
` + : ""} + ${Object.keys( + this._migrationData.migration_entity_map + ).length + ? html`

+ Entities that will be migrated: +

+
    + ${Object.keys( + this._migrationData + .migration_entity_map + ).map( + (entity_id) => html`
  • + ${computeStateName( + this.hass.states[entity_id] + )} + (${entity_id}) +
  • ` + )} +
` + : ""} + ` + : html`
+

Loading migration data...

+ + +
`} +
+
+ + Migrate + +
+
+ ` + : ``} + ${this._step === 4 + ? html` +
+ That was all! You are now migrated to the new OZW + integration, check if all your devices and entities are + back the way they where, if not all entities could be + migrated you might have to change those manually. +
+
+ + Go to OZW config panel + +
+
` + : ""} + `} +
+
+ `; + } + + private async _getMigrationConfig(): Promise { + this._migrationConfig = await fetchMigrationConfig(this.hass!); + } + + private async _unsubscribe(): Promise { + if (this._unsub) { + (await this._unsub)(); + this._unsub = undefined; + } + } + + private _continue(): void { + this._step++; + } + + private async _stopNetwork(): Promise { + this._stoppingNetwork = true; + await this._getNetworkStatus(); + if (this._networkStatus?.state === ZWAVE_NETWORK_STATE_STOPPED) { + this._networkStopped(); + return; + } + + this._unsub = this.hass!.connection.subscribeEvents( + () => this._networkStopped(), + "zwave.network_stop" + ); + this.hass!.callService("zwave", "stop_network"); + } + + private async _setupOzw() { + const ozwConfigFlow = await startOzwConfigFlow(this.hass); + if ( + !this.hass.config.components.includes("hassio") && + this.hass.config.components.includes("ozw") + ) { + this._getMigrationData(); + this._step = 3; + return; + } + showConfigFlowDialog(this, { + continueFlowId: ozwConfigFlow.flow_id, + dialogClosedCallback: () => { + if (this.hass.config.components.includes("ozw")) { + this._getMigrationData(); + this._step = 3; + } + }, + showAdvanced: this.hass.userData?.showAdvanced, + }); + this.hass.loadBackendTranslation("title", "ozw", true); + } + + private async _getMigrationData() { + this._migrationData = await migrateZwave(this.hass, true); + this._migratedZwaveEntities = Object.keys( + this._migrationData.migration_entity_map + ); + if (Object.keys(this._migrationData.migration_device_map).length) { + this._deviceRegistry = await fetchDeviceRegistry(this.hass); + } + } + + private _computeDeviceName(deviceId) { + const device = this._deviceRegistry?.find( + (devReg) => devReg.id === deviceId + ); + if (!device) { + return deviceId; + } + return computeDeviceName(device, this.hass); + } + + private async _doMigrate() { + const data = await migrateZwave(this.hass, false); + if (!data.migrated) { + showAlertDialog(this, { title: "Migration failed!" }); + return; + } + this._step = 4; + } + + private _navigateOzw() { + navigate(this, "/config/ozw"); + } + + private _networkStopped(): void { + this._unsubscribe(); + this._getMigrationConfig(); + this._stoppingNetwork = false; + this._step = 2; + } + + private async _getNetworkStatus(): Promise { + this._networkStatus = await fetchNetworkStatus(this.hass!); + } + + static get styles(): CSSResult[] { + return [ + haStyle, + css` + .content { + margin-top: 24px; + } + + .flex-container { + display: flex; + align-items: center; + } + + .flex-container ha-circular-progress { + margin-right: 20px; + } + + blockquote { + display: block; + background-color: var(--secondary-background-color); + color: var(--primary-text-color); + padding: 8px; + margin: 8px 0; + font-size: 0.9em; + font-family: monospace; + } + + ha-card { + margin: 0 auto; + max-width: 600px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zwave-migration": ZwaveMigration; + } +} diff --git a/src/panels/config/integrations/integration-panels/zwave/zwave-node-config.ts b/src/panels/config/integrations/integration-panels/zwave/zwave-node-config.ts index a6326633c8..343cc3b479 100644 --- a/src/panels/config/integrations/integration-panels/zwave/zwave-node-config.ts +++ b/src/panels/config/integrations/integration-panels/zwave/zwave-node-config.ts @@ -7,9 +7,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, TemplateResult, } from "lit-element"; diff --git a/src/panels/config/integrations/integration-panels/zwave/zwave-node-protection.js b/src/panels/config/integrations/integration-panels/zwave/zwave-node-protection.js index ba7430a999..25921db753 100644 --- a/src/panels/config/integrations/integration-panels/zwave/zwave-node-protection.js +++ b/src/panels/config/integrations/integration-panels/zwave/zwave-node-protection.js @@ -128,11 +128,19 @@ class ZwaveNodeProtection extends LocalizeMixin(PolymerElement) { if (this.protection.length === 0) { return; } + let options = []; + let value_id = -1; + let selected = -1; + this.protection.forEach(function (item) { + if (item.key === "options") options = item.value; + else if (item.key === "value_id") value_id = item.value; + else if (item.key === "selected") selected = item.value; + }); this.setProperties({ protectionNode: true, - _protectionOptions: this.protection[0].value, - _loadedProtectionValue: this.protection[1].value, - _protectionValueID: this.protection[2].value, + _protectionOptions: options, + _loadedProtectionValue: selected, + _protectionValueID: value_id, }); } } diff --git a/src/panels/config/integrations/integration-panels/zwave/zwave-values.ts b/src/panels/config/integrations/integration-panels/zwave/zwave-values.ts index 7966f0cdeb..948e8fe492 100644 --- a/src/panels/config/integrations/integration-panels/zwave/zwave-values.ts +++ b/src/panels/config/integrations/integration-panels/zwave/zwave-values.ts @@ -6,9 +6,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import "../../../../../components/buttons/ha-call-service-button"; diff --git a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-add-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-add-node.ts new file mode 100644 index 0000000000..fa9f30869e --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-add-node.ts @@ -0,0 +1,307 @@ +import "@material/mwc-button/mwc-button"; +import { mdiCheckCircle, mdiCloseCircle } from "@mdi/js"; +import "../../../../../components/ha-switch"; +import "../../../../../components/ha-formfield"; +import { + CSSResult, + customElement, + html, + LitElement, + property, + internalProperty, + TemplateResult, + css, +} from "lit-element"; +import "../../../../../components/ha-circular-progress"; +import { createCloseHeading } from "../../../../../components/ha-dialog"; +import { haStyleDialog } from "../../../../../resources/styles"; +import { HomeAssistant } from "../../../../../types"; +import { ZWaveJSAddNodeDialogParams } from "./show-dialog-zwave_js-add-node"; +import { fireEvent } from "../../../../../common/dom/fire_event"; + +export interface ZWaveJSAddNodeDevice { + id: string; + name: string; +} + +@customElement("dialog-zwave_js-add-node") +class DialogZWaveJSAddNode extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @internalProperty() private entry_id?: string; + + @internalProperty() private _use_secure_inclusion = false; + + @internalProperty() private _status = ""; + + @internalProperty() private _device?: ZWaveJSAddNodeDevice; + + private _addNodeTimeoutHandle?: number; + + private _subscribed?: Promise<() => Promise>; + + public disconnectedCallback(): void { + super.disconnectedCallback(); + this._unsubscribe(); + } + + public async showDialog(params: ZWaveJSAddNodeDialogParams): Promise { + this.entry_id = params.entry_id; + } + + protected render(): TemplateResult { + if (!this.entry_id) { + return html``; + } + + return html` + + ${this._status === "" + ? html` +

+ ${this.hass.localize( + "ui.panel.config.zwave_js.add_node.introduction" + )} +

+
+ + + +

+ + + ${this.hass!.localize( + "ui.panel.config.zwave_js.add_node.secure_inclusion_warning" + )} + + +

+
+ + ${this._use_secure_inclusion + ? html`${this.hass.localize( + "ui.panel.config.zwave_js.add_node.start_secure_inclusion" + )}` + : html` ${this.hass.localize( + "ui.panel.config.zwave_js.add_node.start_inclusion" + )}`} + + ` + : ``} + ${this._status === "started" + ? html` +
+ +
+

+ ${this.hass.localize( + "ui.panel.config.zwave_js.add_node.controller_in_inclusion_mode" + )} +

+

+ ${this.hass.localize( + "ui.panel.config.zwave_js.add_node.follow_device_instructions" + )} +

+
+
+ + ${this.hass.localize( + "ui.panel.config.zwave_js.add_node.cancel_inclusion" + )} + + ` + : ``} + ${this._status === "failed" + ? html` +
+ +
+

+ ${this.hass.localize( + "ui.panel.config.zwave_js.add_node.inclusion_failed" + )} +

+
+
+ + ${this.hass.localize("ui.panel.config.zwave_js.common.close")} + + ` + : ``} + ${this._status === "finished" + ? html` +
+ +
+

+ ${this.hass.localize( + "ui.panel.config.zwave_js.add_node.inclusion_finished" + )} +

+ + + ${this.hass.localize( + "ui.panel.config.zwave_js.add_node.view_device" + )} + + +
+
+ + ${this.hass.localize("ui.panel.config.zwave_js.common.close")} + + ` + : ``} +
+ `; + } + + private async _secureInclusionToggleChanged(ev): Promise { + const target = ev.target; + this._use_secure_inclusion = target.checked; + } + + private _startInclusion(): void { + if (!this.hass) { + return; + } + this._subscribed = this.hass.connection.subscribeMessage( + (message) => this._handleMessage(message), + { + type: "zwave_js/add_node", + entry_id: this.entry_id, + secure: this._use_secure_inclusion, + } + ); + this._addNodeTimeoutHandle = window.setTimeout( + () => this._unsubscribe(), + 90000 + ); + } + + private _handleMessage(message: any): void { + if (message.event === "inclusion started") { + this._status = "started"; + } + if (message.event === "inclusion failed") { + this._unsubscribe(); + this._status = "failed"; + } + if (message.event === "inclusion stopped") { + if (this._status !== "finished") { + this._status = ""; + } + this._unsubscribe(); + } + if (message.event === "device registered") { + this._device = message.device; + this._status = "finished"; + this._unsubscribe(); + } + } + + private _unsubscribe(): void { + if (this._subscribed) { + this._subscribed.then((unsub) => unsub()); + this._subscribed = undefined; + } + if (this._status === "started") { + this.hass.callWS({ + type: "zwave_js/stop_inclusion", + entry_id: this.entry_id, + }); + } + if (this._status !== "finished") { + this._status = ""; + } + if (this._addNodeTimeoutHandle) { + clearTimeout(this._addNodeTimeoutHandle); + } + } + + public closeDialog(): void { + this._unsubscribe(); + this.entry_id = undefined; + this._status = ""; + this._device = undefined; + this._use_secure_inclusion = false; + + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + static get styles(): CSSResult[] { + return [ + haStyleDialog, + css` + .secure_inclusion_field { + margin-top: 48px; + } + + .success { + color: green; + } + + .failed { + color: red; + } + + blockquote { + display: block; + background-color: #ddd; + padding: 8px; + margin: 8px 0; + font-size: 0.9em; + } + + blockquote em { + font-size: 0.9em; + margin-top: 6px; + } + + .flex-container { + display: flex; + align-items: center; + } + + ha-svg-icon { + width: 68px; + height: 48px; + } + + .flex-container ha-circular-progress, + .flex-container ha-svg-icon { + margin-right: 20px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-zwave_js-add-node": DialogZWaveJSAddNode; + } +} diff --git a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-remove-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-remove-node.ts new file mode 100644 index 0000000000..a319ad8ce9 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-remove-node.ts @@ -0,0 +1,264 @@ +import "@material/mwc-button/mwc-button"; +import { + CSSResult, + customElement, + html, + LitElement, + property, + internalProperty, + TemplateResult, + css, +} from "lit-element"; +import "../../../../../components/ha-circular-progress"; +import { createCloseHeading } from "../../../../../components/ha-dialog"; +import { haStyleDialog } from "../../../../../resources/styles"; +import { HomeAssistant } from "../../../../../types"; +import { ZWaveJSRemoveNodeDialogParams } from "./show-dialog-zwave_js-remove-node"; +import { mdiCheckCircle, mdiCloseCircle } from "@mdi/js"; +import { fireEvent } from "../../../../../common/dom/fire_event"; + +export interface ZWaveJSRemovedNode { + node_id: number; + manufacturer: string; + label: string; +} + +@customElement("dialog-zwave_js-remove-node") +class DialogZWaveJSRemoveNode extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @internalProperty() private entry_id?: string; + + @internalProperty() private _status = ""; + + @internalProperty() private _node?: ZWaveJSRemovedNode; + + private _removeNodeTimeoutHandle?: number; + + private _subscribed?: Promise<() => Promise>; + + public disconnectedCallback(): void { + super.disconnectedCallback(); + this._unsubscribe(); + } + + public async showDialog( + params: ZWaveJSRemoveNodeDialogParams + ): Promise { + this.entry_id = params.entry_id; + } + + protected render(): TemplateResult { + if (!this.entry_id) { + return html``; + } + + return html` + + ${this._status === "" + ? html` +

+ ${this.hass.localize( + "ui.panel.config.zwave_js.remove_node.introduction" + )} +

+ + ${this.hass.localize( + "ui.panel.config.zwave_js.remove_node.start_exclusion" + )} + + ` + : ``} + ${this._status === "started" + ? html` +
+ +
+

+ ${this.hass.localize( + "ui.panel.config.zwave_js.remove_node.controller_in_exclusion_mode" + )} +

+

+ ${this.hass.localize( + "ui.panel.config.zwave_js.remove_node.follow_device_instructions" + )} +

+
+
+ + ${this.hass.localize( + "ui.panel.config.zwave_js.remove_node.cancel_exclusion" + )} + + ` + : ``} + ${this._status === "failed" + ? html` +
+ +
+

+ ${this.hass.localize( + "ui.panel.config.zwave_js.remove_node.exclusion_failed" + )} +

+
+
+ + ${this.hass.localize("ui.panel.config.zwave_js.common.close")} + + ` + : ``} + ${this._status === "finished" + ? html` +
+ +
+

+ ${this.hass.localize( + "ui.panel.config.zwave_js.remove_node.exclusion_finished", + "id", + this._node!.node_id + )} +

+
+
+ + ${this.hass.localize("ui.panel.config.zwave_js.common.close")} + + ` + : ``} +
+ `; + } + + private _startExclusion(): void { + if (!this.hass) { + return; + } + this._subscribed = this.hass.connection.subscribeMessage( + (message) => this._handleMessage(message), + { + type: "zwave_js/remove_node", + entry_id: this.entry_id, + } + ); + this._removeNodeTimeoutHandle = window.setTimeout( + () => this._unsubscribe(), + 120000 + ); + } + + private _handleMessage(message: any): void { + if (message.event === "exclusion started") { + this._status = "started"; + } + if (message.event === "exclusion failed") { + this._unsubscribe(); + this._status = "failed"; + } + if (message.event === "exclusion stopped") { + if (this._status !== "finished") { + this._status = ""; + } + this._unsubscribe(); + } + if (message.event === "node removed") { + this._status = "finished"; + this._node = message.node; + this._unsubscribe(); + } + } + + private _unsubscribe(): void { + if (this._subscribed) { + this._subscribed.then((unsub) => unsub()); + this._subscribed = undefined; + } + if (this._status === "started") { + this.hass.callWS({ + type: "zwave_js/stop_exclusion", + entry_id: this.entry_id, + }); + } + if (this._status !== "finished") { + this._status = ""; + } + if (this._removeNodeTimeoutHandle) { + clearTimeout(this._removeNodeTimeoutHandle); + } + } + + public closeDialog(): void { + this._unsubscribe(); + this.entry_id = undefined; + this._status = ""; + + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + static get styles(): CSSResult[] { + return [ + haStyleDialog, + css` + .success { + color: green; + } + + .failed { + color: red; + } + + blockquote { + display: block; + background-color: #ddd; + padding: 8px; + margin: 8px 0; + font-size: 0.9em; + } + + blockquote em { + font-size: 0.9em; + margin-top: 6px; + } + + .flex-container { + display: flex; + align-items: center; + } + + ha-svg-icon { + width: 68px; + height: 48px; + } + + .flex-container ha-circular-progress, + .flex-container ha-svg-icon { + margin-right: 20px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-zwave_js-remove-node": DialogZWaveJSRemoveNode; + } +} diff --git a/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node.ts new file mode 100644 index 0000000000..702a618fa3 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node.ts @@ -0,0 +1,18 @@ +import { fireEvent } from "../../../../../common/dom/fire_event"; + +export interface ZWaveJSAddNodeDialogParams { + entry_id: string; +} + +export const loadAddNodeDialog = () => import("./dialog-zwave_js-add-node"); + +export const showZWaveJSAddNodeDialog = ( + element: HTMLElement, + addNodeDialogParams: ZWaveJSAddNodeDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-zwave_js-add-node", + dialogImport: loadAddNodeDialog, + dialogParams: addNodeDialogParams, + }); +}; diff --git a/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-node.ts new file mode 100644 index 0000000000..ac76e6dbc8 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-node.ts @@ -0,0 +1,19 @@ +import { fireEvent } from "../../../../../common/dom/fire_event"; + +export interface ZWaveJSRemoveNodeDialogParams { + entry_id: string; +} + +export const loadRemoveNodeDialog = () => + import("./dialog-zwave_js-remove-node"); + +export const showZWaveJSRemoveNodeDialog = ( + element: HTMLElement, + removeNodeDialogParams: ZWaveJSRemoveNodeDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-zwave_js-remove-node", + dialogImport: loadRemoveNodeDialog, + dialogParams: removeNodeDialogParams, + }); +}; diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts new file mode 100644 index 0000000000..bc219081a4 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts @@ -0,0 +1,347 @@ +import "@material/mwc-button/mwc-button"; +import "@material/mwc-icon-button/mwc-icon-button"; +import { mdiCheckCircle, mdiCircle, mdiRefresh } from "@mdi/js"; +import { + css, + CSSResultArray, + customElement, + html, + internalProperty, + LitElement, + property, + TemplateResult, +} from "lit-element"; +import { classMap } from "lit-html/directives/class-map"; +import "../../../../../components/ha-card"; +import "../../../../../components/ha-svg-icon"; +import "../../../../../components/ha-icon-next"; +import { getSignedPath } from "../../../../../data/auth"; +import { + fetchNetworkStatus, + fetchNodeStatus, + NodeStatus, + ZWaveJSNetwork, + ZWaveJSNode, +} from "../../../../../data/zwave_js"; +import { + showAlertDialog, + showConfirmationDialog, +} from "../../../../../dialogs/generic/show-dialog-box"; +import "../../../../../layouts/hass-tabs-subpage"; +import { haStyle } from "../../../../../resources/styles"; +import type { HomeAssistant, Route } from "../../../../../types"; +import "../../../ha-config-section"; +import { showZWaveJSAddNodeDialog } from "./show-dialog-zwave_js-add-node"; +import { showZWaveJSRemoveNodeDialog } from "./show-dialog-zwave_js-remove-node"; +import { configTabs } from "./zwave_js-config-router"; + +@customElement("zwave_js-config-dashboard") +class ZWaveJSConfigDashboard extends LitElement { + @property({ type: Object }) public hass!: HomeAssistant; + + @property({ type: Object }) public route!: Route; + + @property({ type: Boolean }) public narrow!: boolean; + + @property({ type: Boolean }) public isWide!: boolean; + + @property() public configEntryId?: string; + + @internalProperty() private _network?: ZWaveJSNetwork; + + @internalProperty() private _nodes?: ZWaveJSNode[]; + + @internalProperty() private _status = "unknown"; + + @internalProperty() private _icon = mdiCircle; + + protected firstUpdated() { + if (this.hass) { + this._fetchData(); + } + } + + protected render(): TemplateResult { + return html` + + + + + +
+ ${this.hass.localize("ui.panel.config.zwave_js.dashboard.header")} +
+ +
+ ${this.hass.localize( + "ui.panel.config.zwave_js.dashboard.introduction" + )} +
+ ${this._network + ? html` + +
+
+
+ ${this._status === "connecting" + ? html`` + : html` + + `} +
+ ${this._status !== "connecting" + ? html` +
+ ${this.hass.localize( + "ui.panel.config.zwave_js.common.network" + )} + ${this.hass.localize( + `ui.panel.config.zwave_js.network_status.${this._status}` + )}
+ ${this._network.client.ws_server_url} +
+ ` + : ``} +
+
+ ${this.hass.localize( + "ui.panel.config.zwave_js.dashboard.driver_version" + )}: + ${this._network.client.driver_version}
+ ${this.hass.localize( + "ui.panel.config.zwave_js.dashboard.server_version" + )}: + ${this._network.client.server_version}
+ ${this.hass.localize( + "ui.panel.config.zwave_js.dashboard.home_id" + )}: + ${this._network.controller.home_id}
+ ${this.hass.localize( + "ui.panel.config.zwave_js.dashboard.nodes_ready" + )}: + ${this._nodes?.filter((node) => node.ready).length ?? 0} / + ${this._network.controller.nodes.length} +
+
+
+ + + ${this.hass.localize("ui.panel.config.devices.caption")} + + + + + ${this.hass.localize( + "ui.panel.config.entities.caption" + )} + + + + ${this.hass.localize( + "ui.panel.config.zwave_js.common.add_node" + )} + + + ${this.hass.localize( + "ui.panel.config.zwave_js.common.remove_node" + )} + +
+
+ ` + : ``} + +
+
+ `; + } + + private async _fetchData() { + if (!this.configEntryId) { + return; + } + this._network = await fetchNetworkStatus(this.hass!, this.configEntryId); + this._status = this._network.client.state; + if (this._status === "connected") { + this._icon = mdiCheckCircle; + } + this._fetchNodeStatus(); + } + + private async _fetchNodeStatus() { + if (!this._network) { + return; + } + const nodeStatePromisses = this._network.controller.nodes.map((nodeId) => + fetchNodeStatus(this.hass, this.configEntryId!, nodeId) + ); + this._nodes = await Promise.all(nodeStatePromisses); + } + + private async _addNodeClicked() { + showZWaveJSAddNodeDialog(this, { + entry_id: this.configEntryId!, + }); + } + + private async _removeNodeClicked() { + showZWaveJSRemoveNodeDialog(this, { + entry_id: this.configEntryId!, + }); + } + + private async _dumpDebugClicked() { + await this._fetchNodeStatus(); + + const notReadyNodes = this._nodes?.filter((node) => !node.ready); + const deadNodes = this._nodes?.filter( + (node) => node.status === NodeStatus.Dead + ); + + if (deadNodes?.length) { + await showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.zwave_js.dashboard.dump_dead_nodes_title" + ), + text: this.hass.localize( + "ui.panel.config.zwave_js.dashboard.dump_dead_nodes_text" + ), + }); + } + + if ( + notReadyNodes?.length && + notReadyNodes.length !== deadNodes?.length && + !(await showConfirmationDialog(this, { + title: this.hass.localize( + "ui.panel.config.zwave_js.dashboard.dump_not_ready_title" + ), + text: this.hass.localize( + "ui.panel.config.zwave_js.dashboard.dump_not_ready_text" + ), + confirmText: this.hass.localize( + "ui.panel.config.zwave_js.dashboard.dump_not_ready_confirm" + ), + })) + ) { + return; + } + + let signedPath: { path: string }; + try { + signedPath = await getSignedPath( + this.hass, + `/api/zwave_js/dump/${this.configEntryId}` + ); + } catch (err) { + showAlertDialog(this, { + title: "Error", + text: err.error || err.body || err, + }); + return; + } + + const a = document.createElement("a"); + a.href = signedPath.path; + a.download = `zwave_js_dump.jsonl`; + this.shadowRoot!.appendChild(a); + a.click(); + this.shadowRoot!.removeChild(a); + } + + static get styles(): CSSResultArray { + return [ + haStyle, + css` + .secondary { + color: var(--secondary-text-color); + } + .connected { + color: green; + } + .starting { + color: orange; + } + .offline { + color: red; + } + + .content { + margin-top: 24px; + } + + .sectionHeader { + position: relative; + padding-right: 40px; + } + + .network-status div.heading { + display: flex; + align-items: center; + margin-bottom: 16px; + } + + .network-status div.heading .icon { + width: 48px; + height: 48px; + margin-right: 16px; + } + .network-status div.heading ha-svg-icon { + width: 48px; + height: 48px; + } + .network-status div.heading .details { + font-size: 1.5rem; + } + + .network-status small { + font-size: 1rem; + } + + ha-card { + margin: 0 auto; + max-width: 600px; + } + + button.dump { + width: 100%; + text-align: center; + color: var(--secondary-text-color); + } + + [hidden] { + display: none; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zwave_js-config-dashboard": ZWaveJSConfigDashboard; + } +} diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-router.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-router.ts new file mode 100644 index 0000000000..659a7fb964 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-router.ts @@ -0,0 +1,71 @@ +import { customElement, property } from "lit-element"; +import { + HassRouterPage, + RouterOptions, +} from "../../../../../layouts/hass-router-page"; +import { HomeAssistant } from "../../../../../types"; +import { navigate } from "../../../../../common/navigate"; +import { PageNavigation } from "../../../../../layouts/hass-tabs-subpage"; + +import { mdiServerNetwork } from "@mdi/js"; + +export const configTabs: PageNavigation[] = [ + { + translationKey: "ui.panel.config.zwave_js.navigation.network", + path: `/config/zwave_js/dashboard`, + iconPath: mdiServerNetwork, + }, +]; + +@customElement("zwave_js-config-router") +class ZWaveJSConfigRouter extends HassRouterPage { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public isWide!: boolean; + + @property() public narrow!: boolean; + + private _configEntry = new URLSearchParams(window.location.search).get( + "config_entry" + ); + + protected routerOptions: RouterOptions = { + defaultPage: "dashboard", + showLoading: true, + routes: { + dashboard: { + tag: "zwave_js-config-dashboard", + load: () => import("./zwave_js-config-dashboard"), + }, + }, + }; + + protected updatePageEl(el): void { + el.route = this.routeTail; + el.hass = this.hass; + el.isWide = this.isWide; + el.narrow = this.narrow; + el.configEntryId = this._configEntry; + if (this._currentPage === "node") { + el.nodeId = this.routeTail.path.substr(1); + } + + const searchParams = new URLSearchParams(window.location.search); + if (this._configEntry && !searchParams.has("config_entry")) { + searchParams.append("config_entry", this._configEntry); + navigate( + this, + `${this.routeTail.prefix}${ + this.routeTail.path + }?${searchParams.toString()}`, + true + ); + } + } +} + +declare global { + interface HTMLElementTagNameMap { + "zwave_js-config-router": ZWaveJSConfigRouter; + } +} diff --git a/src/panels/config/logs/dialog-system-log-detail.ts b/src/panels/config/logs/dialog-system-log-detail.ts index 320657f8c4..0444724f77 100644 --- a/src/panels/config/logs/dialog-system-log-detail.ts +++ b/src/panels/config/logs/dialog-system-log-detail.ts @@ -1,8 +1,6 @@ -import "../../../components/ha-header-bar"; import "@material/mwc-icon-button/mwc-icon-button"; -import { mdiContentCopy, mdiClose } from "@mdi/js"; +import { mdiClose, mdiContentCopy } from "@mdi/js"; import "@polymer/paper-tooltip/paper-tooltip"; -import type { PaperTooltipElement } from "@polymer/paper-tooltip/paper-tooltip"; import { css, CSSResult, @@ -10,11 +8,12 @@ import { internalProperty, LitElement, property, - query, TemplateResult, } from "lit-element"; import { fireEvent } from "../../../common/dom/fire_event"; +import { copyToClipboard } from "../../../common/util/copy-clipboard"; import "../../../components/ha-dialog"; +import "../../../components/ha-header-bar"; import "../../../components/ha-svg-icon"; import { domainToName, @@ -25,6 +24,7 @@ import { import { getLoggedErrorIntegration } from "../../../data/system_log"; import { haStyleDialog } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; +import { showToast } from "../../../util/toast"; import type { SystemLogDetailDialogParams } from "./show-dialog-system-log-detail"; import { formatSystemLogTime } from "./util"; @@ -35,8 +35,6 @@ class DialogSystemLogDetail extends LitElement { @internalProperty() private _manifest?: IntegrationManifest; - @query("paper-tooltip") private _toolTip?: PaperTooltipElement; - public async showDialog(params: SystemLogDetailDialogParams): Promise { this._params = params; this._manifest = undefined; @@ -83,15 +81,6 @@ class DialogSystemLogDetail extends LitElement { - ${this.hass.localize("ui.common.copied")}

@@ -162,23 +151,15 @@ class DialogSystemLogDetail extends LitElement { } } - private _copyLog(): void { + private async _copyLog(): Promise { const copyElement = this.shadowRoot?.querySelector( ".contents" ) as HTMLElement; - const selection = window.getSelection()!; - const range = document.createRange(); - - range.selectNodeContents(copyElement); - selection.removeAllRanges(); - selection.addRange(range); - - document.execCommand("copy"); - window.getSelection()!.removeAllRanges(); - - this._toolTip!.show(); - setTimeout(() => this._toolTip?.hide(), 3000); + await copyToClipboard(copyElement.innerText); + showToast(this, { + message: this.hass.localize("ui.common.copied_clipboard"), + }); } static get styles(): CSSResult[] { diff --git a/src/panels/config/logs/error-log-card.ts b/src/panels/config/logs/error-log-card.ts index 89229e41ef..9da5772c9b 100644 --- a/src/panels/config/logs/error-log-card.ts +++ b/src/panels/config/logs/error-log-card.ts @@ -1,14 +1,14 @@ import "@material/mwc-button"; -import "../../../components/ha-icon-button"; import { css, CSSResult, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; +import "../../../components/ha-icon-button"; import { fetchErrorLog } from "../../../data/error_log"; import { HomeAssistant } from "../../../types"; diff --git a/src/panels/config/logs/ha-config-logs.ts b/src/panels/config/logs/ha-config-logs.ts index 178fa70863..a8c822ccd1 100644 --- a/src/panels/config/logs/ha-config-logs.ts +++ b/src/panels/config/logs/ha-config-logs.ts @@ -8,13 +8,13 @@ import { query, TemplateResult, } from "lit-element"; +import "../../../layouts/hass-tabs-subpage"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; +import { configSections } from "../ha-panel-config"; import "./error-log-card"; import "./system-log-card"; import type { SystemLogCard } from "./system-log-card"; -import { configSections } from "../ha-panel-config"; -import "../../../layouts/hass-tabs-subpage"; @customElement("ha-config-logs") export class HaConfigLogs extends LitElement { diff --git a/src/panels/config/logs/system-log-card.ts b/src/panels/config/logs/system-log-card.ts index dade77f9bb..57533373bf 100644 --- a/src/panels/config/logs/system-log-card.ts +++ b/src/panels/config/logs/system-log-card.ts @@ -1,20 +1,20 @@ -import "../../../components/ha-icon-button"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; -import "../../../components/ha-circular-progress"; import { css, CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import "../../../components/buttons/ha-call-service-button"; import "../../../components/buttons/ha-progress-button"; import "../../../components/ha-card"; +import "../../../components/ha-circular-progress"; +import "../../../components/ha-icon-button"; import { domainToName } from "../../../data/integration"; import { fetchSystemLog, diff --git a/src/panels/config/lovelace/dashboards/dialog-lovelace-dashboard-detail.ts b/src/panels/config/lovelace/dashboards/dialog-lovelace-dashboard-detail.ts index d5e4a2fa54..3ed55ea16d 100644 --- a/src/panels/config/lovelace/dashboards/dialog-lovelace-dashboard-detail.ts +++ b/src/panels/config/lovelace/dashboards/dialog-lovelace-dashboard-detail.ts @@ -4,17 +4,18 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; -import { createCloseHeading } from "../../../../components/ha-dialog"; -import "../../../../components/ha-icon-input"; -import type { HaSwitch } from "../../../../components/ha-switch"; -import "../../../../components/ha-switch"; -import "../../../../components/ha-formfield"; import { slugify } from "../../../../common/string/slugify"; +import { computeRTLDirection } from "../../../../common/util/compute_rtl"; +import { createCloseHeading } from "../../../../components/ha-dialog"; +import "../../../../components/ha-formfield"; +import "../../../../components/ha-icon-input"; +import "../../../../components/ha-switch"; +import type { HaSwitch } from "../../../../components/ha-switch"; import { LovelaceDashboard, LovelaceDashboardCreateParams, @@ -25,7 +26,6 @@ import { PolymerChangedEvent } from "../../../../polymer-types"; import { haStyleDialog } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { LovelaceDashboardDetailsDialogParams } from "./show-dialog-lovelace-dashboard-detail"; -import { computeRTLDirection } from "../../../../common/util/compute_rtl"; @customElement("dialog-lovelace-dashboard-detail") export class DialogLovelaceDashboardDetail extends LitElement { diff --git a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts index 87af0ef6bf..6a4f1d14c2 100644 --- a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts +++ b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts @@ -1,4 +1,3 @@ -import "../../../../components/ha-fab"; import { mdiPlus } from "@mdi/js"; import "@polymer/paper-tooltip/paper-tooltip"; import { @@ -17,6 +16,7 @@ import { DataTableColumnContainer, RowClickedEvent, } from "../../../../components/data-table/ha-data-table"; +import "../../../../components/ha-fab"; import "../../../../components/ha-icon"; import "../../../../components/ha-icon-button"; import "../../../../components/ha-svg-icon"; diff --git a/src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts b/src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts index 317f05f261..ff4df8eb82 100644 --- a/src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts +++ b/src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts @@ -7,9 +7,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import { createCloseHeading } from "../../../../components/ha-dialog"; diff --git a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts index c0f81a18f7..c1a975e71e 100644 --- a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts +++ b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts @@ -1,4 +1,3 @@ -import "../../../../components/ha-fab"; import { mdiPlus } from "@mdi/js"; import "@polymer/paper-checkbox/paper-checkbox"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; @@ -19,6 +18,7 @@ import { DataTableColumnContainer, RowClickedEvent, } from "../../../../components/data-table/ha-data-table"; +import "../../../../components/ha-fab"; import "../../../../components/ha-icon"; import "../../../../components/ha-svg-icon"; import { diff --git a/src/panels/config/person/dialog-person-detail.ts b/src/panels/config/person/dialog-person-detail.ts index d9edab630f..9cbe669ab4 100644 --- a/src/panels/config/person/dialog-person-detail.ts +++ b/src/panels/config/person/dialog-person-detail.ts @@ -10,33 +10,33 @@ import { TemplateResult, } from "lit-element"; import memoizeOne from "memoize-one"; +import { computeRTLDirection } from "../../../common/util/compute_rtl"; import "../../../components/entity/ha-entities-picker"; import { createCloseHeading } from "../../../components/ha-dialog"; +import "../../../components/ha-formfield"; import "../../../components/ha-picture-upload"; import type { HaPictureUpload } from "../../../components/ha-picture-upload"; +import { adminChangePassword } from "../../../data/auth"; import { PersonMutableParams } from "../../../data/person"; +import { + deleteUser, + SYSTEM_GROUP_ID_ADMIN, + SYSTEM_GROUP_ID_USER, + updateUser, + User, +} from "../../../data/user"; +import { + showAlertDialog, + showConfirmationDialog, + showPromptDialog, +} from "../../../dialogs/generic/show-dialog-box"; import { CropOptions } from "../../../dialogs/image-cropper-dialog/show-image-cropper-dialog"; import { PolymerChangedEvent } from "../../../polymer-types"; import { haStyleDialog } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; -import { PersonDetailDialogParams } from "./show-dialog-person-detail"; -import "../../../components/ha-formfield"; -import { computeRTLDirection } from "../../../common/util/compute_rtl"; -import { - User, - SYSTEM_GROUP_ID_ADMIN, - deleteUser, - SYSTEM_GROUP_ID_USER, - updateUser, -} from "../../../data/user"; -import { - showAlertDialog, - showPromptDialog, - showConfirmationDialog, -} from "../../../dialogs/generic/show-dialog-box"; -import { adminChangePassword } from "../../../data/auth"; import { showAddUserDialog } from "../users/show-dialog-add-user"; +import { PersonDetailDialogParams } from "./show-dialog-person-detail"; const includeDomains = ["device_tracker"]; diff --git a/src/panels/config/person/ha-config-person.ts b/src/panels/config/person/ha-config-person.ts index 013b7dabd8..0a6a0128ae 100644 --- a/src/panels/config/person/ha-config-person.ts +++ b/src/panels/config/person/ha-config-person.ts @@ -1,4 +1,3 @@ -import "../../../components/ha-fab"; import { mdiPlus } from "@mdi/js"; import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-item-body"; @@ -13,6 +12,7 @@ import { } from "lit-element"; import { compare } from "../../../common/string/compare"; import "../../../components/ha-card"; +import "../../../components/ha-fab"; import "../../../components/ha-svg-icon"; import "../../../components/user/ha-person-badge"; import { diff --git a/src/panels/config/scene/ha-config-scene.ts b/src/panels/config/scene/ha-config-scene.ts index 3e5d5fe9e7..5cae67b775 100644 --- a/src/panels/config/scene/ha-config-scene.ts +++ b/src/panels/config/scene/ha-config-scene.ts @@ -2,6 +2,7 @@ import { HassEntities } from "home-assistant-js-websocket"; import { customElement, property, PropertyValues } from "lit-element"; import memoizeOne from "memoize-one"; import { computeStateDomain } from "../../../common/entity/compute_state_domain"; +import { debounce } from "../../../common/util/debounce"; import { SceneEntity } from "../../../data/scene"; import { HassRouterPage, @@ -10,7 +11,6 @@ import { import { HomeAssistant } from "../../../types"; import "./ha-scene-dashboard"; import "./ha-scene-editor"; -import { debounce } from "../../../common/util/debounce"; const equal = (a: SceneEntity[], b: SceneEntity[]): boolean => { if (a.length !== b.length) { diff --git a/src/panels/config/scene/ha-scene-dashboard.ts b/src/panels/config/scene/ha-scene-dashboard.ts index 879f1893cd..d9b141e9a2 100644 --- a/src/panels/config/scene/ha-scene-dashboard.ts +++ b/src/panels/config/scene/ha-scene-dashboard.ts @@ -1,6 +1,5 @@ -import "../../../components/ha-fab"; import "@material/mwc-icon-button"; -import { mdiPlus, mdiHelpCircle } from "@mdi/js"; +import { mdiHelpCircle, mdiPlus } from "@mdi/js"; import "@polymer/paper-tooltip/paper-tooltip"; import { css, @@ -17,6 +16,7 @@ import { fireEvent } from "../../../common/dom/fire_event"; import { computeStateName } from "../../../common/entity/compute_state_name"; import { stateIcon } from "../../../common/entity/state_icon"; import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table"; +import "../../../components/ha-fab"; import "../../../components/ha-icon"; import "../../../components/ha-icon-button"; import "../../../components/ha-svg-icon"; diff --git a/src/panels/config/scene/ha-scene-editor.ts b/src/panels/config/scene/ha-scene-editor.ts index 73b0d2cae6..5a3e8c965a 100644 --- a/src/panels/config/scene/ha-scene-editor.ts +++ b/src/panels/config/scene/ha-scene-editor.ts @@ -1,4 +1,4 @@ -import "../../../components/ha-icon-button"; +import { mdiContentSave } from "@mdi/js"; import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; @@ -8,9 +8,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, TemplateResult, } from "lit-element"; @@ -24,8 +24,10 @@ import { computeRTL } from "../../../common/util/compute_rtl"; import "../../../components/device/ha-device-picker"; import "../../../components/entity/ha-entities-picker"; import "../../../components/ha-card"; -import "../../../components/ha-icon-input"; import "../../../components/ha-fab"; +import "../../../components/ha-icon-button"; +import "../../../components/ha-icon-input"; +import "../../../components/ha-svg-icon"; import { computeDeviceName, DeviceRegistryEntry, @@ -48,18 +50,16 @@ import { SCENE_IGNORED_DOMAINS, } from "../../../data/scene"; import { - showConfirmationDialog, showAlertDialog, + showConfirmationDialog, } from "../../../dialogs/generic/show-dialog-box"; +import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; +import { showToast } from "../../../util/toast"; import "../ha-config-section"; import { configSections } from "../ha-panel-config"; -import "../../../components/ha-svg-icon"; -import { showToast } from "../../../util/toast"; -import { mdiContentSave } from "@mdi/js"; -import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin"; interface DeviceEntities { id: string; diff --git a/src/panels/config/script/ha-config-script.ts b/src/panels/config/script/ha-config-script.ts index a1e9643c84..93e52ea6a4 100644 --- a/src/panels/config/script/ha-config-script.ts +++ b/src/panels/config/script/ha-config-script.ts @@ -2,6 +2,8 @@ import { HassEntities } from "home-assistant-js-websocket"; import { customElement, property, PropertyValues } from "lit-element"; import memoizeOne from "memoize-one"; import { computeStateDomain } from "../../../common/entity/compute_state_domain"; +import { debounce } from "../../../common/util/debounce"; +import { ScriptEntity } from "../../../data/script"; import { HassRouterPage, RouterOptions, @@ -9,8 +11,6 @@ import { import { HomeAssistant } from "../../../types"; import "./ha-script-editor"; import "./ha-script-picker"; -import { debounce } from "../../../common/util/debounce"; -import { ScriptEntity } from "../../../data/script"; const equal = (a: ScriptEntity[], b: ScriptEntity[]): boolean => { if (a.length !== b.length) { diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index 8683d5d31f..d658ea7fe4 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -1,16 +1,15 @@ -import "../../../components/ha-fab"; +import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; +import "@material/mwc-list/mwc-list-item"; import { mdiCheck, + mdiContentDuplicate, mdiContentSave, mdiDelete, mdiDotsVertical, - mdiContentDuplicate, } from "@mdi/js"; import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light"; -import "@material/mwc-list/mwc-list-item"; -import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import { PaperListboxElement } from "@polymer/paper-listbox"; import { css, @@ -20,22 +19,23 @@ import { LitElement, property, PropertyValues, - TemplateResult, query, + TemplateResult, } from "lit-element"; import { classMap } from "lit-html/directives/class-map"; import { computeObjectId } from "../../../common/entity/compute_object_id"; import { navigate } from "../../../common/navigate"; import { slugify } from "../../../common/string/slugify"; import { computeRTL } from "../../../common/util/compute_rtl"; +import { copyToClipboard } from "../../../common/util/copy-clipboard"; import "../../../components/ha-button-menu"; import "../../../components/ha-card"; +import "../../../components/ha-fab"; import "../../../components/ha-icon-button"; import "../../../components/ha-icon-input"; import "../../../components/ha-svg-icon"; import "../../../components/ha-yaml-editor"; import type { HaYamlEditor } from "../../../components/ha-yaml-editor"; -import { copyToClipboard } from "../../../common/util/copy-clipboard"; import { Action, deleteScript, @@ -544,9 +544,12 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { return this._config; } - private async _copyYaml() { + private async _copyYaml(): Promise { if (this._editor?.yaml) { - copyToClipboard(this._editor.yaml); + await copyToClipboard(this._editor.yaml); + showToast(this, { + message: this.hass.localize("ui.common.copied_clipboard"), + }); } } @@ -657,9 +660,9 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { } }, (errors) => { - this._errors = errors.body.message; + this._errors = errors.body.message || errors.error || errors.body; showToast(this, { - message: errors.body.message, + message: errors.body.message || errors.error || errors.body, }); throw errors; } diff --git a/src/panels/config/script/ha-script-picker.ts b/src/panels/config/script/ha-script-picker.ts index 6718ef9682..81a3d71f4d 100644 --- a/src/panels/config/script/ha-script-picker.ts +++ b/src/panels/config/script/ha-script-picker.ts @@ -1,5 +1,5 @@ import "@material/mwc-icon-button"; -import "../../../components/ha-icon-button"; +import { mdiHelpCircle, mdiPlus } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; import { css, @@ -14,20 +14,20 @@ import memoizeOne from "memoize-one"; import { formatDateTime } from "../../../common/datetime/format_date_time"; import { fireEvent } from "../../../common/dom/fire_event"; import { computeStateName } from "../../../common/entity/compute_state_name"; +import { stateIcon } from "../../../common/entity/state_icon"; import { computeRTL } from "../../../common/util/compute_rtl"; import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table"; import "../../../components/ha-fab"; +import "../../../components/ha-icon-button"; +import "../../../components/ha-svg-icon"; import { triggerScript } from "../../../data/script"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-tabs-subpage-data-table"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; +import { documentationUrl } from "../../../util/documentation-url"; import { showToast } from "../../../util/toast"; import { configSections } from "../ha-panel-config"; -import "../../../components/ha-svg-icon"; -import { mdiPlus, mdiHelpCircle } from "@mdi/js"; -import { stateIcon } from "../../../common/entity/state_icon"; -import { documentationUrl } from "../../../util/documentation-url"; @customElement("ha-script-picker") class HaScriptPicker extends LitElement { diff --git a/src/panels/config/tags/dialog-tag-detail.ts b/src/panels/config/tags/dialog-tag-detail.ts index 411a3393ef..12f124d5c1 100644 --- a/src/panels/config/tags/dialog-tag-detail.ts +++ b/src/panels/config/tags/dialog-tag-detail.ts @@ -10,7 +10,6 @@ import { property, TemplateResult, } from "lit-element"; - import { fireEvent } from "../../../common/dom/fire_event"; import { createCloseHeading } from "../../../components/ha-dialog"; import "../../../components/ha-formfield"; diff --git a/src/panels/config/tags/ha-config-tags.ts b/src/panels/config/tags/ha-config-tags.ts index 713b14d72a..cffe11f6b5 100644 --- a/src/panels/config/tags/ha-config-tags.ts +++ b/src/panels/config/tags/ha-config-tags.ts @@ -1,4 +1,3 @@ -import "../../../components/ha-fab"; import "@material/mwc-icon-button"; import { mdiCog, @@ -18,6 +17,7 @@ import { import memoizeOne from "memoize-one"; import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table"; import "../../../components/ha-card"; +import "../../../components/ha-fab"; import "../../../components/ha-relative-time"; import { showAutomationEditor, TagTrigger } from "../../../data/automation"; import { diff --git a/src/panels/config/tags/tag-image.ts b/src/panels/config/tags/tag-image.ts index c1b10ca86c..7392b935cb 100644 --- a/src/panels/config/tags/tag-image.ts +++ b/src/panels/config/tags/tag-image.ts @@ -1,13 +1,13 @@ +import { mdiNfcVariant } from "@mdi/js"; import { - property, - customElement, - LitElement, - html, - CSSResult, css, + CSSResult, + customElement, + html, + LitElement, + property, } from "lit-element"; import "../../../components/ha-svg-icon"; -import { mdiNfcVariant } from "@mdi/js"; import { TagRowData } from "./ha-config-tags"; @customElement("tag-image") diff --git a/src/panels/config/users/dialog-user-detail.ts b/src/panels/config/users/dialog-user-detail.ts index b854da0666..7882b5ba16 100644 --- a/src/panels/config/users/dialog-user-detail.ts +++ b/src/panels/config/users/dialog-user-detail.ts @@ -13,8 +13,8 @@ import { } from "lit-element"; import { computeRTLDirection } from "../../../common/util/compute_rtl"; import { createCloseHeading } from "../../../components/ha-dialog"; -import "../../../components/ha-help-tooltip"; import "../../../components/ha-formfield"; +import "../../../components/ha-help-tooltip"; import "../../../components/ha-switch"; import { adminChangePassword } from "../../../data/auth"; import { diff --git a/src/panels/config/users/ha-config-users.ts b/src/panels/config/users/ha-config-users.ts index db920b223f..74dde61fc3 100644 --- a/src/panels/config/users/ha-config-users.ts +++ b/src/panels/config/users/ha-config-users.ts @@ -1,4 +1,3 @@ -import "../../../components/ha-fab"; import { mdiPlus } from "@mdi/js"; import { customElement, @@ -13,6 +12,7 @@ import { DataTableColumnContainer, RowClickedEvent, } from "../../../components/data-table/ha-data-table"; +import "../../../components/ha-fab"; import "../../../components/ha-svg-icon"; import { deleteUser, fetchUsers, updateUser, User } from "../../../data/user"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; diff --git a/src/panels/config/zone/dialog-zone-detail.ts b/src/panels/config/zone/dialog-zone-detail.ts index f31f5e0565..b2dd807734 100644 --- a/src/panels/config/zone/dialog-zone-detail.ts +++ b/src/panels/config/zone/dialog-zone-detail.ts @@ -4,16 +4,17 @@ import { css, CSSResult, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import { fireEvent } from "../../../common/dom/fire_event"; import { addDistanceToCoord } from "../../../common/location/add_distance_to_coord"; +import { computeRTLDirection } from "../../../common/util/compute_rtl"; import { createCloseHeading } from "../../../components/ha-dialog"; -import "../../../components/ha-switch"; import "../../../components/ha-formfield"; +import "../../../components/ha-switch"; import "../../../components/map/ha-location-editor"; import { defaultRadiusColor, @@ -24,7 +25,6 @@ import { import { haStyleDialog } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import { ZoneDetailDialogParams } from "./show-dialog-zone-detail"; -import { computeRTLDirection } from "../../../common/util/compute_rtl"; class DialogZoneDetail extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; diff --git a/src/panels/custom/ha-panel-custom.ts b/src/panels/custom/ha-panel-custom.ts index e64476fef6..e000a4c7dd 100644 --- a/src/panels/custom/ha-panel-custom.ts +++ b/src/panels/custom/ha-panel-custom.ts @@ -4,8 +4,8 @@ import { CustomPanelInfo } from "../../data/panel_custom"; import { HomeAssistant, Route } from "../../types"; import { createCustomPanelElement } from "../../util/custom-panel/create-custom-panel-element"; import { - loadCustomPanel, getUrl, + loadCustomPanel, } from "../../util/custom-panel/load-custom-panel"; import { setCustomPanelProperties } from "../../util/custom-panel/set-custom-panel-properties"; diff --git a/src/panels/developer-tools/event/developer-tools-event.js b/src/panels/developer-tools/event/developer-tools-event.js index ec7526fee9..7da723ee44 100644 --- a/src/panels/developer-tools/event/developer-tools-event.js +++ b/src/panels/developer-tools/event/developer-tools-event.js @@ -9,8 +9,8 @@ import "../../../components/ha-code-editor"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { EventsMixin } from "../../../mixins/events-mixin"; import LocalizeMixin from "../../../mixins/localize-mixin"; -import { documentationUrl } from "../../../util/documentation-url"; import "../../../styles/polymer-ha-style"; +import { documentationUrl } from "../../../util/documentation-url"; import "./event-subscribe-card"; import "./events-list"; diff --git a/src/panels/developer-tools/event/event-subscribe-card.ts b/src/panels/developer-tools/event/event-subscribe-card.ts index f2f386d1ec..7d8350d0de 100644 --- a/src/panels/developer-tools/event/event-subscribe-card.ts +++ b/src/panels/developer-tools/event/event-subscribe-card.ts @@ -6,9 +6,9 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; import { formatTime } from "../../../common/datetime/format_time"; diff --git a/src/panels/developer-tools/ha-panel-developer-tools.ts b/src/panels/developer-tools/ha-panel-developer-tools.ts index 630853e5ca..3ad8fe9887 100644 --- a/src/panels/developer-tools/ha-panel-developer-tools.ts +++ b/src/panels/developer-tools/ha-panel-developer-tools.ts @@ -1,9 +1,6 @@ import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; -import "../../layouts/ha-app-layout"; -import "../../components/ha-icon-button"; import "@polymer/paper-tabs/paper-tab"; -import "../../components/ha-tabs"; import { css, CSSResultArray, @@ -14,7 +11,10 @@ import { TemplateResult, } from "lit-element"; import { navigate } from "../../common/navigate"; +import "../../components/ha-icon-button"; import "../../components/ha-menu-button"; +import "../../components/ha-tabs"; +import "../../layouts/ha-app-layout"; import { haStyle } from "../../resources/styles"; import { HomeAssistant, Route } from "../../types"; import "./developer-tools-router"; @@ -109,7 +109,10 @@ class PanelDeveloperTools extends LitElement { ha-tabs { margin-left: max(env(safe-area-inset-left), 24px); margin-right: max(env(safe-area-inset-right), 24px); - --paper-tabs-selection-bar-color: var(--text-primary-color, #fff); + --paper-tabs-selection-bar-color: var( + --app-header-selection-bar-color, + var(--app-header-text-color, #fff) + ); text-transform: uppercase; } `, diff --git a/src/panels/developer-tools/service/developer-tools-service.js b/src/panels/developer-tools/service/developer-tools-service.js index 71c2a91949..73eee72788 100644 --- a/src/panels/developer-tools/service/developer-tools-service.js +++ b/src/panels/developer-tools/service/developer-tools-service.js @@ -5,9 +5,9 @@ import { safeDump, safeLoad } from "js-yaml"; import { computeRTL } from "../../../common/util/compute_rtl"; import "../../../components/buttons/ha-progress-button"; import "../../../components/entity/ha-entity-picker"; +import "../../../components/ha-card"; import "../../../components/ha-code-editor"; import "../../../components/ha-service-picker"; -import "../../../components/ha-card"; import { ENTITY_COMPONENT_DOMAINS } from "../../../data/entity"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import LocalizeMixin from "../../../mixins/localize-mixin"; diff --git a/src/panels/developer-tools/state/developer-tools-state.js b/src/panels/developer-tools/state/developer-tools-state.js index 02f6e67ec2..eb5f6c580a 100644 --- a/src/panels/developer-tools/state/developer-tools-state.js +++ b/src/panels/developer-tools/state/developer-tools-state.js @@ -1,20 +1,20 @@ import "@material/mwc-button"; +import { mdiInformationOutline } from "@mdi/js"; import "@polymer/paper-checkbox/paper-checkbox"; import "@polymer/paper-input/paper-input"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; import { safeDump, safeLoad } from "js-yaml"; +import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time"; +import { computeRTL } from "../../../common/util/compute_rtl"; import "../../../components/entity/ha-entity-picker"; -import "../../../components/ha-svg-icon"; import "../../../components/ha-code-editor"; +import "../../../components/ha-svg-icon"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { EventsMixin } from "../../../mixins/events-mixin"; import LocalizeMixin from "../../../mixins/localize-mixin"; import "../../../styles/polymer-ha-style"; -import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time"; -import { mdiInformationOutline } from "@mdi/js"; -import { computeRTL } from "../../../common/util/compute_rtl"; const ERROR_SENTINEL = {}; /* diff --git a/src/panels/history/ha-panel-history.ts b/src/panels/history/ha-panel-history.ts index 566aa3c0da..80f9591547 100644 --- a/src/panels/history/ha-panel-history.ts +++ b/src/panels/history/ha-panel-history.ts @@ -1,24 +1,24 @@ -import "../../layouts/ha-app-layout"; import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; -import { computeRTL } from "../../common/util/compute_rtl"; -import "../../components/ha-menu-button"; -import "../../components/state-history-charts"; import { - LitElement, css, - property, internalProperty, + LitElement, + property, PropertyValues, } from "lit-element"; import { html } from "lit-html"; +import { computeRTL } from "../../common/util/compute_rtl"; +import "../../components/entity/ha-entity-picker"; +import "../../components/ha-circular-progress"; +import "../../components/ha-date-range-picker"; +import type { DateRangePickerRanges } from "../../components/ha-date-range-picker"; +import "../../components/ha-menu-button"; +import "../../components/state-history-charts"; +import { computeHistory, fetchDate } from "../../data/history"; +import "../../layouts/ha-app-layout"; import { haStyle } from "../../resources/styles"; import { HomeAssistant } from "../../types"; -import type { DateRangePickerRanges } from "../../components/ha-date-range-picker"; -import "../../components/ha-date-range-picker"; -import "../../components/entity/ha-entity-picker"; -import { fetchDate, computeHistory } from "../../data/history"; -import "../../components/ha-circular-progress"; class HaPanelHistory extends LitElement { @property() hass!: HomeAssistant; @@ -119,27 +119,21 @@ class HaPanelHistory extends LitElement { todayEnd.setDate(todayEnd.getDate() + 1); todayEnd.setMilliseconds(todayEnd.getMilliseconds() - 1); - const todayCopy = new Date(today); - - const yesterday = new Date(todayCopy.setDate(today.getDate() - 1)); - const yesterdayEnd = new Date(yesterday); - yesterdayEnd.setDate(yesterdayEnd.getDate() + 1); + const yesterday = new Date(today); + yesterday.setDate(today.getDate() - 1); + const yesterdayEnd = new Date(today); yesterdayEnd.setMilliseconds(yesterdayEnd.getMilliseconds() - 1); - const thisWeekStart = new Date( - todayCopy.setDate(today.getDate() - today.getDay()) - ); - const thisWeekEnd = new Date( - todayCopy.setDate(thisWeekStart.getDate() + 7) - ); + const thisWeekStart = new Date(today); + thisWeekStart.setDate(today.getDate() - today.getDay()); + const thisWeekEnd = new Date(thisWeekStart); + thisWeekEnd.setDate(thisWeekStart.getDate() + 7); thisWeekEnd.setMilliseconds(thisWeekEnd.getMilliseconds() - 1); - const lastWeekStart = new Date( - todayCopy.setDate(today.getDate() - today.getDay() - 7) - ); - const lastWeekEnd = new Date( - todayCopy.setDate(lastWeekStart.getDate() + 7) - ); + const lastWeekStart = new Date(today); + lastWeekStart.setDate(today.getDate() - today.getDay() - 7); + const lastWeekEnd = new Date(lastWeekStart); + lastWeekEnd.setDate(lastWeekStart.getDate() + 7); lastWeekEnd.setMilliseconds(lastWeekEnd.getMilliseconds() - 1); this._ranges = { diff --git a/src/panels/iframe/ha-panel-iframe.js b/src/panels/iframe/ha-panel-iframe.js index 28d220de25..eee67cf102 100644 --- a/src/panels/iframe/ha-panel-iframe.js +++ b/src/panels/iframe/ha-panel-iframe.js @@ -24,7 +24,7 @@ class HaPanelIframe extends PolymerElement {