Compare commits

..

1 Commits

Author SHA1 Message Date
Zack
1f6243145f Add use_uuid 2022-03-24 13:43:59 -05:00
508 changed files with 8242 additions and 21746 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Upload Translations
run: |

2
.vscode/tasks.json vendored
View File

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

View File

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

View File

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

View File

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

View File

@@ -3,10 +3,10 @@ const webpack = require("webpack");
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const log = require("fancy-log");
const WebpackBar = require("webpackbar");
const paths = require("./paths.js");
const bundle = require("./bundle.js");
const log = require("fancy-log");
const WebpackBar = require("webpackbar");
class LogStartCompilePlugin {
ignoredFirst = false;
@@ -138,8 +138,6 @@ const createWebpackConfig = ({
"lit/directives/cache$": "lit/directives/cache.js",
"lit/directives/repeat$": "lit/directives/repeat.js",
"lit/polyfill-support$": "lit/polyfill-support.js",
"@lit-labs/virtualizer/layouts/grid":
"@lit-labs/virtualizer/layouts/grid.js",
},
},
output: {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockConfig = (hass: MockHomeAssistant) => {
hass.mockAPI("config/config_entries/entry?domain=co2signal", () => [
hass.mockAPI("config/config_entries/entry", () => [
{
entry_id: "co2signal",
domain: "co2signal",

View File

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

View File

@@ -31,7 +31,7 @@ export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
version_latest: "3.6.2",
update_available: false,
repository: "a0d7b954",
icon: false,
icon: true,
logo: true,
},
{

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -53,19 +53,13 @@ class DemoBlackWhiteRow extends LitElement {
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
applyThemesOnElement(
this.shadowRoot!.querySelector(".dark"),
{
default_theme: "default",
default_dark_theme: "default",
themes: {},
darkMode: true,
theme: "default",
},
undefined,
undefined,
true
);
applyThemesOnElement(this.shadowRoot!.querySelector(".dark"), {
default_theme: "default",
default_dark_theme: "default",
themes: {},
darkMode: true,
theme: "default",
});
}
handleSubmit(ev) {

View File

@@ -1,11 +0,0 @@
export const LONG_TEXT = `
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc laoreet velit ut elit volutpat, eget ultrices odio lacinia. In imperdiet malesuada est, nec sagittis metus ultricies quis. Sed nisl ex, convallis porttitor ante quis, hendrerit tristique justo. Mauris pharetra venenatis augue, eu maximus sem cursus in. Quisque sed consequat risus. Suspendisse facilisis ligula a odio consectetur condimentum. Curabitur vehicula elit nec augue mollis, et volutpat massa dictum.
Nam pellentesque auctor rutrum. Suspendisse elit est, sodales vel diam nec, porttitor faucibus massa. Ut pretium ac orci eu pharetra. Praesent in nibh at magna viverra rutrum eu vitae tortor. Etiam eget sem ex. Fusce tristique odio nec lacus mattis, vitae tempor nunc malesuada. Maecenas faucibus magna vel libero maximus egestas. Vestibulum luctus semper velit, in lobortis risus tempus non. Curabitur bibendum ornare commodo. Quisque commodo neque sit amet tincidunt lacinia. Proin elementum ante velit, eu congue nulla semper quis. Pellentesque consequat vel nunc at scelerisque. Mauris sit amet venenatis diam, blandit viverra leo. Integer commodo laoreet orci.
Curabitur ipsum tortor, sodales ut augue sed, commodo porttitor libero. Pellentesque molestie vitae mi consectetur tempor. In sed lectus consequat, lobortis neque non, semper ipsum. Etiam eget ex et nibh sagittis pulvinar lacinia ac mauris. Aenean ligula eros, viverra ac nibh at, venenatis semper quam. Sed interdum ligula sit amet massa tincidunt tincidunt. Suspendisse potenti. Aliquam egestas facilisis est, sed faucibus erat scelerisque id. Duis dolor quam, viverra vitae orci euismod, laoreet pellentesque justo. Nunc malesuada non erat at ullamcorper. Mauris eget posuere odio. Vestibulum turpis nunc, pharetra eget ante in, feugiat mollis justo. Proin porttitor, diam nec vulputate pretium, tellus arcu rhoncus turpis, a blandit nisi nulla quis arcu. Nunc ac ullamcorper ligula, nec facilisis leo.
In vitae eros sollicitudin, iaculis ex eget, egestas orci. Etiam sed pretium lorem. Nam nisi enim, consectetur sit amet semper ac, semper pharetra diam. In pulvinar neque sapien, ac ullamcorper est lacinia a. Etiam tincidunt velit sed diam malesuada, eu ornare ex consectetur. Phasellus in imperdiet tellus. Sed bibendum, dui sit amet fringilla aliquet, enim odio sollicitudin lorem, vel semper turpis mauris vel mauris. Aenean congue magna ac massa cursus, in dictum orci commodo. Pellentesque mollis velit in sollicitudin tincidunt. Vestibulum et efficitur nulla.
Quisque posuere, velit sed porttitor dapibus, neque augue fringilla felis, eu luctus nisi nisl nec ipsum. Curabitur pellentesque ac lectus eget ultricies. Vestibulum est dolor, lacinia pharetra vulputate a, facilisis a magna. Nam vitae arcu nibh. Praesent finibus blandit ante, ac gravida ex mollis eget. Donec quam est, pulvinar vitae neque ut, bibendum aliquam erat. Nullam mollis arcu at sem tincidunt, in tristique lectus facilisis. Aenean ut lacus vel nisl finibus iaculis non a turpis. Integer eget ipsum ante. Donec nunc neque, vestibulum ac magna ac, posuere scelerisque dui. Pellentesque massa nibh, rhoncus id dolor quis, placerat posuere turpis. Donec aliquet augue nisi, eu finibus dui auctor et. Vestibulum eu varius lorem. Quisque lectus ante, malesuada pretium risus eget, interdum mattis enim.
`;

View File

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

View File

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

View File

@@ -62,45 +62,6 @@ const ACTIONS = [
entity_id: "input_boolean.toggle_4",
},
},
{
parallel: [
{ scene: "scene.kitchen_morning" },
{
service: "media_player.play_media",
target: { entity_id: "media_player.living_room" },
data: { media_content_id: "", media_content_type: "" },
metadata: { title: "Happy Song" },
},
],
},
{
stop: "No one is home!",
},
{ repeat: { count: 3, sequence: [{ delay: "00:00:01" }] } },
{
repeat: {
for_each: ["bread", "butter", "cheese"],
sequence: [{ delay: "00:00:01" }],
},
},
{
if: [{ condition: "state" }],
then: [{ delay: "00:00:01" }],
else: [{ delay: "00:00:05" }],
},
{
choose: [
{
conditions: [{ condition: "state" }],
sequence: [{ delay: "00:00:01" }],
},
{
conditions: [{ condition: "sun" }],
sequence: [{ delay: "00:00:05" }],
},
],
default: [{ delay: "00:00:03" }],
},
];
@customElement("demo-automation-describe-action")

View File

@@ -20,10 +20,6 @@ import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
import { Action } from "../../../../src/data/script";
import { HaConditionAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-condition";
import { HaParallelAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-parallel";
import { HaIfAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-if";
import { HaStopAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-stop";
import { HaPlayMediaAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-play_media";
const SCHEMAS: { name: string; actions: Action[] }[] = [
{ name: "Event", actions: [HaEventAction.defaultConfig] },
@@ -32,15 +28,11 @@ const SCHEMAS: { name: string; actions: Action[] }[] = [
{ name: "Condition", actions: [HaConditionAction.defaultConfig] },
{ name: "Delay", actions: [HaDelayAction.defaultConfig] },
{ name: "Scene", actions: [HaSceneAction.defaultConfig] },
{ name: "Play media", actions: [HaPlayMediaAction.defaultConfig] },
{ name: "Wait", actions: [HaWaitAction.defaultConfig] },
{ name: "WaitForTrigger", actions: [HaWaitForTriggerAction.defaultConfig] },
{ name: "Repeat", actions: [HaRepeatAction.defaultConfig] },
{ name: "If-Then", actions: [HaIfAction.defaultConfig] },
{ name: "Choose", actions: [HaChooseAction.defaultConfig] },
{ name: "Variables", actions: [{ variables: { hello: "1" } }] },
{ name: "Parallel", actions: [HaParallelAction.defaultConfig] },
{ name: "Stop", actions: [HaStopAction.defaultConfig] },
];
@customElement("demo-automation-editor-action")
@@ -94,6 +86,6 @@ class DemoHaAutomationEditorAction extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"demo-automation-editor-action": DemoHaAutomationEditorAction;
"demo-ha-automation-editor-action": DemoHaAutomationEditorAction;
}
}

View File

@@ -8,7 +8,7 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
import type { ConditionWithShorthand } from "../../../../src/data/automation";
import type { Condition } from "../../../../src/data/automation";
import "../../../../src/panels/config/automation/condition/ha-automation-condition";
import { HaDeviceCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-device";
import { HaLogicalCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-logical";
@@ -20,7 +20,7 @@ import { HaTimeCondition } from "../../../../src/panels/config/automation/condit
import { HaTriggerCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-trigger";
import { HaZoneCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-zone";
const SCHEMAS: { name: string; conditions: ConditionWithShorthand[] }[] = [
const SCHEMAS: { name: string; conditions: Condition[] }[] = [
{
name: "State",
conditions: [{ condition: "state", ...HaStateCondition.defaultConfig }],
@@ -69,14 +69,6 @@ const SCHEMAS: { name: string; conditions: ConditionWithShorthand[] }[] = [
name: "Trigger",
conditions: [{ condition: "trigger", ...HaTriggerCondition.defaultConfig }],
},
{
name: "Shorthand",
conditions: [
{ and: HaLogicalCondition.defaultConfig.conditions },
{ or: HaLogicalCondition.defaultConfig.conditions },
{ not: HaLogicalCondition.defaultConfig.conditions },
],
},
];
@customElement("demo-automation-editor-condition")

View File

@@ -159,19 +159,13 @@ export class DemoHaAlert extends LitElement {
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
applyThemesOnElement(
this.shadowRoot!.querySelector(".dark"),
{
default_theme: "default",
default_dark_theme: "default",
themes: {},
darkMode: true,
theme: "default",
},
undefined,
undefined,
true
);
applyThemesOnElement(this.shadowRoot!.querySelector(".dark"), {
default_theme: "default",
default_dark_theme: "default",
themes: {},
darkMode: true,
theme: "default",
});
}
static get styles() {

View File

@@ -3,7 +3,18 @@ import { customElement } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-faded";
import "../../../../src/components/ha-markdown";
import { LONG_TEXT } from "../../data/text";
const LONG_TEXT = `
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc laoreet velit ut elit volutpat, eget ultrices odio lacinia. In imperdiet malesuada est, nec sagittis metus ultricies quis. Sed nisl ex, convallis porttitor ante quis, hendrerit tristique justo. Mauris pharetra venenatis augue, eu maximus sem cursus in. Quisque sed consequat risus. Suspendisse facilisis ligula a odio consectetur condimentum. Curabitur vehicula elit nec augue mollis, et volutpat massa dictum.
Nam pellentesque auctor rutrum. Suspendisse elit est, sodales vel diam nec, porttitor faucibus massa. Ut pretium ac orci eu pharetra. Praesent in nibh at magna viverra rutrum eu vitae tortor. Etiam eget sem ex. Fusce tristique odio nec lacus mattis, vitae tempor nunc malesuada. Maecenas faucibus magna vel libero maximus egestas. Vestibulum luctus semper velit, in lobortis risus tempus non. Curabitur bibendum ornare commodo. Quisque commodo neque sit amet tincidunt lacinia. Proin elementum ante velit, eu congue nulla semper quis. Pellentesque consequat vel nunc at scelerisque. Mauris sit amet venenatis diam, blandit viverra leo. Integer commodo laoreet orci.
Curabitur ipsum tortor, sodales ut augue sed, commodo porttitor libero. Pellentesque molestie vitae mi consectetur tempor. In sed lectus consequat, lobortis neque non, semper ipsum. Etiam eget ex et nibh sagittis pulvinar lacinia ac mauris. Aenean ligula eros, viverra ac nibh at, venenatis semper quam. Sed interdum ligula sit amet massa tincidunt tincidunt. Suspendisse potenti. Aliquam egestas facilisis est, sed faucibus erat scelerisque id. Duis dolor quam, viverra vitae orci euismod, laoreet pellentesque justo. Nunc malesuada non erat at ullamcorper. Mauris eget posuere odio. Vestibulum turpis nunc, pharetra eget ante in, feugiat mollis justo. Proin porttitor, diam nec vulputate pretium, tellus arcu rhoncus turpis, a blandit nisi nulla quis arcu. Nunc ac ullamcorper ligula, nec facilisis leo.
In vitae eros sollicitudin, iaculis ex eget, egestas orci. Etiam sed pretium lorem. Nam nisi enim, consectetur sit amet semper ac, semper pharetra diam. In pulvinar neque sapien, ac ullamcorper est lacinia a. Etiam tincidunt velit sed diam malesuada, eu ornare ex consectetur. Phasellus in imperdiet tellus. Sed bibendum, dui sit amet fringilla aliquet, enim odio sollicitudin lorem, vel semper turpis mauris vel mauris. Aenean congue magna ac massa cursus, in dictum orci commodo. Pellentesque mollis velit in sollicitudin tincidunt. Vestibulum et efficitur nulla.
Quisque posuere, velit sed porttitor dapibus, neque augue fringilla felis, eu luctus nisi nisl nec ipsum. Curabitur pellentesque ac lectus eget ultricies. Vestibulum est dolor, lacinia pharetra vulputate a, facilisis a magna. Nam vitae arcu nibh. Praesent finibus blandit ante, ac gravida ex mollis eget. Donec quam est, pulvinar vitae neque ut, bibendum aliquam erat. Nullam mollis arcu at sem tincidunt, in tristique lectus facilisis. Aenean ut lacus vel nisl finibus iaculis non a turpis. Integer eget ipsum ante. Donec nunc neque, vestibulum ac magna ac, posuere scelerisque dui. Pellentesque massa nibh, rhoncus id dolor quis, placerat posuere turpis. Donec aliquet augue nisi, eu finibus dui auctor et. Vestibulum eu varius lorem. Quisque lectus ante, malesuada pretium risus eget, interdum mattis enim.
`;
const SMALL_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";

View File

@@ -1,18 +1,18 @@
/* eslint-disable lit/no-template-arrow */
import "@material/mwc-button";
import { html, LitElement, TemplateResult } from "lit";
import { LitElement, TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators";
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
import "../../../../src/components/ha-form/ha-form";
import "../../components/demo-black-white-row";
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
import "../../../../src/components/ha-form/ha-form";
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../../src/types";
import "../../components/demo-black-white-row";
import { getEntity } from "../../../../src/fake_data/entity";
const ENTITIES = [
getEntity("alarm_control_panel", "alarm", "disarmed", {
@@ -147,9 +147,7 @@ const SCHEMAS: {
{ name: "target", selector: { target: {} } },
{ name: "number", selector: { number: { min: 0, max: 10 } } },
{ name: "boolean", selector: { boolean: {} } },
{ name: "time", required: true, selector: { time: {} } },
{ name: "datetime", required: true, selector: { datetime: {} } },
{ name: "date", required: true, selector: { date: {} } },
{ name: "time", selector: { time: {} } },
{ name: "action", selector: { action: {} } },
{ name: "text", selector: { text: { multiline: false } } },
{ name: "text_multiline", selector: { text: { multiline: true } } },

View File

@@ -1,20 +1,20 @@
/* eslint-disable lit/no-template-arrow */
import "@material/mwc-button";
import { css, html, LitElement, TemplateResult } from "lit";
import { LitElement, TemplateResult, css, html } from "lit";
import { customElement, state } from "lit/decorators";
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
import "../../../../src/components/ha-selector/ha-selector";
import "../../../../src/components/ha-settings-row";
import { BlueprintInput } from "../../../../src/data/blueprint";
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
import type { HomeAssistant } from "../../../../src/types";
import "../../components/demo-black-white-row";
import { BlueprintInput } from "../../../../src/data/blueprint";
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
import { getEntity } from "../../../../src/fake_data/entity";
import { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
const ENTITIES = [
getEntity("alarm_control_panel", "alarm", "disarmed", {
@@ -109,7 +109,7 @@ const AREAS = [
const SCHEMAS: {
name: string;
input: Record<string, (BlueprintInput & { required?: boolean }) | null>;
input: Record<string, BlueprintInput | null>;
}[] = [
{
name: "One of each",
@@ -166,11 +166,8 @@ const SCHEMAS: {
object: { name: "Object", selector: { object: {} } },
select_radio: {
name: "Select (Radio)",
selector: {
select: { options: ["Option 1", "Option 2"], mode: "list" },
},
selector: { select: { options: ["Option 1", "Option 2"] } },
},
template: { name: "Template", selector: { template: {} } },
select: {
name: "Select",
selector: {
@@ -186,22 +183,6 @@ const SCHEMAS: {
},
},
},
select_custom: {
name: "Select (Custom)",
selector: {
select: {
custom_value: true,
options: [
"Option 1",
"Option 2",
"Option 3",
"Option 4",
"Option 5",
"Option 6",
],
},
},
},
icon: { name: "Icon", selector: { icon: {} } },
media: { name: "Media", selector: { media: {} } },
location: { name: "Location", selector: { location: {} } },
@@ -221,35 +202,6 @@ const SCHEMAS: {
input: {
entity: { name: "Entity", selector: { entity: { multiple: true } } },
device: { name: "Device", selector: { device: { multiple: true } } },
area: { name: "Area", selector: { area: { multiple: true } } },
select: {
name: "Select Multiple",
selector: {
select: {
multiple: true,
custom_value: true,
options: [
"Option 1",
"Option 2",
"Option 3",
"Option 4",
"Option 5",
"Option 6",
],
},
},
},
select_checkbox: {
name: "Select Multiple (Checkbox)",
required: false,
selector: {
select: {
mode: "list",
multiple: true,
options: ["Option 1", "Option 2", "Option 3", "Option 4"],
},
},
},
},
},
];
@@ -258,14 +210,6 @@ const SCHEMAS: {
class DemoHaSelector extends LitElement implements ProvideHassElement {
@state() public hass!: HomeAssistant;
@state() private _disabled = false;
@state() private _required = false;
@state() private _helper = false;
@state() private _label = true;
private data = SCHEMAS.map(() => ({}));
constructor() {
@@ -399,36 +343,6 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
protected render(): TemplateResult {
return html`
<div class="options">
<ha-formfield label="Labels">
<ha-switch
.name=${"label"}
.checked=${this._label}
@change=${this._handleOptionChange}
></ha-switch>
</ha-formfield>
<ha-formfield label="Required">
<ha-switch
.name=${"required"}
.checked=${this._required}
@change=${this._handleOptionChange}
></ha-switch>
</ha-formfield>
<ha-formfield label="Disabled">
<ha-switch
.name=${"disabled"}
.checked=${this._disabled}
@change=${this._handleOptionChange}
></ha-switch>
</ha-formfield>
<ha-formfield label="Helper text">
<ha-switch
.name=${"helper"}
.checked=${this._helper}
@change=${this._handleOptionChange}
></ha-switch>
</ha-formfield>
</div>
${SCHEMAS.map((info, idx) => {
const data = this.data[idx];
const valueChanged = (ev) => {
@@ -451,12 +365,8 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
.hass=${this.hass}
.selector=${value!.selector}
.key=${key}
.label=${this._label ? value!.name : undefined}
.value=${data[key] ?? value!.default}
.disabled=${this._disabled}
.required=${this._required}
@value-changed=${valueChanged}
.helper=${this._helper ? "Helper text" : undefined}
></ha-selector>
</ha-settings-row>
`
@@ -468,21 +378,10 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
`;
}
private _handleOptionChange(ev) {
this[`_${ev.target.name}`] = ev.target.checked;
}
static styles = css`
ha-selector {
width: 60;
}
.options {
max-width: 800px;
margin: 16px auto;
}
.options ha-formfield {
margin-right: 16px;
}
`;
}

View File

@@ -1,3 +0,0 @@
---
title: Tips
---

View File

@@ -1,73 +0,0 @@
import { html, css, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../../src/components/ha-tip";
import "../../../../src/components/ha-card";
import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element";
const tips: (string | TemplateResult)[] = [
"Test tip",
"Bigger test tip, with some random text just to fill up as much space as possible without it looking like I'm really trying to to that",
html`<i>Tip</i> <b>with</b> <sub>HTML</sub>`,
];
@customElement("demo-components-ha-tip")
export class DemoHaTip extends LitElement {
protected render(): TemplateResult {
return html` ${["light", "dark"].map(
(mode) => html`
<div class=${mode}>
<ha-card header="ha-tip ${mode} demo">
<div class="card-content">
${tips.map((tip) => html`<ha-tip>${tip}</ha-tip>`)}
</div>
</ha-card>
</div>
`
)}`;
}
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
applyThemesOnElement(
this.shadowRoot!.querySelector(".dark"),
{
default_theme: "default",
default_dark_theme: "default",
themes: {},
darkMode: true,
theme: "default",
},
undefined,
undefined,
true
);
}
static get styles() {
return css`
:host {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.dark,
.light {
display: block;
background-color: var(--primary-background-color);
padding: 0 50px;
}
ha-tip {
margin-bottom: 14px;
}
ha-card {
margin: 24px auto;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-components-ha-tip": DemoHaTip;
}
}

View File

@@ -249,7 +249,7 @@ const CONFIGS = [
name: Bed light
action_name: Toggle light
service: light.toggle
data:
service_data:
entity_id: light.bed_light
- type: section
label: Links

View File

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

View File

@@ -9,7 +9,7 @@ const CONFIGS = [
heading: "markdown-it demo",
config: `
- type: markdown
content: >-
content: >
# h1 Heading 8-)
## h2 Heading
@@ -249,17 +249,6 @@ const CONFIGS = [
::: warning
*here be dragons*
:::
### ha-alert
You can use our [\`ha-alert\`](https://design.home-assistant.io/#components/ha-alert) component in markdown content rendered in the Home Assistant Frontend.
<ha-alert alert-type="error">This is an error alert — check it out!</ha-alert>
<ha-alert alert-type="warning">This is a warning alert — check it out!</ha-alert>
<ha-alert alert-type="info">This is an info alert — check it out!</ha-alert>
<ha-alert alert-type="success">This is a success alert — check it out!</ha-alert>
<ha-alert title="Test alert">This is an alert with a title</ha-alert>
`,
},
];

View File

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

View File

@@ -5,7 +5,6 @@ import {
UPDATE_SUPPORT_BACKUP,
UPDATE_SUPPORT_PROGRESS,
UPDATE_SUPPORT_INSTALL,
UPDATE_SUPPORT_RELEASE_NOTES,
} from "../../../../src/data/update";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
@@ -14,11 +13,10 @@ import {
provideHass,
} from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
import { LONG_TEXT } from "../../data/text";
const base_attributes = {
title: "Awesome",
installed_version: "1.2.2",
current_version: "1.2.2",
latest_version: "1.2.3",
release_url: "https://home-assistant.io",
supported_features: UPDATE_SUPPORT_INSTALL,
@@ -50,7 +48,7 @@ const ENTITIES = [
}),
getEntity("update", "update5", "off", {
...base_attributes,
installed_version: "1.2.3",
current_version: "1.2.3",
friendly_name: "No update",
}),
getEntity("update", "update6", "off", {
@@ -102,43 +100,14 @@ const ENTITIES = [
}),
getEntity("update", "update14", "off", {
...base_attributes,
installed_version: null,
friendly_name: "Update without installed_version",
current_version: null,
friendly_name: "Update without current_version",
}),
getEntity("update", "update15", "off", {
...base_attributes,
latest_version: null,
friendly_name: "Update without latest_version",
}),
getEntity("update", "update16", "off", {
...base_attributes,
friendly_name: "Update with release notes",
supported_features:
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
}),
getEntity("update", "update17", "off", {
...base_attributes,
friendly_name: "Update with release notes error",
supported_features:
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
}),
getEntity("update", "update18", "off", {
...base_attributes,
friendly_name: "Update with release notes loading",
supported_features:
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
}),
getEntity("update", "update19", "on", {
...base_attributes,
friendly_name: "Update with auto update",
auto_update: true,
}),
getEntity("update", "update20", "on", {
...base_attributes,
in_progress: true,
title: undefined,
friendly_name: "Installing without title",
}),
];
@customElement("demo-more-info-update")
@@ -161,24 +130,6 @@ class DemoMoreInfoUpdate extends LitElement {
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.addEntities(ENTITIES);
hass.mockWS(
"update/release_notes",
(msg: { type: string; entity_id: string }) => {
if (msg.entity_id === "update.update16") {
return LONG_TEXT;
}
if (msg.entity_id === "update.update17") {
return Promise.reject({
code: "error",
message: "Could not fetch release notes",
});
}
if (msg.entity_id === "update.update18") {
return undefined;
}
return null;
}
);
}
}

View File

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

View File

@@ -14,15 +14,15 @@ import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event";
import { navigate } from "../../../src/common/navigate";
import "../../../src/components/search-input";
import { extractSearchParam } from "../../../src/common/url/search-params";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-icon-button";
import "../../../src/components/search-input";
import {
HassioAddonInfo,
HassioAddonRepository,
reloadHassioAddons,
} from "../../../src/data/hassio/addon";
import { StoreAddon } from "../../../src/data/supervisor/store";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-subpage";
@@ -66,10 +66,10 @@ class HassioAddonStore extends LitElement {
protected render(): TemplateResult {
let repos: TemplateResult[] = [];
if (this.supervisor.store.repositories) {
if (this.supervisor.addon.repositories) {
repos = this.addonRepositories(
this.supervisor.store.repositories,
this.supervisor.store.addons,
this.supervisor.addon.repositories,
this.supervisor.addon.addons,
this._filter
);
}
@@ -145,7 +145,7 @@ class HassioAddonStore extends LitElement {
private addonRepositories = memoizeOne(
(
repositories: HassioAddonRepository[],
addons: StoreAddon[],
addons: HassioAddonInfo[],
filter?: string
) =>
repositories.sort(sortRepos).map((repo) => {

View File

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

View File

@@ -39,14 +39,7 @@ import type { HomeAssistant } from "../../../../src/types";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
import { hassioStyle } from "../../resources/hassio-style";
const SUPPORTED_UI_TYPES = [
"string",
"select",
"boolean",
"integer",
"float",
"schema",
];
const SUPPORTED_UI_TYPES = ["string", "select", "boolean", "integer", "float"];
const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([
new Type("!secret", {
@@ -55,8 +48,6 @@ const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([
}),
]);
const MASKED_FIELDS = ["password", "secret", "token"];
@customElement("hassio-addon-config")
class HassioAddonConfig extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails;
@@ -84,66 +75,19 @@ class HassioAddonConfig extends LitElement {
public computeLabel = (entry: HaFormSchema): string =>
this.addon.translations[this.hass.language]?.configuration?.[entry.name]
?.name ||
this.addon.translations.en?.configuration?.[entry.name]?.name ||
this.addon.translations.en?.configuration?.[entry.name].name ||
entry.name;
public computeHelper = (entry: HaFormSchema): string =>
this.addon.translations[this.hass.language]?.configuration?.[entry.name]
?.description ||
this.addon.translations.en?.configuration?.[entry.name]?.description ||
"";
private _convertSchema = memoizeOne(
// Convert supervisor schema to selectors
(schema: Record<string, any>): HaFormSchema[] =>
schema.map((entry) =>
entry.type === "select"
? {
name: entry.name,
required: entry.required,
selector: { select: { options: entry.options } },
}
: entry.type === "string"
? entry.multiple
? {
name: entry.name,
required: entry.required,
selector: {
select: { options: [], multiple: true, custom_value: true },
},
}
: {
name: entry.name,
required: entry.required,
selector: {
text: {
type:
entry.format || MASKED_FIELDS.includes(entry.name)
? "password"
: "text",
},
},
}
: entry.type === "boolean"
? {
name: entry.name,
required: entry.required,
selector: { boolean: {} },
}
: entry.type === "schema"
? {
name: entry.name,
required: entry.required,
selector: { object: {} },
}
: entry.type === "float" || entry.type === "integer"
? {
name: entry.name,
required: entry.required,
selector: { number: { mode: "box" } },
}
: entry
)
private _schema = memoizeOne((schema: HaFormSchema[]): HaFormSchema[] =>
// @ts-expect-error supervisor does not implement [string, string] for select.options[]
schema.map((entry) =>
entry.type === "select"
? {
...entry,
options: entry.options.map((option) => [option, option]),
}
: entry
)
);
private _filteredShchema = memoizeOne(
@@ -162,7 +106,7 @@ class HassioAddonConfig extends LitElement {
);
return html`
<h1>${this.addon.name}</h1>
<ha-card outlined>
<ha-card>
<div class="header">
<h2>
${this.supervisor.localize("addon.configuration.options.header")}
@@ -196,8 +140,7 @@ class HassioAddonConfig extends LitElement {
.data=${this._options!}
@value-changed=${this._configChanged}
.computeLabel=${this.computeLabel}
.computeHelper=${this.computeHelper}
.schema=${this._convertSchema(
.schema=${this._schema(
this._showOptional
? this.addon.schema!
: this._filteredShchema(
@@ -254,9 +197,8 @@ class HassioAddonConfig extends LitElement {
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this._canShowSchema = !this.addon.schema!.find(
(entry) =>
// @ts-ignore
!SUPPORTED_UI_TYPES.includes(entry.type)
// @ts-ignore
(entry) => !SUPPORTED_UI_TYPES.includes(entry.type) || entry.multiple
);
this._yamlMode = !this._canShowSchema;
}

View File

@@ -1,3 +1,4 @@
import { PaperInputElement } from "@polymer/paper-input/paper-input";
import {
css,
CSSResultGroup,
@@ -7,13 +8,10 @@ import {
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-form/ha-form";
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
import {
HassioAddonDetails,
HassioAddonSetOptionParams,
@@ -26,6 +24,16 @@ import { HomeAssistant } from "../../../../src/types";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
import { hassioStyle } from "../../resources/hassio-style";
interface NetworkItem {
description: string;
container: string;
host: number | null;
}
interface NetworkItemInput extends PaperInputElement {
container: string;
}
@customElement("hassio-addon-network")
class HassioAddonNetwork extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -34,13 +42,9 @@ class HassioAddonNetwork extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails;
@state() private _showOptional = false;
@state() private _configHasChanged = false;
@state() private _error?: string;
@state() private _config?: Record<string, any>;
@state() private _config?: NetworkItem[];
public connectedCallback(): void {
super.connectedCallback();
@@ -52,61 +56,59 @@ class HassioAddonNetwork extends LitElement {
return html``;
}
const hasHiddenOptions = Object.keys(this._config).find(
(entry) => this._config![entry] === null
);
return html`
<ha-card
outlined
.header=${this.supervisor.localize(
"addon.configuration.network.header"
)}
>
<div class="card-content">
<p>
${this.supervisor.localize(
"addon.configuration.network.introduction"
)}
</p>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<ha-form
.data=${this._config}
@value-changed=${this._configChanged}
.computeLabel=${this._computeLabel}
.computeHelper=${this._computeHelper}
.schema=${this._createSchema(
this._config,
this._showOptional,
this.hass.userData?.showAdvanced || false
)}
></ha-form>
</div>
${hasHiddenOptions
? html`<ha-formfield
class="show-optional"
.label=${this.supervisor.localize(
"addon.configuration.network.show_disabled"
<table>
<tbody>
<tr>
<th>
${this.supervisor.localize(
"addon.configuration.network.container"
)}
</th>
<th>
${this.supervisor.localize(
"addon.configuration.network.host"
)}
</th>
<th>${this.supervisor.localize("common.description")}</th>
</tr>
${this._config!.map(
(item) => html`
<tr>
<td>${item.container}</td>
<td>
<paper-input
@value-changed=${this._configChanged}
placeholder=${this.supervisor.localize(
"addon.configuration.network.disabled"
)}
.value=${item.host ? String(item.host) : ""}
.container=${item.container}
no-label-float
></paper-input>
</td>
<td>${this._computeDescription(item)}</td>
</tr>
`
)}
>
<ha-switch
@change=${this._toggleOptional}
.checked=${this._showOptional}
>
</ha-switch>
</ha-formfield>`
: ""}
</tbody>
</table>
</div>
<div class="card-actions">
<ha-progress-button class="warning" @click=${this._resetTapped}>
${this.supervisor.localize("common.reset_defaults")}
</ha-progress-button>
<ha-progress-button
@click=${this._saveTapped}
.disabled=${!this._configHasChanged}
>
<ha-progress-button @click=${this._saveTapped}>
${this.supervisor.localize("common.save")}
</ha-progress-button>
</div>
@@ -121,60 +123,50 @@ class HassioAddonNetwork extends LitElement {
}
}
private _createSchema = memoizeOne(
(
config: Record<string, number>,
showOptional: boolean,
advanced: boolean
): HaFormSchema[] =>
(showOptional
? Object.keys(config)
: Object.keys(config).filter((entry) => config[entry] !== null)
).map((entry) => ({
name: entry,
selector: {
number: {
mode: "box",
min: 0,
max: 65535,
unit_of_measurement: advanced ? entry : undefined,
},
},
}))
);
private _computeLabel = (_: HaFormSchema): string => "";
private _computeHelper = (item: HaFormSchema): string =>
this.addon.translations[this.hass.language]?.network?.[item.name] ||
this.addon.translations.en?.network?.[item.name] ||
this.addon.network_description?.[item.name] ||
item.name;
private _computeDescription = (item: NetworkItem): string =>
this.addon.translations[this.hass.language]?.network?.[item.container]
?.description ||
this.addon.translations.en?.network?.[item.container]?.description ||
item.description;
private _setNetworkConfig(): void {
this._config = this.addon.network || {};
const network = this.addon.network || {};
const description = this.addon.network_description || {};
const items: NetworkItem[] = Object.keys(network).map((key) => ({
container: key,
host: network[key],
description: description[key],
}));
this._config = items.sort((a, b) => (a.container > b.container ? 1 : -1));
}
private async _configChanged(ev: CustomEvent): Promise<void> {
this._configHasChanged = true;
this._config! = ev.detail.value;
private async _configChanged(ev: Event): Promise<void> {
const target = ev.target as NetworkItemInput;
this._config!.forEach((item) => {
if (
item.container === target.container &&
item.host !== parseInt(String(target.value), 10)
) {
item.host = target.value ? parseInt(String(target.value), 10) : null;
}
});
}
private async _resetTapped(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const data: HassioAddonSetOptionParams = {
network: null,
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
this._configHasChanged = false;
const eventdata = {
success: true,
response: undefined,
path: "option",
};
button.actionSuccess();
fireEvent(this, "hass-api-called", eventdata);
if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
@@ -185,21 +177,19 @@ class HassioAddonNetwork extends LitElement {
"error",
extractApiErrorMessage(err)
);
button.actionError();
}
}
private _toggleOptional() {
this._showOptional = !this._showOptional;
button.progress = false;
}
private async _saveTapped(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
this._error = undefined;
const networkconfiguration = {};
Object.entries(this._config!).forEach(([key, value]) => {
networkconfiguration[key] = value ?? null;
this._config!.forEach((item) => {
networkconfiguration[item.container] = parseInt(String(item.host), 10);
});
const data: HassioAddonSetOptionParams = {
@@ -208,13 +198,11 @@ class HassioAddonNetwork extends LitElement {
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
this._configHasChanged = false;
const eventdata = {
success: true,
response: undefined,
path: "option",
};
button.actionSuccess();
fireEvent(this, "hass-api-called", eventdata);
if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
@@ -225,8 +213,8 @@ class HassioAddonNetwork extends LitElement {
"error",
extractApiErrorMessage(err)
);
button.actionError();
}
button.progress = false;
}
static get styles(): CSSResultGroup {
@@ -244,9 +232,6 @@ class HassioAddonNetwork extends LitElement {
display: flex;
justify-content: space-between;
}
.show-optional {
padding: 16px;
}
`,
];
}

View File

@@ -38,7 +38,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
}
return html`
<div class="content">
<ha-card outlined>
<ha-card>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}

View File

@@ -12,19 +12,12 @@ import { navigate } from "../../../src/common/navigate";
import { extractSearchParam } from "../../../src/common/url/search-params";
import "../../../src/components/ha-circular-progress";
import {
fetchAddonInfo,
fetchHassioAddonInfo,
fetchHassioAddonsInfo,
HassioAddonDetails,
} from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import {
addStoreRepository,
fetchSupervisorStore,
StoreAddonDetails,
} from "../../../src/data/supervisor/store";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
import "../../../src/layouts/hass-error-screen";
import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-tabs-subpage";
@@ -47,9 +40,7 @@ class HassioAddonDashboard extends LitElement {
@property({ attribute: false }) public route!: Route;
@property({ attribute: false }) public addon?:
| HassioAddonDetails
| StoreAddonDetails;
@property({ attribute: false }) public addon?: HassioAddonDetails;
@property({ type: Boolean }) public narrow!: boolean;
@@ -175,39 +166,6 @@ class HassioAddonDashboard extends LitElement {
protected async firstUpdated(): Promise<void> {
if (this.route.path === "") {
const requestedAddon = extractSearchParam("addon");
const requestedAddonRepository = extractSearchParam("repository_url");
if (requestedAddonRepository) {
const storeInfo = await fetchSupervisorStore(this.hass);
if (
!storeInfo.repositories.find(
(repo) => repo.source === requestedAddonRepository
)
) {
if (
!(await showConfirmationDialog(this, {
title: this.supervisor.localize("my.add_addon_repository_title"),
text: this.supervisor.localize(
"my.add_addon_repository_description",
{ addon: requestedAddon, repository: requestedAddonRepository }
),
confirmText: this.supervisor.localize("common.add"),
dismissText: this.supervisor.localize("common.cancel"),
}))
) {
this._error = this.supervisor.localize(
"my.error_repository_not_found"
);
return;
}
try {
await addStoreRepository(this.hass, requestedAddonRepository);
} catch (err: any) {
this._error = extractApiErrorMessage(err);
}
}
}
if (requestedAddon) {
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
const validAddon = addonsInfo.addons.some(
@@ -244,8 +202,6 @@ class HassioAddonDashboard extends LitElement {
if (path === "uninstall") {
window.history.back();
} else if (path === "install") {
this.addon = await fetchHassioAddonInfo(this.hass, this.addon!.slug);
} else {
await this._routeDataChanged();
}
@@ -263,7 +219,8 @@ class HassioAddonDashboard extends LitElement {
return;
}
try {
this.addon = await fetchAddonInfo(this.hass, this.supervisor, addon);
const addoninfo = await fetchHassioAddonInfo(this.hass, addon);
this.addon = addoninfo;
} catch (err: any) {
this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`;
this.addon = undefined;

View File

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

View File

@@ -59,10 +59,7 @@ import {
fetchHassioStats,
HassioStats,
} from "../../../../src/data/hassio/common";
import {
StoreAddon,
StoreAddonDetails,
} from "../../../../src/data/supervisor/store";
import { StoreAddon } from "../../../../src/data/supervisor/store";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import {
showAlertDialog,
@@ -103,9 +100,7 @@ class HassioAddonInfo extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public addon!:
| HassioAddonDetails
| StoreAddonDetails;
@property({ attribute: false }) public addon!: HassioAddonDetails;
@property({ attribute: false }) public supervisor!: Supervisor;
@@ -148,7 +143,7 @@ class HassioAddonInfo extends LitElement {
></update-available-card>
`
: ""}
${"protected" in this.addon && !this.addon.protected
${!this.addon.protected
? html`
<ha-alert
alert-type="error"
@@ -171,7 +166,7 @@ class HassioAddonInfo extends LitElement {
`
: ""}
<ha-card outlined>
<ha-card>
<div class="card-content">
<div class="addon-header">
${!this.narrow ? this.addon.name : ""}
@@ -523,7 +518,7 @@ class HassioAddonInfo extends LitElement {
: ""}
</div>
<div>
${this.addon.version && this.addon.state === "started"
${this.addon.state === "started"
? html`<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
${this.supervisor.localize("addon.dashboard.hostname")}
@@ -654,7 +649,7 @@ class HassioAddonInfo extends LitElement {
${this.addon.long_description
? html`
<ha-card outlined>
<ha-card>
<div class="card-content">
<ha-markdown
.content=${this.addon.long_description}
@@ -674,7 +669,7 @@ class HassioAddonInfo extends LitElement {
}
private async _loadData(): Promise<void> {
if ("state" in this.addon && this.addon.state === "started") {
if (this.addon.state === "started") {
this._metrics = await fetchHassioStats(
this.hass,
`addons/${this.addon.slug}`
@@ -722,22 +717,18 @@ class HassioAddonInfo extends LitElement {
}
private get _computeIsRunning(): boolean {
return (this.addon as HassioAddonDetails)?.state === "started";
return this.addon?.state === "started";
}
private get _pathWebui(): string | null {
return (this.addon as HassioAddonDetails).webui!.replace(
"[HOST]",
document.location.hostname
return (
this.addon.webui &&
this.addon.webui.replace("[HOST]", document.location.hostname)
);
}
private get _computeShowWebUI(): boolean | "" | null {
return (
!this.addon.ingress &&
(this.addon as HassioAddonDetails).webui &&
this._computeIsRunning
);
return !this.addon.ingress && this.addon.webui && this._computeIsRunning;
}
private _openIngress(): void {
@@ -763,8 +754,7 @@ class HassioAddonInfo extends LitElement {
private async _startOnBootToggled(): Promise<void> {
this._error = undefined;
const data: HassioAddonSetOptionParams = {
boot:
(this.addon as HassioAddonDetails).boot === "auto" ? "manual" : "auto",
boot: this.addon.boot === "auto" ? "manual" : "auto",
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
@@ -786,7 +776,7 @@ class HassioAddonInfo extends LitElement {
private async _watchdogToggled(): Promise<void> {
this._error = undefined;
const data: HassioAddonSetOptionParams = {
watchdog: !(this.addon as HassioAddonDetails).watchdog,
watchdog: !this.addon.watchdog,
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
@@ -808,7 +798,7 @@ class HassioAddonInfo extends LitElement {
private async _autoUpdateToggled(): Promise<void> {
this._error = undefined;
const data: HassioAddonSetOptionParams = {
auto_update: !(this.addon as HassioAddonDetails).auto_update,
auto_update: !this.addon.auto_update,
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
@@ -830,7 +820,7 @@ class HassioAddonInfo extends LitElement {
private async _protectionToggled(): Promise<void> {
this._error = undefined;
const data: HassioAddonSetSecurityParams = {
protected: !(this.addon as HassioAddonDetails).protected,
protected: !this.addon.protected,
};
try {
await setHassioAddonSecurity(this.hass, this.addon.slug, data);
@@ -852,7 +842,7 @@ class HassioAddonInfo extends LitElement {
private async _panelToggled(): Promise<void> {
this._error = undefined;
const data: HassioAddonSetOptionParams = {
ingress_panel: !(this.addon as HassioAddonDetails).ingress_panel,
ingress_panel: !this.addon.ingress_panel,
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
@@ -880,7 +870,7 @@ class HassioAddonInfo extends LitElement {
showHassioMarkdownDialog(this, {
title: this.supervisor.localize("addon.dashboard.changelog"),
content: extractChangelog(this.addon as HassioAddonDetails, content),
content: extractChangelog(this.addon, content),
});
} catch (err: any) {
showAlertDialog(this, {

View File

@@ -2,7 +2,6 @@ import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-ansi-to-html";
import "../../../../src/components/ha-card";
import {
fetchHassioAddonLogs,
@@ -12,6 +11,7 @@ import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import "../../components/hassio-ansi-to-html";
import { hassioStyle } from "../../resources/hassio-style";
@customElement("hassio-addon-logs")
@@ -34,15 +34,15 @@ class HassioAddonLogs extends LitElement {
protected render(): TemplateResult {
return html`
<h1>${this.addon.name}</h1>
<ha-card outlined>
<ha-card>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<div class="card-content">
${this._content
? html`<ha-ansi-to-html
? html`<hassio-ansi-to-html
.content=${this._content}
></ha-ansi-to-html>`
></hassio-ansi-to-html>`
: ""}
</div>
<div class="card-actions">

View File

@@ -1,7 +1,7 @@
import "@material/mwc-button";
import { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { mdiBackupRestore, mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js";
import { mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js";
import {
css,
CSSResultGroup,
@@ -98,8 +98,9 @@ export class HassioBackups extends LitElement {
if (backup.content.addons.length !== 0) {
for (const addon of backup.content.addons) {
content.push(
this.supervisor.addon.addons.find((entry) => entry.slug === addon)
?.name || addon
this.supervisor.supervisor.addons.find(
(entry) => entry.slug === addon
)?.name || addon
);
}
}
@@ -165,15 +166,7 @@ export class HassioBackups extends LitElement {
}
return html`
<hass-tabs-subpage-data-table
.tabs=${atLeastVersion(this.hass.config.version, 2022, 5)
? [
{
translationKey: "panel.backups",
path: `/hassio/backups`,
iconPath: mdiBackupRestore,
},
]
: supervisorTabs(this.hass)}
.tabs=${supervisorTabs(this.hass)}
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.searchLabel=${this.supervisor.localize("search")}
@@ -189,9 +182,7 @@ export class HassioBackups extends LitElement {
selectable
hasFab
.mainPage=${!atLeastVersion(this.hass.config.version, 2021, 12)}
back-path=${atLeastVersion(this.hass.config.version, 2022, 5)
? "/config/system"
: "/config"}
back-path="/config"
supervisor
>
<ha-button-menu

View File

@@ -10,8 +10,8 @@ interface State {
backgroundColor: null | string;
}
@customElement("ha-ansi-to-html")
class HaAnsiToHtml extends LitElement {
@customElement("hassio-ansi-to-html")
class HassioAnsiToHtml extends LitElement {
@property() public content!: string;
protected render(): TemplateResult | void {
@@ -241,6 +241,6 @@ class HaAnsiToHtml extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"ha-ansi-to-html": HaAnsiToHtml;
"hassio-ansi-to-html": HassioAnsiToHtml;
}
}

View File

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

View File

@@ -32,6 +32,13 @@ interface AddonCheckboxItem extends CheckboxItem {
const _computeFolders = (folders): CheckboxItem[] => {
const list: CheckboxItem[] = [];
if (folders.includes("homeassistant")) {
list.push({
slug: "homeassistant",
name: "Home Assistant configuration",
checked: false,
});
}
if (folders.includes("ssl")) {
list.push({ slug: "ssl", name: "SSL", checked: false });
}
@@ -93,10 +100,10 @@ export class SupervisorBackupContent extends LitElement {
this.folders = _computeFolders(
this.backup
? this.backup.folders
: ["ssl", "share", "media", "addons/local"]
: ["homeassistant", "ssl", "share", "media", "addons/local"]
);
this.addons = _computeAddons(
this.backup ? this.backup.addons : this.supervisor?.addon.addons
this.backup ? this.backup.addons : this.supervisor?.supervisor.addons
);
this.backupType = this.backup?.type || "full";
this.backupName = this.backup?.name || "";
@@ -180,7 +187,7 @@ export class SupervisorBackupContent extends LitElement {
>
<ha-checkbox
.checked=${this.homeAssistant}
@change=${this.toggleHomeAssistant}
@click=${this.toggleHomeAssistant}
>
</ha-checkbox>
</ha-formfield>

View File

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

View File

@@ -10,7 +10,6 @@ import { HomeAssistant, Route } from "../../../src/types";
import { supervisorTabs } from "../hassio-tabs";
import "./hassio-addons";
import "./hassio-update";
import "../../../src/layouts/hass-subpage";
@customElement("hassio-dashboard")
class HassioDashboard extends LitElement {
@@ -23,31 +22,6 @@ class HassioDashboard extends LitElement {
@property({ attribute: false }) public route!: Route;
protected render(): TemplateResult {
if (atLeastVersion(this.hass.config.version, 2022, 5)) {
return html`<hass-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
.header=${this.supervisor.localize("panel.addons")}
>
<hassio-addons
.hass=${this.hass}
.supervisor=${this.supervisor}
></hassio-addons>
<a href="/hassio/store">
<ha-fab
.label=${this.supervisor.localize("panel.store")}
extended
class="non-tabs"
>
<ha-svg-icon
slot="icon"
.path=${mdiStorePlus}
></ha-svg-icon> </ha-fab
></a>
</hass-subpage>`;
}
return html`
<hass-tabs-subpage
.hass=${this.hass}
@@ -100,12 +74,6 @@ class HassioDashboard extends LitElement {
.content {
margin: 0 auto;
}
ha-fab.non-tabs {
position: fixed;
right: calc(16px + env(safe-area-inset-right));
bottom: calc(16px + env(safe-area-inset-bottom));
z-index: 1;
}
`,
];
}

View File

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

View File

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

View File

@@ -3,8 +3,8 @@ import { customElement, property } from "lit/decorators";
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 { mainWindow } from "../../src/common/dom/get_main_window";
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
import { mainWindow } from "../../src/common/dom/get_main_window";
import { navigate } from "../../src/common/navigate";
import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
import { Supervisor } from "../../src/data/supervisor/supervisor";
@@ -73,18 +73,6 @@ export class HassioMain extends SupervisorBaseElement {
});
});
// Forward keydown events to the main window for quickbar access
document.body.addEventListener("keydown", (ev: KeyboardEvent) => {
if (ev.altKey || ev.ctrlKey || ev.shiftKey || ev.metaKey) {
// Ignore if modifier keys are pressed
return;
}
// @ts-ignore
fireEvent(mainWindow, "hass-quick-bar-trigger", ev, {
bubbles: false,
});
});
makeDialogManager(this, this.shadowRoot!);
}

View File

@@ -15,7 +15,7 @@ import {
} from "../../src/panels/my/ha-panel-my";
import { HomeAssistant, Route } from "../../src/types";
export const REDIRECTS: Redirects = {
const REDIRECTS: Redirects = {
supervisor: {
redirect: "/hassio/dashboard",
},
@@ -42,9 +42,6 @@ export const REDIRECTS: Redirects = {
params: {
addon: "string",
},
optional_params: {
repository_url: "url",
},
},
supervisor_ingress: {
redirect: "/hassio/ingress",
@@ -127,14 +124,6 @@ class HassioMyRedirect extends LitElement {
}
resultParams[key] = params[key];
});
Object.entries(redirect.optional_params || {}).forEach(([key, type]) => {
if (params[key]) {
if (!this._checkParamType(type, params[key])) {
throw Error();
}
resultParams[key] = params[key];
}
});
return `?${createSearchParam(resultParams)}`;
}

View File

@@ -8,27 +8,24 @@ import { atLeastVersion } from "../../src/common/config/version";
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
import { HomeAssistant } from "../../src/types";
export const supervisorTabs = (hass: HomeAssistant): PageNavigation[] =>
atLeastVersion(hass.config.version, 2022, 5)
? []
: [
{
translationKey: atLeastVersion(hass.config.version, 2021, 12)
? "panel.addons"
: "panel.dashboard",
path: `/hassio/dashboard`,
iconPath: atLeastVersion(hass.config.version, 2021, 12)
? mdiPuzzle
: mdiViewDashboard,
},
{
translationKey: "panel.backups",
path: `/hassio/backups`,
iconPath: mdiBackupRestore,
},
{
translationKey: "panel.system",
path: `/hassio/system`,
iconPath: mdiCogs,
},
];
export const supervisorTabs = (hass: HomeAssistant): PageNavigation[] => [
{
translationKey: atLeastVersion(hass.config.version, 2021, 12)
? "panel.addons"
: "panel.dashboard",
path: `/hassio/dashboard`,
iconPath: atLeastVersion(hass.config.version, 2021, 12)
? mdiPuzzle
: mdiViewDashboard,
},
{
translationKey: "panel.backups",
path: `/hassio/backups`,
iconPath: mdiBackupRestore,
},
{
translationKey: "panel.system",
path: `/hassio/system`,
iconPath: mdiCogs,
},
];

View File

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

View File

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

View File

@@ -23,10 +23,6 @@ import {
showAlertDialog,
showConfirmationDialog,
} from "../../../src/dialogs/generic/show-dialog-box";
import {
UNHEALTHY_REASON_URL,
UNSUPPORTED_REASON_URL,
} from "../../../src/panels/config/system-health/ha-config-system-health";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { bytesToString } from "../../../src/util/bytes-to-string";
@@ -34,6 +30,11 @@ import { documentationUrl } from "../../../src/util/documentation-url";
import "../components/supervisor-metric";
import { hassioStyle } from "../resources/hassio-style";
const UNSUPPORTED_REASON_URL = {};
const UNHEALTHY_REASON_URL = {
privileged: "/more-info/unsupported/privileged",
};
@customElement("hassio-supervisor-info")
class HassioSupervisorInfo extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -57,7 +58,7 @@ class HassioSupervisorInfo extends LitElement {
},
];
return html`
<ha-card header="Supervisor" outlined>
<ha-card header="Supervisor">
<div class="card-content">
<div>
<ha-settings-row>

View File

@@ -1,4 +1,3 @@
import "../../../src/components/ha-ansi-to-html";
import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
@@ -12,6 +11,7 @@ import { Supervisor } from "../../../src/data/supervisor/supervisor";
import "../../../src/layouts/hass-loading-screen";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import "../components/hassio-ansi-to-html";
import { hassioStyle } from "../resources/hassio-style";
interface LogProvider {
@@ -65,7 +65,7 @@ class HassioSupervisorLog extends LitElement {
protected render(): TemplateResult | void {
return html`
<ha-card outlined>
<ha-card>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
@@ -89,8 +89,8 @@ class HassioSupervisorLog extends LitElement {
<div class="card-content" id="content">
${this._content
? html`<ha-ansi-to-html .content=${this._content}>
</ha-ansi-to-html>`
? html`<hassio-ansi-to-html .content=${this._content}>
</hassio-ansi-to-html>`
: html`<hass-loading-screen no-toolbar></hass-loading-screen>`}
</div>
<div class="card-actions">

View File

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

View File

@@ -72,8 +72,8 @@
"@material/mwc-textfield": "0.25.3",
"@material/mwc-top-app-bar-fixed": "^0.25.3",
"@material/top-app-bar": "14.0.0-canary.261f2db59.0",
"@mdi/js": "6.9.96",
"@mdi/svg": "6.9.96",
"@mdi/js": "6.6.95",
"@mdi/svg": "6.6.95",
"@polymer/app-layout": "^3.1.0",
"@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1",
@@ -89,8 +89,8 @@
"@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.4.1",
"@thomasloven/round-slider": "0.5.4",
"@vaadin/combo-box": "^23.0.10",
"@vaadin/vaadin-themable-mixin": "^23.0.10",
"@vaadin/combo-box": "^22.0.4",
"@vaadin/vaadin-themable-mixin": "^22.0.4",
"@vibrant/color": "^3.2.1-alpha.1",
"@vibrant/core": "^3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
@@ -108,7 +108,7 @@
"fuse.js": "^6.0.0",
"google-timezones-json": "^1.0.2",
"hls.js": "^1.1.5",
"home-assistant-js-websocket": "^7.1.0",
"home-assistant-js-websocket": "^7.0.1",
"idb-keyval": "^5.1.3",
"intl-messageformat": "^9.9.1",
"js-yaml": "^4.1.0",

View File

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

View File

@@ -15,7 +15,7 @@ if [ -z $(which hass) ]; then
echo "Installing Home Asstant core from dev."
python3 -m pip install --upgrade \
colorlog \
git+https://github.com/home-assistant/home-assistant.git@dev
git+git://github.com/home-assistant/home-assistant.git@dev
fi
if [ ! -d "${WD}/config" ]; then

View File

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

View File

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

View File

@@ -1,16 +0,0 @@
import secondsToDuration from "./seconds_to_duration";
const DAY_IN_SECONDS = 86400;
const HOUR_IN_SECONDS = 3600;
const MINUTE_IN_SECONDS = 60;
export const UNIT_TO_SECOND_CONVERT = {
s: 1,
min: MINUTE_IN_SECONDS,
h: HOUR_IN_SECONDS,
d: DAY_IN_SECONDS,
};
export const formatDuration = (duration: string, units: string): string =>
secondsToDuration(parseFloat(duration) * UNIT_TO_SECOND_CONVERT[units]) ||
"0";

View File

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

View File

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

View File

@@ -12,7 +12,7 @@ export const isNavigationClick = (e: MouseEvent) => {
const anchor = e
.composedPath()
.find((n) => (n as HTMLElement).tagName === "A") as
.filter((n) => (n as HTMLElement).tagName === "A")[0] as
| HTMLAnchorElement
| undefined;
if (

View File

@@ -29,11 +29,8 @@ import {
mdiPowerPlug,
mdiPowerPlugOff,
mdiRadioboxBlank,
mdiSmoke,
mdiSnowflake,
mdiSmokeDetector,
mdiSmokeDetectorAlert,
mdiSmokeDetectorVariant,
mdiSmokeDetectorVariantAlert,
mdiSquare,
mdiSquareOutline,
mdiStop,
@@ -55,8 +52,6 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
return is_off ? mdiBattery : mdiBatteryOutline;
case "battery_charging":
return is_off ? mdiBattery : mdiBatteryCharging;
case "carbon_monoxide":
return is_off ? mdiSmokeDetector : mdiSmokeDetectorAlert;
case "cold":
return is_off ? mdiThermometer : mdiSnowflake;
case "connectivity":
@@ -73,7 +68,7 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
case "tamper":
return is_off ? mdiCheckCircle : mdiAlertCircle;
case "smoke":
return is_off ? mdiSmokeDetectorVariant : mdiSmokeDetectorVariantAlert;
return is_off ? mdiCheckCircle : mdiSmoke;
case "heat":
return is_off ? mdiThermometer : mdiFire;
case "light":

View File

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

View File

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

View File

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

View File

@@ -8,31 +8,29 @@ import {
mdiCalendar,
mdiCast,
mdiCastConnected,
mdiChartSankey,
mdiCheckCircleOutline,
mdiClock,
mdiCloseCircleOutline,
mdiGestureTapButton,
mdiLanConnect,
mdiLanDisconnect,
mdiLightSwitch,
mdiLock,
mdiLockAlert,
mdiLockClock,
mdiLockOpen,
mdiPackage,
mdiPackageDown,
mdiPackageUp,
mdiPowerPlug,
mdiPowerPlugOff,
mdiRestart,
mdiSwapHorizontal,
mdiToggleSwitchVariant,
mdiToggleSwitchVariantOff,
mdiToggleSwitch,
mdiToggleSwitchOff,
mdiCheckCircleOutline,
mdiCloseCircleOutline,
mdiWeatherNight,
mdiPackage,
mdiPackageDown,
} from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
import { UpdateEntity, updateIsInstalling } from "../../data/update";
import { weatherIcon } from "../../data/weather";
import { updateIsInstalling, UpdateEntity } from "../../data/update";
/**
* Return the icon to be used for a domain.
*
@@ -49,20 +47,6 @@ export const domainIcon = (
stateObj?: HassEntity,
state?: string
): string => {
const icon = domainIconWithoutDefault(domain, stateObj, state);
if (icon) {
return icon;
}
// eslint-disable-next-line
console.warn(`Unable to find icon for domain ${domain}`);
return DEFAULT_DOMAIN_ICON;
};
export const domainIconWithoutDefault = (
domain: string,
stateObj?: HassEntity,
state?: string
): string | undefined => {
const compareState = state !== undefined ? state : stateObj?.state;
switch (domain) {
@@ -104,15 +88,6 @@ export const domainIconWithoutDefault = (
? mdiCheckCircleOutline
: mdiCloseCircleOutline;
case "input_datetime":
if (!stateObj?.attributes.has_date) {
return mdiClock;
}
if (!stateObj.attributes.has_time) {
return mdiCalendar;
}
break;
case "lock":
switch (compareState) {
case "unlocked":
@@ -134,11 +109,9 @@ export const domainIconWithoutDefault = (
case "outlet":
return compareState === "on" ? mdiPowerPlug : mdiPowerPlugOff;
case "switch":
return compareState === "on"
? mdiToggleSwitchVariant
: mdiToggleSwitchVariantOff;
return compareState === "on" ? mdiToggleSwitch : mdiToggleSwitchOff;
default:
return mdiToggleSwitchVariant;
return mdiLightSwitch;
}
case "sensor": {
@@ -150,31 +123,33 @@ export const domainIconWithoutDefault = (
break;
}
case "input_datetime":
if (!stateObj?.attributes.has_date) {
return mdiClock;
}
if (!stateObj.attributes.has_time) {
return mdiCalendar;
}
break;
case "sun":
return stateObj?.state === "above_horizon"
? FIXED_DOMAIN_ICONS[domain]
: mdiWeatherNight;
case "switch_as_x":
return mdiSwapHorizontal;
case "threshold":
return mdiChartSankey;
case "update":
return compareState === "on"
? updateIsInstalling(stateObj as UpdateEntity)
? mdiPackageDown
: mdiPackageUp
: mdiPackage;
case "weather":
return weatherIcon(stateObj?.state);
}
if (domain in FIXED_DOMAIN_ICONS) {
return FIXED_DOMAIN_ICONS[domain];
}
return undefined;
// eslint-disable-next-line
console.warn(`Unable to find icon for domain ${domain}`);
return DEFAULT_DOMAIN_ICON;
};

View File

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

View File

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

View File

@@ -7,11 +7,8 @@ import { round } from "./round";
* @param stateObj The entity state object
*/
export const isNumericState = (stateObj: HassEntity): boolean =>
isNumericFromAttributes(stateObj.attributes);
export const isNumericFromAttributes = (attributes: {
[key: string]: any;
}): boolean => !!attributes.unit_of_measurement || !!attributes.state_class;
!!stateObj.attributes.unit_of_measurement ||
!!stateObj.attributes.state_class;
export const numberFormatToLocale = (
localeOptions: FrontendLocaleData

View File

@@ -7,7 +7,6 @@ export const iconColorCSS = css`
ha-state-icon[data-domain="calendar"][data-state="on"],
ha-state-icon[data-domain="camera"][data-state="streaming"],
ha-state-icon[data-domain="cover"][data-state="open"],
ha-state-icon[data-domain="device_tracker"][data-state="home"],
ha-state-icon[data-domain="fan"][data-state="on"],
ha-state-icon[data-domain="humidifier"][data-state="on"],
ha-state-icon[data-domain="light"][data-state="on"],
@@ -70,9 +69,7 @@ export const iconColorCSS = css`
}
}
ha-state-icon[data-domain="plant"][data-state="problem"] {
color: var(--state-icon-error-color);
}
ha-state-icon[data-domain="plant"][data-state="problem"],
/* Color the icon if unavailable */
ha-state-icon[data-state="unavailable"] {

View File

@@ -1,4 +1,3 @@
import { LitElement } from "lit";
import { HomeAssistant } from "../../types";
export function computeRTL(hass: HomeAssistant) {
@@ -16,21 +15,3 @@ export function computeRTLDirection(hass: HomeAssistant) {
export function emitRTLDirection(rtl: boolean) {
return rtl ? "rtl" : "ltr";
}
export function computeDirectionStyles(isRTL: boolean, element: LitElement) {
const direction: string = emitRTLDirection(isRTL);
setDirectionStyles(direction, element);
}
export function setDirectionStyles(direction: string, element: LitElement) {
element.style.direction = direction;
element.style.setProperty("--direction", direction);
element.style.setProperty(
"--float-start",
direction === "ltr" ? "left" : "right"
);
element.style.setProperty(
"--float-end",
direction === "ltr" ? "right" : "left"
);
}

View File

@@ -1,4 +1,4 @@
export const promiseTimeout = (ms: number, promise: Promise<any> | any) => {
export const promiseTimeout = (ms: number, promise: Promise<any>) => {
const timeout = new Promise((_resolve, reject) => {
setTimeout(() => {
reject(`Timed out in ${ms} ms.`);

View File

@@ -1,18 +0,0 @@
import { HomeAssistant } from "../../types";
export const subscribePollingCollection = (
hass: HomeAssistant,
updateData: (hass: HomeAssistant) => void,
interval: number
) => {
let timeout;
const fetchData = async () => {
try {
await updateData(hass);
} finally {
timeout = setTimeout(() => fetchData(), interval);
}
};
fetchData();
return () => clearTimeout(timeout);
};

View File

@@ -13,7 +13,7 @@ export const throttle = <T extends any[]>(
) => {
let timeout: number | undefined;
let previous = 0;
const throttledFunc = (...args: T): void => {
return (...args: T): void => {
const later = () => {
previous = leading === false ? 0 : Date.now();
timeout = undefined;
@@ -35,10 +35,4 @@ export const throttle = <T extends any[]>(
timeout = window.setTimeout(later, remaining);
}
};
throttledFunc.cancel = () => {
clearTimeout(timeout);
timeout = undefined;
previous = 0;
};
return throttledFunc;
};

View File

@@ -1,53 +0,0 @@
import { HomeAssistant } from "../../types";
interface ResultCache<T> {
[entityId: string]: Promise<T> | undefined;
}
/**
* Call a function with result caching per entity.
* @param cacheKey key to store the cache on hass object
* @param cacheTime time to cache the results
* @param func function to fetch the data
* @param hass Home Assistant object
* @param entityId entity to fetch data for
* @param args extra arguments to pass to the function to fetch the data
* @returns
*/
export const timeCacheEntityPromiseFunc = async <T>(
cacheKey: string,
cacheTime: number,
func: (hass: HomeAssistant, entityId: string, ...args: any[]) => Promise<T>,
hass: HomeAssistant,
entityId: string,
...args: any[]
): Promise<T> => {
let cache: ResultCache<T> | undefined = (hass as any)[cacheKey];
if (!cache) {
cache = hass[cacheKey] = {};
}
const lastResult = cache[entityId];
if (lastResult) {
return lastResult;
}
const result = func(hass, entityId, ...args);
cache[entityId] = result;
result.then(
// When successful, set timer to clear cache
() =>
setTimeout(() => {
cache![entityId] = undefined;
}, cacheTime),
// On failure, clear cache right away
() => {
cache![entityId] = undefined;
}
);
return result;
};

View File

@@ -1,80 +1,43 @@
import { HomeAssistant } from "../../types";
interface CacheResult<T> {
result: T;
cacheKey: any;
interface ResultCache<T> {
[entityId: string]: Promise<T> | undefined;
}
/**
* Caches a result of a promise for X time. Allows optional extra validation
* check to invalidate the cache.
* @param cacheKey the key to store the cache
* @param cacheTime the time to cache the result
* @param func the function to fetch the data
* @param generateCacheKey optional function to generate a cache key based on current hass + cached result. Cache is invalid if generates a different cache key.
* @param hass Home Assistant object
* @param args extra arguments to pass to the function to fetch the data
* @returns
*/
export const timeCachePromiseFunc = async <T>(
cacheKey: string,
cacheTime: number,
func: (hass: HomeAssistant, ...args: any[]) => Promise<T>,
generateCacheKey:
| ((hass: HomeAssistant, lastResult: T) => unknown)
| undefined,
func: (hass: HomeAssistant, entityId: string, ...args: any[]) => Promise<T>,
hass: HomeAssistant,
entityId: string,
...args: any[]
): Promise<T> => {
const anyHass = hass as any;
const lastResult: Promise<CacheResult<T>> | CacheResult<T> | undefined =
anyHass[cacheKey];
let cache: ResultCache<T> | undefined = (hass as any)[cacheKey];
const checkCachedResult = (result: CacheResult<T>): T | Promise<T> => {
if (
!generateCacheKey ||
generateCacheKey(hass, result.result) === result.cacheKey
) {
return result.result;
}
anyHass[cacheKey] = undefined;
return timeCachePromiseFunc(
cacheKey,
cacheTime,
func,
generateCacheKey,
hass,
...args
);
};
// If we have a cached result, return it if it's still valid
if (lastResult) {
return lastResult instanceof Promise
? lastResult.then(checkCachedResult)
: checkCachedResult(lastResult);
if (!cache) {
cache = hass[cacheKey] = {};
}
const resultPromise = func(hass, ...args);
anyHass[cacheKey] = resultPromise;
const lastResult = cache[entityId];
resultPromise.then(
if (lastResult) {
return lastResult;
}
const result = func(hass, entityId, ...args);
cache[entityId] = result;
result.then(
// When successful, set timer to clear cache
(result) => {
anyHass[cacheKey] = {
result,
cacheKey: generateCacheKey?.(hass, result),
};
() =>
setTimeout(() => {
anyHass[cacheKey] = undefined;
}, cacheTime);
},
cache![entityId] = undefined;
}, cacheTime),
// On failure, clear cache right away
() => {
anyHass[cacheKey] = undefined;
cache![entityId] = undefined;
}
);
return resultPromise;
return result;
};

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