Merge branch 'dev' into grid-card-size-algorithm
This commit is contained in:
commit
ecc9ddb1cb
|
@ -1,5 +1,5 @@
|
|||
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/python-3/.devcontainer/base.Dockerfile
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.11
|
||||
FROM mcr.microsoft.com/devcontainers/python:3.12
|
||||
|
||||
ENV \
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
|
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4.1.2
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
|
@ -57,7 +57,7 @@ jobs:
|
|||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4.1.2
|
||||
with:
|
||||
ref: master
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4.1.2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
|
@ -37,7 +37,7 @@ jobs:
|
|||
- name: Build resources
|
||||
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
|
||||
- name: Setup lint cache
|
||||
uses: actions/cache@v4.0.0
|
||||
uses: actions/cache@v4.0.1
|
||||
with:
|
||||
path: |
|
||||
node_modules/.cache/prettier
|
||||
|
@ -58,7 +58,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4.1.2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
|
@ -76,7 +76,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4.1.2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
|
@ -100,7 +100,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4.1.2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
|
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4.1.2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
|
|
|
@ -22,7 +22,7 @@ jobs:
|
|||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4.1.2
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
|
@ -58,7 +58,7 @@ jobs:
|
|||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4.1.2
|
||||
with:
|
||||
ref: master
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4.1.2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.2
|
||||
|
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4.1.2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.2
|
||||
|
|
|
@ -6,7 +6,7 @@ on:
|
|||
- cron: "0 1 * * *"
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.11"
|
||||
PYTHON_VERSION: "3.12"
|
||||
NODE_OPTIONS: --max_old_space_size=6144
|
||||
|
||||
permissions:
|
||||
|
@ -20,7 +20,7 @@ jobs:
|
|||
contents: write
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4.1.2
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v5
|
||||
|
|
|
@ -6,7 +6,7 @@ on:
|
|||
- published
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.11"
|
||||
PYTHON_VERSION: "3.12"
|
||||
NODE_OPTIONS: --max_old_space_size=6144
|
||||
|
||||
# Set default workflow permissions
|
||||
|
@ -23,7 +23,7 @@ jobs:
|
|||
contents: write # Required to upload release assets
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4.1.2
|
||||
|
||||
- name: Verify version
|
||||
uses: home-assistant/actions/helpers/verify-version@master
|
||||
|
@ -55,7 +55,7 @@ jobs:
|
|||
script/release
|
||||
|
||||
- name: Upload release assets
|
||||
uses: softprops/action-gh-release@v0.1.15
|
||||
uses: softprops/action-gh-release@v2.0.4
|
||||
with:
|
||||
files: |
|
||||
dist/*.whl
|
||||
|
|
|
@ -13,7 +13,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4.1.2
|
||||
|
||||
- name: Upload Translations
|
||||
run: |
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
diff --git a/simple-tooltip.js b/simple-tooltip.js
|
||||
index 78a87f6a223925f0e29fbedb268c85a142ec6985..3d686dd6a3d5a93342b4b01408089fc316b408ca 100644
|
||||
--- a/simple-tooltip.js
|
||||
+++ b/simple-tooltip.js
|
||||
@@ -195,6 +195,8 @@ class SimpleTooltip extends LitElement {
|
||||
.hidden {
|
||||
position: absolute;
|
||||
left: -10000px;
|
||||
+ inset-inline-start: -10000px;
|
||||
+ inset-inline-end: initial;
|
||||
top: auto;
|
||||
width: 1px;
|
||||
height: 1px;
|
|
@ -0,0 +1,18 @@
|
|||
diff --git a/dist/hls.light.mjs b/dist/hls.light.mjs
|
||||
index eed9d788fafdb159975e1a2eb08ac88ba9c9ac33..ace881935e6665946f1c8110ebd2f739cde4427e 100644
|
||||
--- a/dist/hls.light.mjs
|
||||
+++ b/dist/hls.light.mjs
|
||||
@@ -20523,9 +20523,9 @@ class Hls {
|
||||
}
|
||||
Hls.defaultConfig = void 0;
|
||||
|
||||
-var KeySystemFormats = empty.KeySystemFormats;
|
||||
-var KeySystems = empty.KeySystems;
|
||||
-var SubtitleStreamController = empty.SubtitleStreamController;
|
||||
-var TimelineController = empty.TimelineController;
|
||||
+var KeySystemFormats = empty;
|
||||
+var KeySystems = empty;
|
||||
+var SubtitleStreamController = empty;
|
||||
+var TimelineController = empty;
|
||||
export { AbrController, AttrList, Cues as AudioStreamController, Cues as AudioTrackController, BasePlaylistController, BaseSegment, BaseStreamController, BufferController, Cues as CMCDController, CapLevelController, ChunkMetadata, ContentSteeringController, DateRange, Cues as EMEController, ErrorActionFlags, ErrorController, ErrorDetails, ErrorTypes, Events, FPSController, Fragment, Hls, HlsSkip, HlsUrlParameters, KeySystemFormats, KeySystems, Level, LevelDetails, LevelKey, LoadStats, MetadataSchema, NetworkErrorAction, Part, PlaylistLevelType, SubtitleStreamController, Cues as SubtitleTrackController, TimelineController, Hls as default, getMediaSource, isMSESupported, isSupported };
|
||||
//# sourceMappingURL=hls.light.mjs.map
|
File diff suppressed because one or more lines are too long
|
@ -6,4 +6,4 @@ enableGlobalCache: false
|
|||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.1.0.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.1.1.cjs
|
||||
|
|
|
@ -28,7 +28,7 @@ class HcLaunchScreen extends LitElement {
|
|||
:host {
|
||||
display: block;
|
||||
height: 100vh;
|
||||
background-color: white;
|
||||
background-color: #f2f4f9;
|
||||
font-size: 24px;
|
||||
}
|
||||
.container {
|
||||
|
@ -43,6 +43,9 @@ class HcLaunchScreen extends LitElement {
|
|||
max-width: 80%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.status {
|
||||
color: #1d2126;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -270,7 +270,7 @@ export class HcMain extends HassElement {
|
|||
}
|
||||
|
||||
this._error = undefined;
|
||||
if (msg.urlPath === "lovelace") {
|
||||
if (msg.urlPath === "lovelace" || msg.urlPath === undefined) {
|
||||
msg.urlPath = null;
|
||||
}
|
||||
this._lovelacePath = msg.viewPath;
|
||||
|
|
|
@ -4,6 +4,7 @@ import { energyEntities } from "../stubs/entities";
|
|||
import { DemoConfig } from "./types";
|
||||
|
||||
export const demoConfigs: Array<() => Promise<DemoConfig>> = [
|
||||
() => import("./sections").then((mod) => mod.demoSections),
|
||||
() => import("./arsaboo").then((mod) => mod.demoArsaboo),
|
||||
() => import("./teachingbirds").then((mod) => mod.demoTeachingbirds),
|
||||
() => import("./kernehed").then((mod) => mod.demoKernehed),
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import { html } from "lit";
|
||||
import { DemoConfig } from "../types";
|
||||
|
||||
export const demoLovelaceDescription: DemoConfig["description"] = (
|
||||
localize
|
||||
) => html`
|
||||
<p>
|
||||
${localize("ui.panel.page-demo.config.sections.description", {
|
||||
blog_post: html`<a
|
||||
href="https://www.home-assistant.io/blog/2024/03/04/dashboard-chapter-1/"
|
||||
target="_blank"
|
||||
>${localize("ui.panel.page-demo.config.sections.description_blog_post")}
|
||||
</a>`,
|
||||
})}
|
||||
</p>
|
||||
`;
|
|
@ -0,0 +1,474 @@
|
|||
import { convertEntities } from "../../../../src/fake_data/entity";
|
||||
import { DemoConfig } from "../types";
|
||||
|
||||
export const demoEntitiesSections: DemoConfig["entities"] = () =>
|
||||
convertEntities({
|
||||
"cover.living_room_garden_shutter": {
|
||||
entity_id: "cover.living_room_garden_shutter",
|
||||
state: "open",
|
||||
attributes: {
|
||||
current_position: 100,
|
||||
device_class: "shutter",
|
||||
friendly_name: "Living room garden shutter",
|
||||
supported_features: 15,
|
||||
},
|
||||
},
|
||||
"cover.living_room_graveyard_shutter": {
|
||||
entity_id: "cover.living_room_graveyard_shutter",
|
||||
state: "open",
|
||||
attributes: {
|
||||
current_position: 100,
|
||||
device_class: "shutter",
|
||||
friendly_name: "Living room graveyard shutter",
|
||||
supported_features: 15,
|
||||
},
|
||||
},
|
||||
"cover.living_room_left_shutter": {
|
||||
entity_id: "cover.living_room_left_shutter",
|
||||
state: "open",
|
||||
attributes: {
|
||||
current_position: 100,
|
||||
device_class: "shutter",
|
||||
friendly_name: "Living room left shutter",
|
||||
supported_features: 15,
|
||||
},
|
||||
},
|
||||
"cover.living_room_right_shutter": {
|
||||
entity_id: "cover.living_room_right_shutter",
|
||||
state: "open",
|
||||
attributes: {
|
||||
current_position: 100,
|
||||
device_class: "shutter",
|
||||
friendly_name: "Living room right shutter",
|
||||
supported_features: 15,
|
||||
},
|
||||
},
|
||||
"light.floor_lamp": {
|
||||
entity_id: "light.floor_lamp",
|
||||
state: "on",
|
||||
attributes: {
|
||||
min_color_temp_kelvin: 2000,
|
||||
max_color_temp_kelvin: 6535,
|
||||
min_mireds: 153,
|
||||
max_mireds: 500,
|
||||
supported_color_modes: ["color_temp", "xy"],
|
||||
color_mode: "color_temp",
|
||||
brightness: 178,
|
||||
color_temp_kelvin: 2583,
|
||||
color_temp: 387,
|
||||
hs_color: [28.664, 69.597],
|
||||
rgb_color: [255, 162, 77],
|
||||
xy_color: [0.538, 0.389],
|
||||
icon: "mdi:floor-lamp",
|
||||
friendly_name: "Floor lamp",
|
||||
supported_features: 44,
|
||||
},
|
||||
},
|
||||
"light.living_room_spotlights": {
|
||||
entity_id: "light.living_room_spotlights",
|
||||
state: "on",
|
||||
attributes: {
|
||||
supported_color_modes: ["brightness"],
|
||||
color_mode: "brightness",
|
||||
brightness: 126,
|
||||
icon: "mdi:ceiling-light-multiple",
|
||||
friendly_name: "Living room spotlights",
|
||||
supported_features: 32,
|
||||
},
|
||||
},
|
||||
"light.bar_lamp": {
|
||||
entity_id: "light.bar_lamp",
|
||||
state: "on",
|
||||
attributes: {
|
||||
min_color_temp_kelvin: 2202,
|
||||
max_color_temp_kelvin: 4504,
|
||||
min_mireds: 222,
|
||||
max_mireds: 454,
|
||||
effect_list: ["None", "candle"],
|
||||
supported_color_modes: ["color_temp"],
|
||||
effect: null,
|
||||
color_mode: null,
|
||||
brightness: null,
|
||||
color_temp_kelvin: null,
|
||||
color_temp: null,
|
||||
hs_color: null,
|
||||
rgb_color: null,
|
||||
xy_color: null,
|
||||
mode: "normal",
|
||||
dynamics: "none",
|
||||
icon: "mdi:lightbulb-variant",
|
||||
friendly_name: "Bar lamp",
|
||||
supported_features: 44,
|
||||
},
|
||||
},
|
||||
"sensor.living_room_temperature": {
|
||||
entity_id: "sensor.living_room_temperature",
|
||||
state: "22.8",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
unit_of_measurement: "°C",
|
||||
device_class: "temperature",
|
||||
friendly_name: "Living room Temperature",
|
||||
},
|
||||
},
|
||||
"media_player.living_room_nest_mini": {
|
||||
entity_id: "media_player.living_room_nest_mini",
|
||||
state: "off",
|
||||
attributes: {
|
||||
device_class: "speaker",
|
||||
friendly_name: "Living room Nest Mini",
|
||||
supported_features: 152461,
|
||||
},
|
||||
},
|
||||
"cover.kitchen_shutter": {
|
||||
entity_id: "cover.kitchen_shutter",
|
||||
state: "open",
|
||||
attributes: {
|
||||
current_position: 100,
|
||||
device_class: "shutter",
|
||||
friendly_name: "Kitchen shutter ",
|
||||
supported_features: 15,
|
||||
},
|
||||
},
|
||||
"light.kitchen_spotlights": {
|
||||
entity_id: "light.kitchen_spotlights",
|
||||
state: "off",
|
||||
attributes: {
|
||||
supported_color_modes: ["brightness"],
|
||||
color_mode: null,
|
||||
brightness: null,
|
||||
icon: "mdi:ceiling-light-multiple",
|
||||
friendly_name: "Kitchen spotlights ",
|
||||
supported_features: 32,
|
||||
},
|
||||
},
|
||||
"light.worktop_spotlights": {
|
||||
entity_id: "light.worktop_spotlights",
|
||||
state: "off",
|
||||
attributes: {
|
||||
supported_color_modes: ["brightness"],
|
||||
color_mode: null,
|
||||
brightness: null,
|
||||
icon: "mdi:ceiling-light-multiple",
|
||||
friendly_name: "Worktop spotlights ",
|
||||
supported_features: 32,
|
||||
},
|
||||
},
|
||||
"binary_sensor.fridge_door": {
|
||||
entity_id: "binary_sensor.fridge_door",
|
||||
state: "off",
|
||||
attributes: {
|
||||
device_class: "door",
|
||||
icon: "mdi:fridge",
|
||||
friendly_name: "Fridge door",
|
||||
},
|
||||
},
|
||||
"media_player.kitchen_nest_audio": {
|
||||
entity_id: "media_player.kitchen_nest_audio",
|
||||
state: "on",
|
||||
attributes: {
|
||||
device_class: "speaker",
|
||||
friendly_name: "Kitchen Nest Audio",
|
||||
supported_features: 152461,
|
||||
},
|
||||
},
|
||||
"binary_sensor.tesla_wall_connector_vehicle_connected": {
|
||||
entity_id: "binary_sensor.tesla_wall_connector_vehicle_connected",
|
||||
state: "off",
|
||||
attributes: {
|
||||
device_class: "plug",
|
||||
friendly_name: "Wall Connector Vehicle connected",
|
||||
},
|
||||
},
|
||||
"sensor.tesla_wall_connector_session_energy": {
|
||||
entity_id: "sensor.tesla_wall_connector_session_energy",
|
||||
state: "16.3",
|
||||
attributes: {
|
||||
state_class: "total_increasing",
|
||||
unit_of_measurement: "kWh",
|
||||
device_class: "energy",
|
||||
friendly_name: "Tesla Wall Connector Session energy",
|
||||
},
|
||||
},
|
||||
"sensor.electric_meter_power": {
|
||||
entity_id: "sensor.electric_meter_power",
|
||||
state: "797.86",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
unit_of_measurement: "W",
|
||||
device_class: "power",
|
||||
icon: "mdi:meter-electric",
|
||||
friendly_name: "Electric meter Power",
|
||||
},
|
||||
},
|
||||
"sensor.eletric_meter_voltage": {
|
||||
entity_id: "sensor.eletric_meter_voltage",
|
||||
state: "232.19",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
unit_of_measurement: "V",
|
||||
device_class: "voltage",
|
||||
friendly_name: "Electric meter voltage",
|
||||
},
|
||||
},
|
||||
"sensor.electricity_maps_grid_fossil_fuel_percentage": {
|
||||
entity_id: "sensor.electricity_maps_grid_fossil_fuel_percentage",
|
||||
state: "9.84",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
country_code: "FR",
|
||||
unit_of_measurement: "%",
|
||||
attribution: "Data provided by Electricity Maps",
|
||||
icon: "mdi:barrel",
|
||||
friendly_name: "Electricity Maps Grid fossil fuel percentage",
|
||||
},
|
||||
},
|
||||
"sensor.electricity_maps_co2_intensity": {
|
||||
entity_id: "sensor.electricity_maps_co2_intensity",
|
||||
state: "62.0",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
country_code: "FR",
|
||||
unit_of_measurement: "gCO2eq/kWh",
|
||||
attribution: "Data provided by Electricity Maps",
|
||||
friendly_name: "Electricity Maps CO2 intensity",
|
||||
icon: "mdi:molecule-co2",
|
||||
},
|
||||
},
|
||||
"sun.sun": {
|
||||
entity_id: "sun.sun",
|
||||
state: "above_horizon",
|
||||
attributes: {
|
||||
next_dawn: "2024-03-05T05:50:21.964405+00:00",
|
||||
next_dusk: "2024-03-04T18:08:54.311334+00:00",
|
||||
next_midnight: "2024-03-05T00:00:00+00:00",
|
||||
next_noon: "2024-03-05T12:00:05+00:00",
|
||||
next_rising: "2024-03-05T06:23:42.739159+00:00",
|
||||
next_setting: "2024-03-04T17:35:26.271171+00:00",
|
||||
elevation: 30.38,
|
||||
azimuth: 204.42,
|
||||
rising: false,
|
||||
friendly_name: "Sun",
|
||||
},
|
||||
},
|
||||
"sensor.rain": {
|
||||
entity_id: "sensor.moon_phase",
|
||||
state: "7.2",
|
||||
attributes: {
|
||||
state_class: "total_increasing",
|
||||
unit_of_measurement: "mm",
|
||||
device_class: "precipitation",
|
||||
friendly_name: "Rain",
|
||||
},
|
||||
},
|
||||
"climate.ground_floor": {
|
||||
entity_id: "climate.ground_floor",
|
||||
state: "heat",
|
||||
attributes: {
|
||||
hvac_modes: ["auto", "heat", "off"],
|
||||
min_temp: 7,
|
||||
max_temp: 35,
|
||||
preset_modes: [
|
||||
"comfort",
|
||||
"away",
|
||||
"eco",
|
||||
"frost_protection",
|
||||
"external",
|
||||
"home",
|
||||
],
|
||||
current_temperature: 20.8,
|
||||
temperature: 21,
|
||||
preset_mode: "comfort",
|
||||
icon: "mdi:home-floor-0",
|
||||
friendly_name: "Ground floor Thermostat",
|
||||
supported_features: 401,
|
||||
},
|
||||
},
|
||||
"climate.first_floor": {
|
||||
entity_id: "climate.first_floor",
|
||||
state: "heat",
|
||||
attributes: {
|
||||
hvac_modes: ["auto", "heat", "off"],
|
||||
min_temp: 7,
|
||||
max_temp: 35,
|
||||
preset_modes: [
|
||||
"comfort",
|
||||
"away",
|
||||
"eco",
|
||||
"frost_protection",
|
||||
"external",
|
||||
"home",
|
||||
],
|
||||
current_temperature: 21.7,
|
||||
temperature: 21,
|
||||
preset_mode: "comfort",
|
||||
icon: "mdi:home-floor-1",
|
||||
friendly_name: "First floor Thermostat",
|
||||
supported_features: 401,
|
||||
},
|
||||
},
|
||||
"cover.study_shutter": {
|
||||
entity_id: "cover.study_shutter",
|
||||
state: "open",
|
||||
attributes: {
|
||||
current_position: 100,
|
||||
device_class: "shutter",
|
||||
friendly_name: "Study shutter",
|
||||
supported_features: 15,
|
||||
},
|
||||
},
|
||||
"light.study_spotlights": {
|
||||
entity_id: "light.study_spotlights",
|
||||
state: "off",
|
||||
attributes: {
|
||||
supported_color_modes: ["brightness"],
|
||||
color_mode: null,
|
||||
brightness: null,
|
||||
icon: "mdi:ceiling-light-multiple",
|
||||
friendly_name: "Study spotlights",
|
||||
supported_features: 32,
|
||||
},
|
||||
},
|
||||
"media_player.study_nest_hub": {
|
||||
entity_id: "media_player.study_nest_hub",
|
||||
state: "off",
|
||||
attributes: {
|
||||
friendly_name: "Study Nest Hub",
|
||||
supported_features: 152461,
|
||||
},
|
||||
},
|
||||
"sensor.standing_desk_height": {
|
||||
entity_id: "sensor.standing_desk_height",
|
||||
state: "72",
|
||||
attributes: {
|
||||
unit_of_measurement: "cm",
|
||||
icon: "mdi:tape-measure",
|
||||
friendly_name: "Standing desk Height",
|
||||
},
|
||||
},
|
||||
"light.outdoor_light": {
|
||||
entity_id: "light.outdoor_light",
|
||||
state: "on",
|
||||
attributes: {
|
||||
supported_color_modes: ["brightness"],
|
||||
color_mode: null,
|
||||
brightness: 255,
|
||||
icon: "mdi:outdoor-lamp",
|
||||
friendly_name: "Outdoor light",
|
||||
supported_features: 32,
|
||||
},
|
||||
},
|
||||
"light.flood_light": {
|
||||
entity_id: "light.flood_light",
|
||||
state: "off",
|
||||
attributes: {
|
||||
effect_list: ["None", "candle"],
|
||||
supported_color_modes: ["brightness"],
|
||||
effect: null,
|
||||
color_mode: null,
|
||||
brightness: null,
|
||||
mode: "normal",
|
||||
dynamics: "none",
|
||||
icon: "mdi:light-flood-down",
|
||||
friendly_name: "Flood light",
|
||||
supported_features: 44,
|
||||
},
|
||||
},
|
||||
"sensor.outdoor_motion_sensor_temperature": {
|
||||
entity_id: "sensor.outdoor_motion_sensor_temperature",
|
||||
state: "10.2",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
unit_of_measurement: "°C",
|
||||
device_class: "temperature",
|
||||
friendly_name: "Outdoor motion sensor Temperature",
|
||||
},
|
||||
},
|
||||
"binary_sensor.outdoor_motion_sensor_motion": {
|
||||
entity_id: "binary_sensor.outdoor_motion_sensor_motion",
|
||||
state: "off",
|
||||
attributes: {
|
||||
device_class: "motion",
|
||||
friendly_name: "Outdoor motion sensor Motion",
|
||||
},
|
||||
},
|
||||
"sensor.outdoor_motion_sensor_illuminance": {
|
||||
entity_id: "sensor.outdoor_motion_sensor_illuminance",
|
||||
state: "555",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
light_level: 27444,
|
||||
unit_of_measurement: "lx",
|
||||
device_class: "illuminance",
|
||||
friendly_name: "Outdoor motion sensor Illuminance",
|
||||
},
|
||||
},
|
||||
"automation.home_assistant_auto_update": {
|
||||
entity_id: "automation.home_assistant_auto_update",
|
||||
state: "off",
|
||||
attributes: {
|
||||
id: "1700669321947",
|
||||
last_triggered: "2024-02-29T18:02:05.343139+00:00",
|
||||
mode: "queued",
|
||||
current: 0,
|
||||
max: 50,
|
||||
icon: "mdi:auto-mode",
|
||||
friendly_name: "Home Assistant Auto-update",
|
||||
},
|
||||
},
|
||||
"update.home_assistant_operating_system_update": {
|
||||
entity_id: "update.home_assistant_operating_system_update",
|
||||
state: "off",
|
||||
attributes: {
|
||||
auto_update: false,
|
||||
installed_version: "12.1",
|
||||
in_progress: false,
|
||||
latest_version: "12.1",
|
||||
release_summary: null,
|
||||
release_url:
|
||||
"https://github.com/home-assistant/operating-system/commits/dev",
|
||||
skipped_version: null,
|
||||
title: "Home Assistant Operating System",
|
||||
entity_picture:
|
||||
"https://brands.home-assistant.io/homeassistant/icon.png",
|
||||
friendly_name: "Home Assistant Operating System Update",
|
||||
supported_features: 3,
|
||||
},
|
||||
},
|
||||
"update.home_assistant_supervisor_update": {
|
||||
entity_id: "update.home_assistant_supervisor_update",
|
||||
state: "off",
|
||||
attributes: {
|
||||
auto_update: true,
|
||||
installed_version: "2024.02.2",
|
||||
in_progress: false,
|
||||
latest_version: "2024.02.2",
|
||||
release_summary: null,
|
||||
release_url:
|
||||
"https://github.com/home-assistant/supervisor/commits/main",
|
||||
skipped_version: null,
|
||||
title: "Home Assistant Supervisor",
|
||||
entity_picture: "https://brands.home-assistant.io/hassio/icon.png",
|
||||
friendly_name: "Home Assistant Supervisor Update",
|
||||
supported_features: 1,
|
||||
},
|
||||
},
|
||||
"update.home_assistant_core_update": {
|
||||
entity_id: "update.home_assistant_supervisor_update",
|
||||
state: "off",
|
||||
attributes: {
|
||||
auto_update: false,
|
||||
installed_version: "2024.4.0",
|
||||
in_progress: false,
|
||||
latest_version: "2024.4.0",
|
||||
release_summary: null,
|
||||
release_url: "https://github.com/home-assistant/core/commits/dev",
|
||||
skipped_version: null,
|
||||
title: "Home Assistant Core",
|
||||
entity_picture:
|
||||
"https://brands.home-assistant.io/homeassistant/icon.png",
|
||||
friendly_name: "Home Assistant Core Update",
|
||||
supported_features: 11,
|
||||
},
|
||||
},
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
import { DemoConfig } from "../types";
|
||||
import { demoLovelaceDescription } from "./description";
|
||||
import { demoEntitiesSections } from "./entities";
|
||||
import { demoLovelaceSections } from "./lovelace";
|
||||
|
||||
export const demoSections: DemoConfig = {
|
||||
authorName: "Home Assistant",
|
||||
authorUrl: "https://github.com/home-assistant/frontend/",
|
||||
name: "Home Demo",
|
||||
description: demoLovelaceDescription,
|
||||
lovelace: demoLovelaceSections,
|
||||
entities: demoEntitiesSections,
|
||||
theme: () => ({}),
|
||||
};
|
|
@ -0,0 +1,281 @@
|
|||
import { DemoConfig } from "../types";
|
||||
|
||||
export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
|
||||
title: "Home Assistant Demo",
|
||||
views: [
|
||||
{
|
||||
type: "sections",
|
||||
title: "Demo",
|
||||
path: "home",
|
||||
icon: "mdi:home-assistant",
|
||||
sections: [
|
||||
{
|
||||
title: "Welcome 👋",
|
||||
cards: [{ type: "custom:ha-demo-card" }],
|
||||
},
|
||||
{
|
||||
cards: [
|
||||
{
|
||||
type: "tile",
|
||||
entity: "cover.living_room_garden_shutter",
|
||||
name: "Garden",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "cover.living_room_graveyard_shutter",
|
||||
name: "Rear",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "cover.living_room_left_shutter",
|
||||
name: "Left",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "cover.living_room_right_shutter",
|
||||
name: "Right",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "light.floor_lamp",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "light.living_room_spotlights",
|
||||
name: "Spotlights",
|
||||
features: [
|
||||
{
|
||||
type: "light-brightness",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "light.bar_lamp",
|
||||
},
|
||||
{
|
||||
graph: "line",
|
||||
type: "sensor",
|
||||
entity: "sensor.living_room_temperature",
|
||||
detail: 1,
|
||||
name: "Temperature",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "media_player.living_room_nest_mini",
|
||||
name: "Nest Mini",
|
||||
},
|
||||
],
|
||||
title: "🛋️ Living room ",
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "tile",
|
||||
entity: "cover.kitchen_shutter",
|
||||
name: "Shutter",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "light.kitchen_spotlights",
|
||||
name: "Spotlights",
|
||||
features: [
|
||||
{
|
||||
type: "light-brightness",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "light.worktop_spotlights",
|
||||
name: "Worktop",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "binary_sensor.fridge_door",
|
||||
name: "Fridge",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "media_player.kitchen_nest_audio",
|
||||
name: "Nest Audio",
|
||||
},
|
||||
],
|
||||
title: "👩🍳 Kitchen",
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "tile",
|
||||
entity: "binary_sensor.tesla_wall_connector_vehicle_connected",
|
||||
name: "EV",
|
||||
icon: "mdi:car",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "sensor.tesla_wall_connector_session_energy",
|
||||
name: "Last charge",
|
||||
color: "green",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "sensor.electric_meter_power",
|
||||
color: "deep-orange",
|
||||
name: "Home power",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "sensor.eletric_meter_voltage",
|
||||
name: "Voltage",
|
||||
color: "deep-orange",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "sensor.electricity_maps_grid_fossil_fuel_percentage",
|
||||
name: "Fossil fuel",
|
||||
color: "brown",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "sensor.electricity_maps_co2_intensity",
|
||||
name: "CO2 Intensity",
|
||||
color: "dark-grey",
|
||||
},
|
||||
],
|
||||
title: "⚡️ Energy",
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "tile",
|
||||
entity: "sun.sun",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "sensor.rain",
|
||||
color: "blue",
|
||||
},
|
||||
{
|
||||
features: [
|
||||
{
|
||||
type: "target-temperature",
|
||||
},
|
||||
],
|
||||
type: "tile",
|
||||
name: "Downstairs",
|
||||
entity: "climate.ground_floor",
|
||||
state_content: ["preset_mode", "current_temperature"],
|
||||
},
|
||||
{
|
||||
features: [
|
||||
{
|
||||
type: "target-temperature",
|
||||
},
|
||||
],
|
||||
type: "tile",
|
||||
name: "Upstairs",
|
||||
entity: "climate.first_floor",
|
||||
state_content: ["preset_mode", "current_temperature"],
|
||||
},
|
||||
],
|
||||
title: "🌤️ Climate",
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "tile",
|
||||
entity: "cover.study_shutter",
|
||||
name: "Shutter",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "light.study_spotlights",
|
||||
name: "Spotlights",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "media_player.study_nest_hub",
|
||||
name: "Nest Hub",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "sensor.standing_desk_height",
|
||||
name: "Desk",
|
||||
color: "brown",
|
||||
icon: "mdi:desk",
|
||||
},
|
||||
],
|
||||
title: "🧑💻 Study",
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "tile",
|
||||
entity: "light.outdoor_light",
|
||||
name: "Door light",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "light.flood_light",
|
||||
},
|
||||
{
|
||||
graph: "line",
|
||||
type: "sensor",
|
||||
entity: "sensor.outdoor_motion_sensor_temperature",
|
||||
detail: 1,
|
||||
name: "Temperature",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "binary_sensor.outdoor_motion_sensor_motion",
|
||||
name: "Motion",
|
||||
color: "blue",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "sensor.outdoor_motion_sensor_illuminance",
|
||||
color: "amber",
|
||||
name: "Illuminance",
|
||||
},
|
||||
],
|
||||
title: "🌳 Outdoor",
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "tile",
|
||||
entity: "automation.home_assistant_auto_update",
|
||||
name: "Auto-update",
|
||||
color: "green",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "update.home_assistant_operating_system_update",
|
||||
name: "OS",
|
||||
icon: "mdi:home-assistant",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "update.home_assistant_supervisor_update",
|
||||
icon: "mdi:home-assistant",
|
||||
name: "Supervisor",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "update.home_assistant_core_update",
|
||||
name: "Core",
|
||||
icon: "mdi:home-assistant",
|
||||
},
|
||||
],
|
||||
title: "🎉 Updates",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
|
@ -1,3 +1,4 @@
|
|||
import { TemplateResult } from "lit";
|
||||
import { LocalizeFunc } from "../../../src/common/translations/localize";
|
||||
import { LovelaceConfig } from "../../../src/data/lovelace/config/types";
|
||||
import { Entity } from "../../../src/fake_data/entity";
|
||||
|
@ -7,6 +8,9 @@ export interface DemoConfig {
|
|||
name: string;
|
||||
authorName: string;
|
||||
authorUrl: string;
|
||||
description?:
|
||||
| string
|
||||
| ((localize: LocalizeFunc) => string | TemplateResult<1>);
|
||||
lovelace: (localize: LocalizeFunc) => LovelaceConfig;
|
||||
entities: (localize: LocalizeFunc) => Entity[];
|
||||
theme: () => Record<string, string> | null;
|
||||
|
|
|
@ -39,32 +39,51 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
|||
<div class="picker">
|
||||
<div class="label">
|
||||
${this._switching
|
||||
? html`<ha-circular-progress
|
||||
indeterminate
|
||||
></ha-circular-progress>`
|
||||
? html`
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
`
|
||||
: until(
|
||||
selectedDemoConfig.then(
|
||||
(conf) => html`
|
||||
${conf.name}
|
||||
<small>
|
||||
<a target="_blank" href=${conf.authorUrl}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.page-demo.cards.demo.demo_by",
|
||||
{ name: conf.authorName }
|
||||
)}
|
||||
</a>
|
||||
${this.hass.localize(
|
||||
"ui.panel.page-demo.cards.demo.demo_by",
|
||||
{
|
||||
name: html`
|
||||
<a target="_blank" href=${conf.authorUrl}>
|
||||
${conf.authorName}
|
||||
</a>
|
||||
`,
|
||||
}
|
||||
)}
|
||||
</small>
|
||||
`
|
||||
),
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
|
||||
<mwc-button @click=${this._nextConfig} .disabled=${this._switching}>
|
||||
${this.hass.localize("ui.panel.page-demo.cards.demo.next_demo")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
<div class="content small-hidden">
|
||||
${this.hass.localize("ui.panel.page-demo.cards.demo.introduction")}
|
||||
<div class="content">
|
||||
<p class="small-hidden">
|
||||
${this.hass.localize("ui.panel.page-demo.cards.demo.introduction")}
|
||||
</p>
|
||||
${until(
|
||||
selectedDemoConfig.then((conf) => {
|
||||
if (typeof conf.description === "function") {
|
||||
return conf.description(this.hass.localize);
|
||||
}
|
||||
if (conf.description) {
|
||||
return html`<p>${conf.description}</p>`;
|
||||
}
|
||||
return nothing;
|
||||
}),
|
||||
nothing
|
||||
)}
|
||||
</div>
|
||||
<div class="actions small-hidden">
|
||||
<a href="https://www.home-assistant.io" target="_blank">
|
||||
|
@ -108,6 +127,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
|||
css`
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.actions a {
|
||||
|
@ -115,7 +135,11 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
|||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.content p {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.picker {
|
||||
|
@ -138,9 +162,8 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
|||
}
|
||||
|
||||
.actions {
|
||||
padding-left: 8px;
|
||||
padding: 0px 8px 4px 8px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
.small-hidden {
|
||||
display: none;
|
||||
|
|
|
@ -10,6 +10,7 @@ export const mockConfigEntries = (hass: MockHomeAssistant) => {
|
|||
supports_options: false,
|
||||
supports_remove_device: false,
|
||||
supports_unload: true,
|
||||
supports_reconfigure: true,
|
||||
pref_disable_new_entities: false,
|
||||
pref_disable_polling: false,
|
||||
disabled_by: null,
|
||||
|
|
|
@ -17,6 +17,7 @@ export const basicTrace: DemoTrace = {
|
|||
{
|
||||
path: "trigger/0",
|
||||
timestamp: "2021-03-25T04:36:51.223693+00:00",
|
||||
changed_variables: {},
|
||||
},
|
||||
],
|
||||
"condition/0": [
|
||||
|
|
|
@ -17,6 +17,7 @@ export const motionLightTrace: DemoTrace = {
|
|||
{
|
||||
path: "trigger/0",
|
||||
timestamp: "2021-03-25T04:36:51.223693+00:00",
|
||||
changed_variables: {},
|
||||
},
|
||||
],
|
||||
"action/0": [
|
||||
|
|
|
@ -21,10 +21,10 @@ const ENTITIES = [
|
|||
}),
|
||||
];
|
||||
|
||||
const conditions = [
|
||||
{ condition: "and" },
|
||||
{ condition: "not" },
|
||||
{ condition: "or" },
|
||||
const conditions: Condition[] = [
|
||||
{ condition: "and", conditions: [] },
|
||||
{ condition: "not", conditions: [] },
|
||||
{ condition: "or", conditions: [] },
|
||||
{ condition: "state", entity_id: "light.kitchen", state: "on" },
|
||||
{
|
||||
condition: "numeric_state",
|
||||
|
@ -34,11 +34,11 @@ const conditions = [
|
|||
above: 20,
|
||||
},
|
||||
{ condition: "sun", after: "sunset" },
|
||||
{ condition: "sun", after: "sunrise", offset: "-01:00" },
|
||||
{ condition: "sun", after: "sunrise", before_offset: 3600 },
|
||||
{ condition: "zone", entity_id: "device_tracker.person", zone: "zone.home" },
|
||||
{ condition: "trigger", id: "motion" },
|
||||
{ condition: "time" },
|
||||
{ condition: "template" },
|
||||
{ condition: "template", value_template: "" },
|
||||
];
|
||||
|
||||
const initialCondition: Condition = {
|
||||
|
|
|
@ -55,6 +55,7 @@ export class DemoAutomationTraceTimeline extends LitElement {
|
|||
super.firstUpdated(changedProps);
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("config", "en");
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
|
|
|
@ -60,6 +60,7 @@ export class DemoAutomationTrace extends LitElement {
|
|||
super.firstUpdated(changedProps);
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("config", "en");
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
|
|
|
@ -11,7 +11,7 @@ const ENTITIES = [
|
|||
latitude: 32.877105,
|
||||
longitude: 117.232185,
|
||||
gps_accuracy: 91,
|
||||
battery: 71,
|
||||
battery: 25,
|
||||
friendly_name: "Paulus",
|
||||
}),
|
||||
getEntity("device_tracker", "demo_anne_therese", "school", {
|
||||
|
@ -19,7 +19,7 @@ const ENTITIES = [
|
|||
latitude: 32.877105,
|
||||
longitude: 117.232185,
|
||||
gps_accuracy: 91,
|
||||
battery: 71,
|
||||
battery: 50,
|
||||
friendly_name: "Anne Therese",
|
||||
}),
|
||||
getEntity("device_tracker", "demo_home_boy", "home", {
|
||||
|
@ -27,7 +27,7 @@ const ENTITIES = [
|
|||
latitude: 32.877105,
|
||||
longitude: 117.232185,
|
||||
gps_accuracy: 91,
|
||||
battery: 71,
|
||||
battery: 75,
|
||||
friendly_name: "Home Boy",
|
||||
}),
|
||||
getEntity("light", "bed_light", "on", {
|
||||
|
@ -39,21 +39,53 @@ const ENTITIES = [
|
|||
getEntity("light", "ceiling_lights", "off", {
|
||||
friendly_name: "Ceiling Lights",
|
||||
}),
|
||||
getEntity("sensor", "battery_1", 20, {
|
||||
device_class: "battery",
|
||||
friendly_name: "Battery 1",
|
||||
unit_of_measurement: "%",
|
||||
}),
|
||||
getEntity("sensor", "battery_2", 35, {
|
||||
device_class: "battery",
|
||||
friendly_name: "Battery 2",
|
||||
unit_of_measurement: "%",
|
||||
}),
|
||||
getEntity("sensor", "battery_3", 40, {
|
||||
device_class: "battery",
|
||||
friendly_name: "Battery 3",
|
||||
unit_of_measurement: "%",
|
||||
}),
|
||||
getEntity("sensor", "battery_4", 80, {
|
||||
device_class: "battery",
|
||||
friendly_name: "Battery 4",
|
||||
unit_of_measurement: "%",
|
||||
}),
|
||||
getEntity("input_number", "min_battery_level", 30, {
|
||||
mode: "slider",
|
||||
step: 10,
|
||||
min: 0,
|
||||
max: 100,
|
||||
icon: "mdi:battery-alert-variant",
|
||||
friendly_name: "Minimum Battery Level",
|
||||
unit_of_measurement: "%",
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: "Unfiltered controller",
|
||||
heading: "Unfiltered entities",
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
- light.bed_light
|
||||
- light.ceiling_lights
|
||||
- light.kitchen_lights
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
- light.bed_light
|
||||
- light.ceiling_lights
|
||||
- light.kitchen_lights
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Filtered entities card",
|
||||
heading: "On and home entities",
|
||||
config: `
|
||||
- type: entity-filter
|
||||
entities:
|
||||
|
@ -63,9 +95,28 @@ const CONFIGS = [
|
|||
- light.bed_light
|
||||
- light.ceiling_lights
|
||||
- light.kitchen_lights
|
||||
state_filter:
|
||||
- "on"
|
||||
- home
|
||||
conditions:
|
||||
- condition: state
|
||||
state:
|
||||
- "on"
|
||||
- home
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Same state as Bed Light",
|
||||
config: `
|
||||
- type: entity-filter
|
||||
entities:
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
- light.bed_light
|
||||
- light.ceiling_lights
|
||||
- light.kitchen_lights
|
||||
conditions:
|
||||
- condition: state
|
||||
state:
|
||||
- light.bed_light
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -79,9 +130,11 @@ const CONFIGS = [
|
|||
- light.bed_light
|
||||
- light.ceiling_lights
|
||||
- light.kitchen_lights
|
||||
state_filter:
|
||||
- "on"
|
||||
- not_home
|
||||
conditions:
|
||||
- condition: state
|
||||
state:
|
||||
- "on"
|
||||
- home
|
||||
card:
|
||||
type: entities
|
||||
title: Custom Title
|
||||
|
@ -99,15 +152,101 @@ const CONFIGS = [
|
|||
- light.bed_light
|
||||
- light.ceiling_lights
|
||||
- light.kitchen_lights
|
||||
state_filter:
|
||||
- "on"
|
||||
- not_home
|
||||
conditions:
|
||||
- condition: state
|
||||
state:
|
||||
- "on"
|
||||
- home
|
||||
card:
|
||||
type: glance
|
||||
show_state: true
|
||||
title: Custom Title
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading:
|
||||
"Filtered entities by battery attribute (< '30') using state filter",
|
||||
config: `
|
||||
- type: entity-filter
|
||||
entities:
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
state_filter:
|
||||
- operator: <
|
||||
attribute: battery
|
||||
value: "30"
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Unfiltered number entities",
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
- input_number.min_battery_level
|
||||
- sensor.battery_1
|
||||
- sensor.battery_3
|
||||
- sensor.battery_2
|
||||
- sensor.battery_4
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Battery lower than 50%",
|
||||
config: `
|
||||
- type: entity-filter
|
||||
entities:
|
||||
- sensor.battery_1
|
||||
- sensor.battery_3
|
||||
- sensor.battery_2
|
||||
- sensor.battery_4
|
||||
conditions:
|
||||
- condition: numeric_state
|
||||
below: 50
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Battery lower than min battery level",
|
||||
config: `
|
||||
- type: entity-filter
|
||||
entities:
|
||||
- sensor.battery_1
|
||||
- sensor.battery_3
|
||||
- sensor.battery_2
|
||||
- sensor.battery_4
|
||||
conditions:
|
||||
- condition: numeric_state
|
||||
below: input_number.min_battery_level
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Battery between min battery level and 70%",
|
||||
config: `
|
||||
- type: entity-filter
|
||||
entities:
|
||||
- sensor.battery_1
|
||||
- sensor.battery_3
|
||||
- sensor.battery_2
|
||||
- sensor.battery_4
|
||||
conditions:
|
||||
- condition: numeric_state
|
||||
above: input_number.min_battery_level
|
||||
below: 70
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Error: Entities must be specified",
|
||||
config: `
|
||||
- type: entity-filter
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Error: Incorrect filter config",
|
||||
config: `
|
||||
- type: entity-filter
|
||||
entities:
|
||||
- sensor.gas_station_lowest_price
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-lovelace-entity-filter-card")
|
||||
|
|
|
@ -46,7 +46,9 @@ const ENTITIES = [
|
|||
friendly_name: "Sensibo purifier",
|
||||
fan_modes: ["low", "high"],
|
||||
fan_mode: "low",
|
||||
supported_features: 9,
|
||||
swing_modes: ["on", "off", "both", "vertical", "horizontal"],
|
||||
swing_mode: "vertical",
|
||||
supported_features: 41,
|
||||
}),
|
||||
getEntity("climate", "unavailable", "unavailable", {
|
||||
supported_features: 43,
|
||||
|
@ -85,6 +87,14 @@ const CONFIGS = [
|
|||
fan_modes:
|
||||
- low
|
||||
- high
|
||||
- type: climate-swing-modes
|
||||
style: icons
|
||||
swing_modes:
|
||||
- 'on'
|
||||
- 'off'
|
||||
- 'both'
|
||||
- 'vertical'
|
||||
- 'horizontal'
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -31,6 +31,7 @@ const createConfigEntry = (
|
|||
supports_options: false,
|
||||
supports_remove_device: false,
|
||||
supports_unload: true,
|
||||
supports_reconfigure: true,
|
||||
disabled_by: null,
|
||||
pref_disable_new_entities: false,
|
||||
pref_disable_polling: false,
|
||||
|
|
|
@ -1263,6 +1263,7 @@ class HassioAddonInfo extends LitElement {
|
|||
.card-actions {
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
direction: var(--direction);
|
||||
}
|
||||
.changelog {
|
||||
display: contents;
|
||||
|
|
|
@ -154,12 +154,16 @@ class HassioHardwareDialog extends LitElement {
|
|||
ha-icon-button {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
inset-inline-end: 16px;
|
||||
inset-inline-start: initial;
|
||||
top: 10px;
|
||||
text-decoration: none;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
h2 {
|
||||
margin: 18px 42px 0 18px;
|
||||
margin-inline-start: 18px;
|
||||
margin-inline-end: 42px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiDelete, mdiDeleteOff } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
|
@ -27,6 +25,8 @@ import type { HomeAssistant } from "../../../../src/types";
|
|||
import { HassioRepositoryDialogParams } from "./show-dialog-repositories";
|
||||
import type { HaTextField } from "../../../../src/components/ha-textfield";
|
||||
import "../../../../src/components/ha-textfield";
|
||||
import "../../../../src/components/ha-list-new";
|
||||
import "../../../../src/components/ha-list-item-new";
|
||||
|
||||
@customElement("dialog-hassio-repositories")
|
||||
class HassioRepositoriesDialog extends LitElement {
|
||||
|
@ -106,44 +106,46 @@ class HassioRepositoriesDialog extends LitElement {
|
|||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
<div class="form">
|
||||
${repositories.length
|
||||
? repositories.map(
|
||||
(repo) => html`
|
||||
<paper-item class="option">
|
||||
<paper-item-body three-line>
|
||||
<div>${repo.name}</div>
|
||||
<div secondary>${repo.maintainer}</div>
|
||||
<div secondary>${repo.url}</div>
|
||||
</paper-item-body>
|
||||
<div class="delete">
|
||||
<ha-icon-button
|
||||
.label=${this._dialogParams!.supervisor.localize(
|
||||
"dialog.repositories.remove"
|
||||
)}
|
||||
.disabled=${usedRepositories.includes(repo.slug)}
|
||||
.slug=${repo.slug}
|
||||
.path=${usedRepositories.includes(repo.slug)
|
||||
? mdiDeleteOff
|
||||
: mdiDelete}
|
||||
@click=${this._removeRepository}
|
||||
>
|
||||
</ha-icon-button>
|
||||
<simple-tooltip
|
||||
animation-delay="0"
|
||||
position="bottom"
|
||||
offset="1"
|
||||
>
|
||||
${this._dialogParams!.supervisor.localize(
|
||||
usedRepositories.includes(repo.slug)
|
||||
? "dialog.repositories.used"
|
||||
: "dialog.repositories.remove"
|
||||
)}
|
||||
</simple-tooltip>
|
||||
</div>
|
||||
</paper-item>
|
||||
`
|
||||
)
|
||||
: html`<paper-item> No repositories </paper-item>`}
|
||||
<ha-list-new>
|
||||
${repositories.length
|
||||
? repositories.map(
|
||||
(repo) => html`
|
||||
<ha-list-item-new class="option">
|
||||
${repo.name}
|
||||
<div slot="supporting-text">
|
||||
<div>${repo.maintainer}</div>
|
||||
<div>${repo.url}</div>
|
||||
</div>
|
||||
<div class="delete" slot="end">
|
||||
<ha-icon-button
|
||||
.label=${this._dialogParams!.supervisor.localize(
|
||||
"dialog.repositories.remove"
|
||||
)}
|
||||
.disabled=${usedRepositories.includes(repo.slug)}
|
||||
.slug=${repo.slug}
|
||||
.path=${usedRepositories.includes(repo.slug)
|
||||
? mdiDeleteOff
|
||||
: mdiDelete}
|
||||
@click=${this._removeRepository}
|
||||
>
|
||||
</ha-icon-button>
|
||||
<simple-tooltip
|
||||
animation-delay="0"
|
||||
position="bottom"
|
||||
offset="1"
|
||||
>
|
||||
${this._dialogParams!.supervisor.localize(
|
||||
usedRepositories.includes(repo.slug)
|
||||
? "dialog.repositories.used"
|
||||
: "dialog.repositories.remove"
|
||||
)}
|
||||
</simple-tooltip>
|
||||
</div>
|
||||
</ha-list-item-new>
|
||||
`
|
||||
)
|
||||
: html`<ha-list-item-new> No repositories </ha-list-item-new>`}
|
||||
</ha-list-new>
|
||||
<div class="layout horizontal bottom">
|
||||
<ha-textfield
|
||||
class="flex-auto"
|
||||
|
@ -206,6 +208,9 @@ class HassioRepositoriesDialog extends LitElement {
|
|||
div.delete ha-icon-button {
|
||||
color: var(--error-color);
|
||||
}
|
||||
ha-list-item-new {
|
||||
position: relative;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
|
102
package.json
102
package.json
|
@ -25,15 +25,15 @@
|
|||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.23.9",
|
||||
"@braintree/sanitize-url": "7.0.0",
|
||||
"@codemirror/autocomplete": "6.12.0",
|
||||
"@babel/runtime": "7.24.1",
|
||||
"@braintree/sanitize-url": "7.0.1",
|
||||
"@codemirror/autocomplete": "6.15.0",
|
||||
"@codemirror/commands": "6.3.3",
|
||||
"@codemirror/language": "6.10.1",
|
||||
"@codemirror/legacy-modes": "6.3.3",
|
||||
"@codemirror/search": "6.5.6",
|
||||
"@codemirror/state": "6.4.0",
|
||||
"@codemirror/view": "6.24.0",
|
||||
"@codemirror/state": "6.4.1",
|
||||
"@codemirror/view": "6.26.0",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "6.12.2",
|
||||
"@formatjs/intl-displaynames": "6.6.6",
|
||||
|
@ -43,18 +43,18 @@
|
|||
"@formatjs/intl-numberformat": "8.10.0",
|
||||
"@formatjs/intl-pluralrules": "5.2.12",
|
||||
"@formatjs/intl-relativetimeformat": "11.2.12",
|
||||
"@fullcalendar/core": "6.1.10",
|
||||
"@fullcalendar/daygrid": "6.1.10",
|
||||
"@fullcalendar/interaction": "6.1.10",
|
||||
"@fullcalendar/list": "6.1.10",
|
||||
"@fullcalendar/luxon3": "6.1.10",
|
||||
"@fullcalendar/timegrid": "6.1.10",
|
||||
"@fullcalendar/core": "6.1.11",
|
||||
"@fullcalendar/daygrid": "6.1.11",
|
||||
"@fullcalendar/interaction": "6.1.11",
|
||||
"@fullcalendar/list": "6.1.11",
|
||||
"@fullcalendar/luxon3": "6.1.11",
|
||||
"@fullcalendar/timegrid": "6.1.11",
|
||||
"@lezer/highlight": "1.2.0",
|
||||
"@lit-labs/context": "0.4.1",
|
||||
"@lit-labs/motion": "1.0.7",
|
||||
"@lit-labs/observers": "2.0.2",
|
||||
"@lit-labs/virtualizer": "2.0.12",
|
||||
"@lrnwebcomponents/simple-tooltip": "patch:@lrnwebcomponents/simple-tooltip@npm%3A8.0.0#~/.yarn/patches/@lrnwebcomponents-simple-tooltip-npm-8.0.0-77591f2e0c.patch",
|
||||
"@lrnwebcomponents/simple-tooltip": "8.0.2",
|
||||
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/mwc-base": "0.27.0",
|
||||
|
@ -72,6 +72,7 @@
|
|||
"@material/mwc-radio": "0.27.0",
|
||||
"@material/mwc-ripple": "0.27.0",
|
||||
"@material/mwc-select": "0.27.0",
|
||||
"@material/mwc-snackbar": "0.27.0",
|
||||
"@material/mwc-switch": "0.27.0",
|
||||
"@material/mwc-tab": "0.27.0",
|
||||
"@material/mwc-tab-bar": "0.27.0",
|
||||
|
@ -80,17 +81,16 @@
|
|||
"@material/mwc-top-app-bar": "0.27.0",
|
||||
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/web": "=1.2.0",
|
||||
"@material/web": "=1.3.0",
|
||||
"@mdi/js": "7.4.47",
|
||||
"@mdi/svg": "7.4.47",
|
||||
"@polymer/paper-item": "3.0.1",
|
||||
"@polymer/paper-listbox": "3.0.1",
|
||||
"@polymer/paper-tabs": "3.1.0",
|
||||
"@polymer/paper-toast": "3.0.1",
|
||||
"@polymer/polymer": "3.5.1",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@vaadin/combo-box": "24.3.6",
|
||||
"@vaadin/vaadin-themable-mixin": "24.3.6",
|
||||
"@vaadin/combo-box": "24.3.9",
|
||||
"@vaadin/vaadin-themable-mixin": "24.3.9",
|
||||
"@vibrant/color": "3.2.1-alpha.1",
|
||||
"@vibrant/core": "3.2.1-alpha.1",
|
||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||
|
@ -98,18 +98,19 @@
|
|||
"@webcomponents/scoped-custom-element-registry": "0.0.9",
|
||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||
"app-datepicker": "5.1.1",
|
||||
"chart.js": "4.4.1",
|
||||
"chart.js": "4.4.2",
|
||||
"color-name": "2.0.0",
|
||||
"comlink": "4.4.1",
|
||||
"core-js": "3.35.1",
|
||||
"core-js": "3.36.1",
|
||||
"cropperjs": "1.6.1",
|
||||
"date-fns": "2.30.0",
|
||||
"date-fns-tz": "2.0.0",
|
||||
"date-fns-tz": "2.0.1",
|
||||
"deep-clone-simple": "1.1.1",
|
||||
"deep-freeze": "0.0.1",
|
||||
"element-internals-polyfill": "1.3.10",
|
||||
"fuse.js": "7.0.0",
|
||||
"google-timezones-json": "1.2.0",
|
||||
"hls.js": "1.5.4",
|
||||
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
|
||||
"home-assistant-js-websocket": "9.1.0",
|
||||
"idb-keyval": "6.2.1",
|
||||
"intl-messageformat": "10.5.11",
|
||||
|
@ -118,7 +119,7 @@
|
|||
"leaflet-draw": "1.0.4",
|
||||
"lit": "2.8.0",
|
||||
"luxon": "3.4.4",
|
||||
"marked": "12.0.0",
|
||||
"marked": "12.0.1",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
"proxy-polyfill": "0.3.2",
|
||||
|
@ -129,7 +130,7 @@
|
|||
"rrule": "2.8.1",
|
||||
"sortablejs": "1.15.2",
|
||||
"stacktrace-js": "2.0.2",
|
||||
"superstruct": "1.0.3",
|
||||
"superstruct": "1.0.4",
|
||||
"tinykeys": "2.1.0",
|
||||
"tsparticles-engine": "2.12.0",
|
||||
"tsparticles-preset-links": "2.12.0",
|
||||
|
@ -146,20 +147,20 @@
|
|||
"workbox-precaching": "7.0.0",
|
||||
"workbox-routing": "7.0.0",
|
||||
"workbox-strategies": "7.0.0",
|
||||
"xss": "1.0.14"
|
||||
"xss": "1.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.23.9",
|
||||
"@babel/helper-define-polyfill-provider": "0.5.0",
|
||||
"@babel/plugin-proposal-decorators": "7.23.9",
|
||||
"@babel/plugin-transform-runtime": "7.23.9",
|
||||
"@babel/preset-env": "7.23.9",
|
||||
"@babel/preset-typescript": "7.23.3",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.9.2",
|
||||
"@babel/core": "7.24.3",
|
||||
"@babel/helper-define-polyfill-provider": "0.6.1",
|
||||
"@babel/plugin-proposal-decorators": "7.24.1",
|
||||
"@babel/plugin-transform-runtime": "7.24.3",
|
||||
"@babel/preset-env": "7.24.3",
|
||||
"@babel/preset-typescript": "7.24.1",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.12.1",
|
||||
"@koa/cors": "5.0.0",
|
||||
"@lokalise/node-api": "12.1.0",
|
||||
"@octokit/auth-oauth-device": "6.0.1",
|
||||
"@octokit/plugin-retry": "6.0.1",
|
||||
"@lokalise/node-api": "12.3.0",
|
||||
"@octokit/auth-oauth-device": "7.0.1",
|
||||
"@octokit/plugin-retry": "7.0.3",
|
||||
"@octokit/rest": "20.0.2",
|
||||
"@open-wc/dev-server-hmr": "0.1.4",
|
||||
"@rollup/plugin-babel": "6.0.4",
|
||||
|
@ -169,7 +170,8 @@
|
|||
"@rollup/plugin-replace": "5.0.5",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.13",
|
||||
"@types/chromecast-caf-sender": "1.0.8",
|
||||
"@types/chromecast-caf-sender": "1.0.9",
|
||||
"@types/color-name": "1.1.3",
|
||||
"@types/glob": "8.1.0",
|
||||
"@types/html-minifier-terser": "7.0.2",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
|
@ -179,21 +181,21 @@
|
|||
"@types/mocha": "10.0.6",
|
||||
"@types/qrcode": "1.5.5",
|
||||
"@types/serve-handler": "6.1.4",
|
||||
"@types/sortablejs": "1.15.7",
|
||||
"@types/sortablejs": "1.15.8",
|
||||
"@types/tar": "6.1.11",
|
||||
"@types/ua-parser-js": "0.7.39",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "7.0.1",
|
||||
"@typescript-eslint/parser": "7.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "7.3.1",
|
||||
"@typescript-eslint/parser": "7.3.1",
|
||||
"@web/dev-server": "0.1.38",
|
||||
"@web/dev-server-rollup": "0.4.1",
|
||||
"babel-loader": "9.1.3",
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"chai": "5.1.0",
|
||||
"del": "7.1.0",
|
||||
"eslint": "8.56.0",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-airbnb-typescript": "17.1.0",
|
||||
"eslint-config-airbnb-typescript": "18.0.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-import-resolver-webpack": "0.13.8",
|
||||
"eslint-plugin-disable": "2.0.3",
|
||||
|
@ -207,22 +209,22 @@
|
|||
"glob": "10.3.10",
|
||||
"gulp": "4.0.2",
|
||||
"gulp-flatmap": "1.0.2",
|
||||
"gulp-json-transform": "0.4.8",
|
||||
"gulp-merge-json": "2.1.2",
|
||||
"gulp-json-transform": "0.5.0",
|
||||
"gulp-merge-json": "2.2.1",
|
||||
"gulp-rename": "2.0.0",
|
||||
"gulp-zopfli-green": "6.0.1",
|
||||
"html-minifier-terser": "7.2.0",
|
||||
"husky": "9.0.10",
|
||||
"husky": "9.0.11",
|
||||
"instant-mocha": "1.5.2",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "15.2.2",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.template": "4.5.0",
|
||||
"magic-string": "0.30.7",
|
||||
"magic-string": "0.30.8",
|
||||
"map-stream": "0.0.7",
|
||||
"mocha": "10.3.0",
|
||||
"object-hash": "3.0.0",
|
||||
"open": "10.0.3",
|
||||
"open": "10.1.0",
|
||||
"pinst": "3.0.0",
|
||||
"prettier": "3.2.5",
|
||||
"rollup": "2.79.1",
|
||||
|
@ -235,17 +237,17 @@
|
|||
"systemjs": "6.14.3",
|
||||
"tar": "6.2.0",
|
||||
"terser-webpack-plugin": "5.3.10",
|
||||
"transform-async-modules-webpack-plugin": "1.0.2",
|
||||
"transform-async-modules-webpack-plugin": "1.0.3",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.3.3",
|
||||
"typescript": "5.4.3",
|
||||
"vinyl-buffer": "1.0.1",
|
||||
"vinyl-source-stream": "2.0.0",
|
||||
"webpack": "5.90.1",
|
||||
"webpack": "5.91.0",
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-dev-server": "4.15.1",
|
||||
"webpack-dev-server": "5.0.4",
|
||||
"webpack-manifest-plugin": "5.0.0",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
"webpackbar": "6.0.0",
|
||||
"webpackbar": "6.0.1",
|
||||
"workbox-build": "7.0.0"
|
||||
},
|
||||
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
||||
|
@ -258,5 +260,5 @@
|
|||
"sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch",
|
||||
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||
},
|
||||
"packageManager": "yarn@4.1.0"
|
||||
"packageManager": "yarn@4.1.1"
|
||||
}
|
||||
|
|
|
@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
|||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20240207.0"
|
||||
version = "20240228.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{name = "The Home Assistant Authors", email = "hello@home-assistant.io"}
|
||||
]
|
||||
requires-python = ">=3.10.0"
|
||||
requires-python = ">=3.11.0"
|
||||
|
||||
[project.urls]
|
||||
"Homepage" = "https://github.com/home-assistant/frontend"
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { theme2hex } from "./convert-color";
|
||||
|
||||
export const COLORS = [
|
||||
"#44739e",
|
||||
"#984ea3",
|
||||
|
@ -65,10 +67,10 @@ export function getColorByIndex(index: number) {
|
|||
export function getGraphColorByIndex(
|
||||
index: number,
|
||||
style: CSSStyleDeclaration
|
||||
) {
|
||||
): string {
|
||||
// The CSS vars for the colors use range 1..n, so we need to adjust the index from the internal 0..n color index range.
|
||||
return (
|
||||
const themeColor =
|
||||
style.getPropertyValue(`--graph-color-${index + 1}`) ||
|
||||
getColorByIndex(index)
|
||||
);
|
||||
getColorByIndex(index);
|
||||
return theme2hex(themeColor);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import colors from "color-name";
|
||||
import { expandHex } from "./hex";
|
||||
|
||||
const rgb_hex = (component: number): string => {
|
||||
|
@ -126,3 +127,18 @@ export const rgb2hs = (rgb: [number, number, number]): [number, number] =>
|
|||
|
||||
export const hs2rgb = (hs: [number, number]): [number, number, number] =>
|
||||
hsv2rgb([hs[0], hs[1], 255]);
|
||||
|
||||
export function theme2hex(themeColor: string): string {
|
||||
if (themeColor.startsWith("#")) {
|
||||
return themeColor;
|
||||
}
|
||||
|
||||
const rgbFromColorName = colors[themeColor];
|
||||
if (!rgbFromColorName) {
|
||||
// We have a named color, and there's nothing in the table,
|
||||
// so nothing further we can do with it.
|
||||
// Compare/border/background color will all be the same.
|
||||
return themeColor;
|
||||
}
|
||||
return rgb2hex(rgbFromColorName);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { ensureArray } from "../array/ensure-array";
|
||||
import { isComponentLoaded } from "./is_component_loaded";
|
||||
|
||||
export const canShowPage = (hass: HomeAssistant, page: PageNavigation) =>
|
||||
(isCore(page) || isLoadedIntegration(hass, page)) &&
|
||||
!hideAdvancedPage(hass, page);
|
||||
!hideAdvancedPage(hass, page) &&
|
||||
isNotLoadedIntegration(hass, page);
|
||||
|
||||
const isLoadedIntegration = (hass: HomeAssistant, page: PageNavigation) =>
|
||||
page.component
|
||||
? isComponentLoaded(hass, page.component)
|
||||
: page.components
|
||||
? page.components.some((integration) =>
|
||||
isComponentLoaded(hass, integration)
|
||||
)
|
||||
: true;
|
||||
!page.component ||
|
||||
ensureArray(page.component).some((integration) =>
|
||||
isComponentLoaded(hass, integration)
|
||||
);
|
||||
|
||||
const isNotLoadedIntegration = (hass: HomeAssistant, page: PageNavigation) =>
|
||||
!page.not_component ||
|
||||
!ensureArray(page.not_component).some((integration) =>
|
||||
isComponentLoaded(hass, integration)
|
||||
);
|
||||
|
||||
const isCore = (page: PageNavigation) => page.core;
|
||||
const isAdvancedPage = (page: PageNavigation) => page.advancedOnly;
|
||||
const userWantsAdvanced = (hass: HomeAssistant) => hass.userData?.showAdvanced;
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
import { MAIN_WINDOW_NAME } from "../../data/main_window";
|
||||
|
||||
export const mainWindow =
|
||||
window.name === MAIN_WINDOW_NAME
|
||||
? window
|
||||
: parent.name === MAIN_WINDOW_NAME
|
||||
? parent
|
||||
: top!;
|
||||
export const mainWindow = (() => {
|
||||
try {
|
||||
return window.name === MAIN_WINDOW_NAME
|
||||
? window
|
||||
: parent.name === MAIN_WINDOW_NAME
|
||||
? parent
|
||||
: top!;
|
||||
} catch {
|
||||
return window;
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -20,14 +20,14 @@ function findNestedItem(
|
|||
}, obj);
|
||||
}
|
||||
|
||||
export function nestedArrayMove<T>(
|
||||
obj: T | T[],
|
||||
export function nestedArrayMove<A>(
|
||||
obj: A,
|
||||
oldIndex: number,
|
||||
newIndex: number,
|
||||
oldPath?: ItemPath,
|
||||
newPath?: ItemPath
|
||||
): T | T[] {
|
||||
const newObj = Array.isArray(obj) ? [...obj] : { ...obj };
|
||||
): A {
|
||||
const newObj = (Array.isArray(obj) ? [...obj] : { ...obj }) as A;
|
||||
const from = oldPath ? findNestedItem(newObj, oldPath) : newObj;
|
||||
const to = newPath ? findNestedItem(newObj, newPath, true) : newObj;
|
||||
|
||||
|
|
|
@ -75,6 +75,8 @@ export class HaChartBase extends LitElement {
|
|||
|
||||
private _paddingYAxisInternal = 0;
|
||||
|
||||
private _datasetOrder: number[] = [];
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._releaseCanvas();
|
||||
|
@ -165,7 +167,17 @@ export class HaChartBase extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
// put the legend labels in sorted order if provided
|
||||
if (changedProps.has("data")) {
|
||||
this._datasetOrder = this.data.datasets.map((_, index) => index);
|
||||
if (this.data?.datasets.some((dataset) => dataset.order)) {
|
||||
this._datasetOrder.sort(
|
||||
(a, b) =>
|
||||
(this.data.datasets[a].order || 0) -
|
||||
(this.data.datasets[b].order || 0)
|
||||
);
|
||||
}
|
||||
|
||||
if (this.externalHidden) {
|
||||
this._hiddenDatasets = new Set();
|
||||
if (this.data?.datasets) {
|
||||
|
@ -205,8 +217,9 @@ export class HaChartBase extends LitElement {
|
|||
${this.options?.plugins?.legend?.display === true
|
||||
? html`<div class="chartLegend">
|
||||
<ul>
|
||||
${this.data.datasets.map((dataset, index) =>
|
||||
this.extraData?.[index]?.show_legend === false
|
||||
${this._datasetOrder.map((index) => {
|
||||
const dataset = this.data.datasets[index];
|
||||
return this.extraData?.[index]?.show_legend === false
|
||||
? nothing
|
||||
: html`<li
|
||||
.datasetIndex=${index}
|
||||
|
@ -228,8 +241,8 @@ export class HaChartBase extends LitElement {
|
|||
${this.extraData?.[index]?.legend_label ??
|
||||
dataset.label}
|
||||
</div>
|
||||
</li>`
|
||||
)}
|
||||
</li>`;
|
||||
})}
|
||||
</ul>
|
||||
</div>`
|
||||
: ""}
|
||||
|
|
|
@ -111,7 +111,7 @@ export class StateHistoryChartLine extends LitElement {
|
|||
config: this.hass.config,
|
||||
},
|
||||
},
|
||||
suggestedMin: this.startTime,
|
||||
min: this.startTime,
|
||||
suggestedMax: this.endTime,
|
||||
ticks: {
|
||||
maxRotation: 0,
|
||||
|
|
|
@ -114,7 +114,7 @@ export class StateHistoryChartTimeline extends LitElement {
|
|||
config: this.hass.config,
|
||||
},
|
||||
},
|
||||
suggestedMin: this.startTime,
|
||||
min: this.startTime,
|
||||
suggestedMax: this.endTime,
|
||||
ticks: {
|
||||
autoSkip: true,
|
||||
|
|
|
@ -233,16 +233,32 @@ export class StateHistoryCharts extends LitElement {
|
|||
new Date().getTime() - 60 * 60 * this.hoursToShow * 1000
|
||||
);
|
||||
} else {
|
||||
this._computedStartTime = new Date(
|
||||
(this.historyData?.timeline ?? []).reduce(
|
||||
(minTime, stateInfo) =>
|
||||
Math.min(
|
||||
minTime,
|
||||
new Date(stateInfo.data[0].last_changed).getTime()
|
||||
),
|
||||
new Date().getTime()
|
||||
)
|
||||
let minTimeAll = (this.historyData?.timeline ?? []).reduce(
|
||||
(minTime, stateInfo) =>
|
||||
Math.min(
|
||||
minTime,
|
||||
new Date(stateInfo.data[0].last_changed).getTime()
|
||||
),
|
||||
new Date().getTime()
|
||||
);
|
||||
|
||||
minTimeAll = (this.historyData?.line ?? []).reduce(
|
||||
(minTimeLine, line) =>
|
||||
Math.min(
|
||||
minTimeLine,
|
||||
line.data.reduce(
|
||||
(minTimeData, data) =>
|
||||
Math.min(
|
||||
minTimeData,
|
||||
new Date(data.states[0].last_changed).getTime()
|
||||
),
|
||||
minTimeLine
|
||||
)
|
||||
),
|
||||
minTimeAll
|
||||
);
|
||||
|
||||
this._computedStartTime = new Date(minTimeAll);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import { customElement, property, state, query } from "lit/decorators";
|
|||
import memoizeOne from "memoize-one";
|
||||
import { getGraphColorByIndex } from "../../common/color/colors";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import {
|
||||
formatNumber,
|
||||
numberFormatToLocale,
|
||||
|
@ -25,6 +26,7 @@ import {
|
|||
getDisplayUnit,
|
||||
getStatisticLabel,
|
||||
getStatisticMetadata,
|
||||
isExternalStatistic,
|
||||
Statistics,
|
||||
statisticsHaveType,
|
||||
StatisticsMetaData,
|
||||
|
@ -79,6 +81,8 @@ export class StatisticsChart extends LitElement {
|
|||
|
||||
@property({ type: Boolean }) public isLoadingData = false;
|
||||
|
||||
@property({ type: Boolean }) public clickForMoreInfo = true;
|
||||
|
||||
@property() public period?: string;
|
||||
|
||||
@state() private _chartData: ChartData = { datasets: [] };
|
||||
|
@ -273,6 +277,33 @@ export class StatisticsChart extends LitElement {
|
|||
},
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(this.hass.locale),
|
||||
onClick: (e: any) => {
|
||||
if (
|
||||
!this.clickForMoreInfo ||
|
||||
!(e.native instanceof MouseEvent) ||
|
||||
(e.native instanceof PointerEvent && e.native.pointerType !== "mouse")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chart = e.chart;
|
||||
|
||||
const points = chart.getElementsAtEventForMode(
|
||||
e,
|
||||
"nearest",
|
||||
{ intersect: true },
|
||||
true
|
||||
);
|
||||
|
||||
if (points.length) {
|
||||
const firstPoint = points[0];
|
||||
const statisticId = this._statisticIds[firstPoint.datasetIndex];
|
||||
if (!isExternalStatistic(statisticId)) {
|
||||
fireEvent(this, "hass-more-info", { entityId: statisticId });
|
||||
chart.canvas.dispatchEvent(new Event("mouseout")); // to hide tooltip
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -205,7 +205,9 @@ export class TimelineController extends BarController {
|
|||
|
||||
const y = vScale.getPixelForValue(this.index);
|
||||
|
||||
const xStart = iScale.getPixelForValue(data.start.getTime());
|
||||
const xStart = iScale.getPixelForValue(
|
||||
Math.max(iScale.min, data.start.getTime())
|
||||
);
|
||||
const xEnd = iScale.getPixelForValue(data.end.getTime());
|
||||
const width = xEnd - xStart;
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ export class TimeLineScale extends TimeScale {
|
|||
max = isFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit);
|
||||
|
||||
// Make sure that max is strictly higher than min (required by the lookup table)
|
||||
this.min = Math.min(min, max - 1);
|
||||
this.max = Math.max(min + 1, max);
|
||||
this.min = adapter.parse(options.min, this) ?? Math.min(min, max - 1);
|
||||
this.max = adapter.parse(options.max, this) ?? Math.max(min + 1, max);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,8 @@ class HaDataTableIcon extends LitElement {
|
|||
div {
|
||||
position: absolute;
|
||||
right: 28px;
|
||||
inset-inline-end: 28px;
|
||||
inset-inline-start: initial;
|
||||
z-index: 1002;
|
||||
outline: none;
|
||||
font-size: 10px;
|
||||
|
|
|
@ -32,7 +32,9 @@ export class StateBadge extends LitElement {
|
|||
|
||||
@property() public overrideImage?: string;
|
||||
|
||||
@property({ type: Boolean }) public stateColor = false;
|
||||
// Cannot be a boolean attribute because undefined is treated different than
|
||||
// false. When it is undefined, state is still colored for light entities.
|
||||
@property({ attribute: false }) public stateColor?: boolean;
|
||||
|
||||
@property() public color?: string;
|
||||
|
||||
|
@ -70,7 +72,7 @@ export class StateBadge extends LitElement {
|
|||
const domain = this.stateObj
|
||||
? computeStateDomain(this.stateObj)
|
||||
: undefined;
|
||||
return this.stateColor || (domain === "light" && this.stateColor !== false);
|
||||
return this.stateColor ?? domain === "light";
|
||||
}
|
||||
|
||||
protected render() {
|
||||
|
|
|
@ -156,6 +156,7 @@ class HaClimateState extends LitElement {
|
|||
|
||||
.current {
|
||||
color: var(--secondary-text-color);
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.state-label {
|
||||
|
|
|
@ -127,9 +127,11 @@ export class HaControlButton extends LitElement {
|
|||
opacity 180ms ease-in-out;
|
||||
opacity: var(--control-button-background-opacity);
|
||||
}
|
||||
.button ::slotted(*) {
|
||||
.button {
|
||||
transition: color 180ms ease-in-out;
|
||||
color: var(--control-button-icon-color);
|
||||
}
|
||||
.button ::slotted(*) {
|
||||
pointer-events: none;
|
||||
}
|
||||
.button:disabled {
|
||||
|
|
|
@ -273,9 +273,13 @@ export class HaControlNumberButton extends LitElement {
|
|||
}
|
||||
.button.minus {
|
||||
left: 0;
|
||||
inset-inline-start: 0;
|
||||
inset-inline-end: initial;
|
||||
}
|
||||
.button.plus {
|
||||
right: 0;
|
||||
inset-inline-start: initial;
|
||||
inset-inline-end: 0;
|
||||
}
|
||||
.unit {
|
||||
white-space: pre;
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { FormfieldBase } from "@material/mwc-formfield/mwc-formfield-base";
|
||||
import { styles } from "@material/mwc-formfield/mwc-formfield.css";
|
||||
import { css } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
|
||||
@customElement("ha-formfield")
|
||||
export class HaFormfield extends FormfieldBase {
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
protected _labelClick() {
|
||||
const input = this.input as HTMLInputElement | undefined;
|
||||
if (!input) return;
|
||||
|
@ -44,6 +46,9 @@ export class HaFormfield extends FormfieldBase {
|
|||
padding-inline-start: 4px;
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
:host([disabled]) label {
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -136,6 +136,8 @@ class HaMenuButton extends LitElement {
|
|||
height: 12px;
|
||||
top: 9px;
|
||||
right: 7px;
|
||||
inset-inline-end: 7px;
|
||||
inset-inline-start: initial;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--app-header-background-color);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@ class HaMetric extends LitElement {
|
|||
<ha-settings-row>
|
||||
<span slot="heading"> ${this.heading} </span>
|
||||
<div slot="description" .title=${this.tooltip ?? ""}>
|
||||
<span class="value"> ${roundedValue} % </span>
|
||||
<span class="value">
|
||||
<div>${roundedValue} %</div>
|
||||
</span>
|
||||
<ha-bar
|
||||
class=${classMap({
|
||||
"target-warning": roundedValue > 50,
|
||||
|
@ -70,6 +72,10 @@ class HaMetric extends LitElement {
|
|||
padding-inline-start: initial;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.value > div {
|
||||
direction: ltr;
|
||||
text-align: var(--float-start);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ export const pushSupported =
|
|||
class HaPushNotificationsToggle extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _disabled: boolean = false;
|
||||
@property({ type: Boolean }) public disabled!: boolean;
|
||||
|
||||
@state() private _pushChecked: boolean =
|
||||
"Notification" in window && Notification.permission === "granted";
|
||||
|
@ -27,7 +27,7 @@ class HaPushNotificationsToggle extends LitElement {
|
|||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-switch
|
||||
.disabled=${this._disabled || this._loading}
|
||||
.disabled=${this.disabled || this._loading}
|
||||
.checked=${this._pushChecked}
|
||||
@change=${this._handlePushChange}
|
||||
></ha-switch>
|
||||
|
|
|
@ -186,6 +186,8 @@ class HaQrScanner extends LitElement {
|
|||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
inset-inline-end: 8px;
|
||||
inset-inline-start: initial;
|
||||
background: #727272b2;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
|
|
|
@ -102,7 +102,10 @@ export class HaSelectSelector extends LitElement {
|
|||
${this.label}
|
||||
${options.map(
|
||||
(item: SelectOption) => html`
|
||||
<ha-formfield .label=${item.label}>
|
||||
<ha-formfield
|
||||
.label=${item.label}
|
||||
.disabled=${item.disabled || this.disabled}
|
||||
>
|
||||
<ha-radio
|
||||
.checked=${item.value === this.value}
|
||||
.value=${item.value}
|
||||
|
|
|
@ -93,6 +93,8 @@ export class HaServiceControl extends LitElement {
|
|||
|
||||
@property({ type: Boolean, reflect: true }) public hidePicker = false;
|
||||
|
||||
@property({ type: Boolean }) public hideDescription = false;
|
||||
|
||||
@state() private _value!: this["value"];
|
||||
|
||||
@state() private _checkedKeys = new Set();
|
||||
|
@ -373,7 +375,8 @@ export class HaServiceControl extends LitElement {
|
|||
)) ||
|
||||
serviceData?.description;
|
||||
|
||||
return html`${this.hidePicker
|
||||
return html`
|
||||
${this.hidePicker
|
||||
? nothing
|
||||
: html`<ha-service-picker
|
||||
.hass=${this.hass}
|
||||
|
@ -381,29 +384,33 @@ export class HaServiceControl extends LitElement {
|
|||
.disabled=${this.disabled}
|
||||
@value-changed=${this._serviceChanged}
|
||||
></ha-service-picker>`}
|
||||
<div class="description">
|
||||
${description ? html`<p>${description}</p>` : ""}
|
||||
${this._manifest
|
||||
? html` <a
|
||||
href=${this._manifest.is_built_in
|
||||
? documentationUrl(
|
||||
this.hass,
|
||||
`/integrations/${this._manifest.domain}`
|
||||
)
|
||||
: this._manifest.documentation}
|
||||
title=${this.hass.localize(
|
||||
"ui.components.service-control.integration_doc"
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<ha-icon-button
|
||||
.path=${mdiHelpCircle}
|
||||
class="help-icon"
|
||||
></ha-icon-button>
|
||||
</a>`
|
||||
: ""}
|
||||
</div>
|
||||
${this.hideDescription
|
||||
? nothing
|
||||
: html`
|
||||
<div class="description">
|
||||
${description ? html`<p>${description}</p>` : ""}
|
||||
${this._manifest
|
||||
? html` <a
|
||||
href=${this._manifest.is_built_in
|
||||
? documentationUrl(
|
||||
this.hass,
|
||||
`/integrations/${this._manifest.domain}`
|
||||
)
|
||||
: this._manifest.documentation}
|
||||
title=${this.hass.localize(
|
||||
"ui.components.service-control.integration_doc"
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<ha-icon-button
|
||||
.path=${mdiHelpCircle}
|
||||
class="help-icon"
|
||||
></ha-icon-button>
|
||||
</a>`
|
||||
: nothing}
|
||||
</div>
|
||||
`}
|
||||
${serviceData && "target" in serviceData
|
||||
? html`<ha-settings-row .narrow=${this.narrow}>
|
||||
${hasOptional
|
||||
|
@ -517,7 +524,8 @@ export class HaServiceControl extends LitElement {
|
|||
></ha-selector>
|
||||
</ha-settings-row>`
|
||||
: "";
|
||||
})}`;
|
||||
})}
|
||||
`;
|
||||
}
|
||||
|
||||
private _localizeValueCallback = (key: string) => {
|
||||
|
|
|
@ -114,11 +114,14 @@ class HaServicePicker extends LitElement {
|
|||
if (!filter) {
|
||||
return processedServices;
|
||||
}
|
||||
return processedServices.filter(
|
||||
(service) =>
|
||||
service.service.toLowerCase().includes(filter) ||
|
||||
service.name?.toLowerCase().includes(filter)
|
||||
);
|
||||
const split_filter = filter.split(" ");
|
||||
return processedServices.filter((service) => {
|
||||
const lower_service_name = service.name.toLowerCase();
|
||||
const lower_service = service.service.toLowerCase();
|
||||
return split_filter.every(
|
||||
(f) => lower_service_name.includes(f) || lower_service.includes(f)
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -1010,8 +1010,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||
}
|
||||
.profile paper-icon-item {
|
||||
padding-left: 4px;
|
||||
margin-inline-start: 4px;
|
||||
margin-inline-end: auto;
|
||||
padding-inline-start: 4px;
|
||||
padding-inline-end: auto;
|
||||
}
|
||||
.profile .item-text {
|
||||
margin-left: 8px;
|
||||
|
@ -1040,6 +1040,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||
position: absolute;
|
||||
bottom: 14px;
|
||||
left: 26px;
|
||||
inset-inline-start: 26px;
|
||||
inset-inline-end: initial;
|
||||
font-size: 0.65em;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,9 +14,16 @@ declare global {
|
|||
oldPath?: ItemPath;
|
||||
newPath?: ItemPath;
|
||||
};
|
||||
"drag-start": undefined;
|
||||
"drag-end": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export type HaSortableOptions = Omit<
|
||||
SortableInstance.SortableOptions,
|
||||
"onStart" | "onChoose" | "onEnd"
|
||||
>;
|
||||
|
||||
@customElement("ha-sortable")
|
||||
export class HaSortable extends LitElement {
|
||||
private _sortable?: SortableInstance;
|
||||
|
@ -36,14 +43,17 @@ export class HaSortable extends LitElement {
|
|||
@property({ type: String, attribute: "handle-selector" })
|
||||
public handleSelector?: string;
|
||||
|
||||
@property({ type: String, attribute: "group" })
|
||||
public group?: string;
|
||||
|
||||
@property({ type: Number, attribute: "swap-threshold" })
|
||||
public swapThreshold?: number;
|
||||
@property({ type: String })
|
||||
public group?: string | SortableInstance.GroupOptions;
|
||||
|
||||
@property({ type: Boolean, attribute: "invert-swap" })
|
||||
public invertSwap?: boolean;
|
||||
public invertSwap: boolean = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public options?: HaSortableOptions;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public rollback: boolean = true;
|
||||
|
||||
protected updated(changedProperties: PropertyValues<this>) {
|
||||
if (changedProperties.has("disabled")) {
|
||||
|
@ -72,6 +82,9 @@ export class HaSortable extends LitElement {
|
|||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._shouldBeDestroy = false;
|
||||
if (this.hasUpdated) {
|
||||
this._createSortable();
|
||||
}
|
||||
}
|
||||
|
||||
protected createRenderRoot() {
|
||||
|
@ -114,26 +127,20 @@ export class HaSortable extends LitElement {
|
|||
|
||||
const options: SortableInstance.Options = {
|
||||
animation: 150,
|
||||
swapThreshold: 1,
|
||||
...this.options,
|
||||
onChoose: this._handleChoose,
|
||||
onStart: this._handleStart,
|
||||
onEnd: this._handleEnd,
|
||||
};
|
||||
|
||||
if (this.draggableSelector) {
|
||||
options.draggable = this.draggableSelector;
|
||||
}
|
||||
|
||||
if (this.swapThreshold !== undefined) {
|
||||
options.swapThreshold = this.swapThreshold;
|
||||
}
|
||||
if (this.invertSwap !== undefined) {
|
||||
options.invertSwap = this.invertSwap;
|
||||
}
|
||||
if (this.handleSelector) {
|
||||
options.handle = this.handleSelector;
|
||||
}
|
||||
if (this.draggableSelector) {
|
||||
options.draggable = this.draggableSelector;
|
||||
if (this.invertSwap !== undefined) {
|
||||
options.invertSwap = this.invertSwap;
|
||||
}
|
||||
if (this.group) {
|
||||
options.group = this.group;
|
||||
|
@ -143,8 +150,9 @@ export class HaSortable extends LitElement {
|
|||
}
|
||||
|
||||
private _handleEnd = async (evt: SortableEvent) => {
|
||||
fireEvent(this, "drag-end");
|
||||
// put back in original location
|
||||
if ((evt.item as any).placeholder) {
|
||||
if (this.rollback && (evt.item as any).placeholder) {
|
||||
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||
delete (evt.item as any).placeholder;
|
||||
}
|
||||
|
@ -170,7 +178,12 @@ export class HaSortable extends LitElement {
|
|||
});
|
||||
};
|
||||
|
||||
private _handleStart = () => {
|
||||
fireEvent(this, "drag-start");
|
||||
};
|
||||
|
||||
private _handleChoose = (evt: SortableEvent) => {
|
||||
if (!this.rollback) return;
|
||||
(evt.item as any).placeholder = document.createComment("sort-placeholder");
|
||||
evt.item.after((evt.item as any).placeholder);
|
||||
};
|
||||
|
|
|
@ -90,7 +90,7 @@ export class HaTextField extends TextFieldBase {
|
|||
padding-right: var(--text-field-suffix-padding-right, 0px);
|
||||
padding-inline-start: var(--text-field-suffix-padding-left, 12px);
|
||||
padding-inline-end: var(--text-field-suffix-padding-right, 0px);
|
||||
direction: var(--direction);
|
||||
direction: ltr;
|
||||
}
|
||||
.mdc-text-field--with-leading-icon {
|
||||
padding-inline-start: var(--text-field-suffix-padding-left, 0px);
|
||||
|
@ -199,7 +199,6 @@ export class HaTextField extends TextFieldBase {
|
|||
// safari workaround - must be explicit
|
||||
mainWindow.document.dir === "rtl"
|
||||
? css`
|
||||
.mdc-text-field__affix--suffix,
|
||||
.mdc-text-field--with-leading-icon,
|
||||
.mdc-text-field__icon--leading,
|
||||
.mdc-floating-label,
|
||||
|
|
|
@ -1,35 +1,8 @@
|
|||
import "@polymer/paper-toast/paper-toast";
|
||||
import type { PaperToastElement } from "@polymer/paper-toast/paper-toast";
|
||||
import { customElement } from "lit/decorators";
|
||||
import type { Constructor } from "../types";
|
||||
|
||||
const PaperToast = customElements.get(
|
||||
"paper-toast"
|
||||
) as Constructor<PaperToastElement>;
|
||||
import { Snackbar } from "@material/mwc-snackbar/mwc-snackbar";
|
||||
|
||||
@customElement("ha-toast")
|
||||
export class HaToast extends PaperToast {
|
||||
private _resizeListener?: (obj: { matches: boolean }) => unknown;
|
||||
|
||||
private _mediaq?: MediaQueryList;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
if (!this._resizeListener) {
|
||||
this._resizeListener = (ev) =>
|
||||
this.classList.toggle("fit-bottom", ev.matches);
|
||||
this._mediaq = window.matchMedia("(max-width: 599px");
|
||||
}
|
||||
this._mediaq!.addListener(this._resizeListener);
|
||||
this._resizeListener(this._mediaq!);
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._mediaq!.removeListener(this._resizeListener!);
|
||||
}
|
||||
}
|
||||
export class HaToast extends Snackbar {}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
|
|
|
@ -223,7 +223,6 @@ class DialogMediaPlayerBrowse extends LitElement {
|
|||
|
||||
ha-media-player-browse {
|
||||
--media-browser-max-height: calc(100vh - 65px);
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
:host(.opened) ha-media-player-browse {
|
||||
|
|
|
@ -879,6 +879,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
ha-circular-progress {
|
||||
|
|
|
@ -163,21 +163,22 @@ export class HaTracePathDetails extends LitElement {
|
|||
}
|
||||
)}
|
||||
<br />
|
||||
${error
|
||||
? html`<div class="error">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.automation.trace.path.error",
|
||||
{
|
||||
error: error,
|
||||
}
|
||||
)}
|
||||
</div>`
|
||||
: nothing}
|
||||
${result
|
||||
? html`${this.hass!.localize(
|
||||
"ui.panel.config.automation.trace.path.result"
|
||||
)}
|
||||
<pre>${dump(result)}</pre>`
|
||||
: error
|
||||
? html`<div class="error">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.automation.trace.path.error",
|
||||
{
|
||||
error: error,
|
||||
}
|
||||
)}
|
||||
</div>`
|
||||
: nothing}
|
||||
: nothing}
|
||||
${Object.keys(rest).length === 0
|
||||
? nothing
|
||||
: html`<pre>${dump(rest)}</pre>`}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import { mdiExclamationThick } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
html,
|
||||
TemplateResult,
|
||||
svg,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
svg,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { NODE_SIZE, SPACING } from "./hat-graph-const";
|
||||
import { isSafari } from "../../util/is_safari";
|
||||
import { NODE_SIZE, SPACING } from "./hat-graph-const";
|
||||
|
||||
/**
|
||||
* @attribute active
|
||||
|
@ -21,6 +22,8 @@ export class HatGraphNode extends LitElement {
|
|||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public error = false;
|
||||
|
||||
@property({ reflect: true, type: Boolean }) notEnabled = false;
|
||||
|
||||
@property({ reflect: true, type: Boolean }) graphStart = false;
|
||||
|
@ -65,16 +68,28 @@ export class HatGraphNode extends LitElement {
|
|||
`}
|
||||
<g class="node">
|
||||
<circle cx="0" cy="0" r=${NODE_SIZE / 2} />
|
||||
${this.error
|
||||
? svg`
|
||||
<g class="error">
|
||||
<circle
|
||||
cx="-12"
|
||||
cy=${-NODE_SIZE / 2}
|
||||
r="8"
|
||||
></circle>
|
||||
<path transform="translate(-18 -21) scale(.5)" class="exclamation" d=${mdiExclamationThick}/>
|
||||
</g>
|
||||
`
|
||||
: nothing}
|
||||
${this.badge
|
||||
? svg`
|
||||
<g class="number">
|
||||
<circle
|
||||
cx="8"
|
||||
cx="12"
|
||||
cy=${-NODE_SIZE / 2}
|
||||
r="8"
|
||||
></circle>
|
||||
<text
|
||||
x="8"
|
||||
x="12"
|
||||
y=${-NODE_SIZE / 2}
|
||||
text-anchor="middle"
|
||||
alignment-baseline="middle"
|
||||
|
@ -82,7 +97,7 @@ export class HatGraphNode extends LitElement {
|
|||
</g>
|
||||
`
|
||||
: nothing}
|
||||
<g style="pointer-events: none" transform="translate(${-12} ${-12})">
|
||||
<g style="pointer-events: none" transform="translate(-12 -12)">
|
||||
${this.iconPath
|
||||
? svg`<path class="icon" d=${this.iconPath}/>`
|
||||
: svg`<foreignObject><span class="icon"><slot name="icon"></slot></span></foreignObject>`}
|
||||
|
@ -143,13 +158,22 @@ export class HatGraphNode extends LitElement {
|
|||
fill: var(--background-clr);
|
||||
stroke: var(--circle-clr, var(--stroke-clr));
|
||||
}
|
||||
.error circle {
|
||||
fill: var(--error-color);
|
||||
stroke: none;
|
||||
stroke-width: 0;
|
||||
}
|
||||
.error .exclamation {
|
||||
fill: var(--text-primary-color);
|
||||
}
|
||||
.number circle {
|
||||
fill: var(--track-clr);
|
||||
stroke: none;
|
||||
stroke-width: 0;
|
||||
}
|
||||
.number text {
|
||||
font-size: smaller;
|
||||
font-size: 10px;
|
||||
fill: var(--text-primary-color);
|
||||
}
|
||||
path.icon {
|
||||
fill: var(--icon-clr);
|
||||
|
|
|
@ -93,6 +93,7 @@ export class HatScriptGraph extends LitElement {
|
|||
?active=${this.selected === path}
|
||||
.iconPath=${mdiAsterisk}
|
||||
.notEnabled=${config.enabled === false}
|
||||
.error=${this.trace.trace[path]?.some((tr) => tr.error)}
|
||||
tabindex=${track ? "0" : "-1"}
|
||||
></hat-graph-node>
|
||||
`;
|
||||
|
@ -171,6 +172,7 @@ export class HatScriptGraph extends LitElement {
|
|||
?track=${trace !== undefined}
|
||||
?active=${this.selected === path}
|
||||
.notEnabled=${disabled || config.enabled === false}
|
||||
.error=${this.trace.trace[path]?.some((tr) => tr.error)}
|
||||
slot="head"
|
||||
nofocus
|
||||
></hat-graph-node>
|
||||
|
@ -424,6 +426,7 @@ export class HatScriptGraph extends LitElement {
|
|||
?track=${path in this.trace.trace}
|
||||
?active=${this.selected === path}
|
||||
.notEnabled=${disabled || node.enabled === false}
|
||||
.error=${this.trace.trace[path]?.some((tr) => tr.error)}
|
||||
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
|
||||
>
|
||||
${node.service
|
||||
|
@ -451,6 +454,7 @@ export class HatScriptGraph extends LitElement {
|
|||
?track=${path in this.trace.trace}
|
||||
?active=${this.selected === path}
|
||||
.notEnabled=${disabled || node.enabled === false}
|
||||
.error=${this.trace.trace[path]?.some((tr) => tr.error)}
|
||||
tabindex=${this.trace && path in this.trace.trace ? "0" : "-1"}
|
||||
></hat-graph-node>
|
||||
`;
|
||||
|
@ -517,6 +521,7 @@ export class HatScriptGraph extends LitElement {
|
|||
@focus=${this.selectNode(node, path)}
|
||||
?track=${path in this.trace.trace}
|
||||
?active=${this.selected === path}
|
||||
.error=${this.trace.trace[path]?.some((tr) => tr.error)}
|
||||
.notEnabled=${disabled || node.enabled === false}
|
||||
></hat-graph-node>
|
||||
`;
|
||||
|
|
|
@ -153,7 +153,7 @@ class LogbookRenderer {
|
|||
|
||||
const parts: TemplateResult[] = [];
|
||||
|
||||
let i;
|
||||
let i: number;
|
||||
|
||||
for (
|
||||
i = 0;
|
||||
|
@ -232,7 +232,7 @@ class ActionRenderer {
|
|||
const value = this._getItem(index);
|
||||
|
||||
if (renderAllIterations) {
|
||||
let i;
|
||||
let i: number = 0;
|
||||
value.forEach((item) => {
|
||||
i = this._renderIteration(index, item, actionType);
|
||||
});
|
||||
|
@ -270,7 +270,12 @@ class ActionRenderer {
|
|||
} catch (err: any) {
|
||||
this._renderEntry(
|
||||
path,
|
||||
`Unable to extract path ${path}. Download trace and report as bug`
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.trace.messages.path_error",
|
||||
{
|
||||
path: path,
|
||||
}
|
||||
)
|
||||
);
|
||||
return index + 1;
|
||||
}
|
||||
|
@ -324,20 +329,22 @@ class ActionRenderer {
|
|||
private _handleTrigger(index: number, triggerStep: TriggerTraceStep): number {
|
||||
this._renderEntry(
|
||||
triggerStep.path,
|
||||
`${
|
||||
triggerStep.changed_variables.trigger.alias
|
||||
? `${triggerStep.changed_variables.trigger.alias} triggered`
|
||||
: "Triggered"
|
||||
} ${
|
||||
triggerStep.path === "trigger"
|
||||
? "manually"
|
||||
: `by the ${this.trace.trigger}`
|
||||
} at
|
||||
${formatDateTimeWithSeconds(
|
||||
new Date(triggerStep.timestamp),
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
)}`,
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.trace.messages.triggered_by",
|
||||
{
|
||||
triggeredBy: triggerStep.changed_variables.trigger?.alias
|
||||
? "alias"
|
||||
: "other",
|
||||
alias: triggerStep.changed_variables.trigger?.alias,
|
||||
triggeredPath: triggerStep.path === "trigger" ? "manual" : "trigger",
|
||||
trigger: this.trace.trigger,
|
||||
time: formatDateTimeWithSeconds(
|
||||
new Date(triggerStep.timestamp),
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
),
|
||||
}
|
||||
),
|
||||
mdiCircle
|
||||
);
|
||||
return index + 1;
|
||||
|
@ -367,12 +374,17 @@ class ActionRenderer {
|
|||
this.keys[index]
|
||||
) as ChooseAction;
|
||||
const disabled = chooseConfig.enabled === false;
|
||||
const name = chooseConfig.alias || "Choose";
|
||||
const name =
|
||||
chooseConfig.alias ||
|
||||
this.hass.localize("ui.panel.config.automation.trace.messages.choose");
|
||||
|
||||
if (defaultExecuted) {
|
||||
this._renderEntry(
|
||||
choosePath,
|
||||
`${name}: Default action executed`,
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.trace.messages.default_action_executed",
|
||||
{ name: name }
|
||||
),
|
||||
undefined,
|
||||
disabled
|
||||
);
|
||||
|
@ -385,8 +397,17 @@ class ActionRenderer {
|
|||
`${this.keys[index]}/choose/${chooseTrace.result.choice}`
|
||||
) as ChooseActionChoice | undefined;
|
||||
const choiceName = choiceConfig
|
||||
? `${choiceConfig.alias || `Option ${choiceNumeric}`} executed`
|
||||
: `Error: ${chooseTrace.error}`;
|
||||
? `${
|
||||
choiceConfig.alias ||
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.trace.messages.option_executed",
|
||||
{ option: choiceNumeric }
|
||||
)
|
||||
}`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.trace.messages.error",
|
||||
{ error: chooseTrace.error }
|
||||
);
|
||||
this._renderEntry(
|
||||
choosePath,
|
||||
`${name}: ${choiceName}`,
|
||||
|
@ -396,13 +417,16 @@ class ActionRenderer {
|
|||
} else {
|
||||
this._renderEntry(
|
||||
choosePath,
|
||||
`${name}: No action taken`,
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.trace.messages.no_action_executed",
|
||||
{ name: name }
|
||||
),
|
||||
undefined,
|
||||
disabled
|
||||
);
|
||||
}
|
||||
|
||||
let i;
|
||||
let i: number;
|
||||
|
||||
// Skip over conditions
|
||||
for (i = index + 1; i < this.keys.length; i++) {
|
||||
|
@ -479,26 +503,38 @@ class ActionRenderer {
|
|||
const ifTrace = this._getItem(index)[0] as IfActionTraceStep;
|
||||
const ifConfig = this._getDataFromPath(this.keys[index]) as IfAction;
|
||||
const disabled = ifConfig.enabled === false;
|
||||
const name = ifConfig.alias || "If";
|
||||
const name =
|
||||
ifConfig.alias ||
|
||||
this.hass.localize("ui.panel.config.automation.trace.messages.if");
|
||||
|
||||
if (ifTrace.result?.choice) {
|
||||
const choiceConfig = this._getDataFromPath(
|
||||
`${this.keys[index]}/${ifTrace.result.choice}/`
|
||||
) as any;
|
||||
const choiceName = choiceConfig
|
||||
? `${choiceConfig.alias || `${ifTrace.result.choice} action executed`}`
|
||||
: `Error: ${ifTrace.error}`;
|
||||
? choiceConfig.alias ||
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.trace.messages.action_executed",
|
||||
{ action: ifTrace.result.choice }
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.trace.messages.error",
|
||||
{ error: ifTrace.error }
|
||||
);
|
||||
this._renderEntry(ifPath, `${name}: ${choiceName}`, undefined, disabled);
|
||||
} else {
|
||||
this._renderEntry(
|
||||
ifPath,
|
||||
`${name}: No action taken`,
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.trace.messages.no_action_executed",
|
||||
{ name: name }
|
||||
),
|
||||
undefined,
|
||||
disabled
|
||||
);
|
||||
}
|
||||
|
||||
let i;
|
||||
let i: number;
|
||||
|
||||
// Skip over conditions
|
||||
for (i = index + 1; i < this.keys.length; i++) {
|
||||
|
@ -534,7 +570,11 @@ class ActionRenderer {
|
|||
|
||||
const disabled = parallelConfig.enabled === false;
|
||||
|
||||
const name = parallelConfig.alias || "Execute in parallel";
|
||||
const name =
|
||||
parallelConfig.alias ||
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.trace.messages.execute_in_parallel"
|
||||
);
|
||||
|
||||
this._renderEntry(parallelPath, name, undefined, disabled);
|
||||
|
||||
|
@ -564,7 +604,11 @@ class ActionRenderer {
|
|||
this.entries.push(html`
|
||||
<ha-timeline .icon=${icon} data-path=${path} .notEnabled=${disabled}>
|
||||
${description}${disabled
|
||||
? html`<span class="disabled"> (disabled)</span>`
|
||||
? html`<span class="disabled">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.trace.messages.disabled"
|
||||
)}</span
|
||||
>`
|
||||
: ""}
|
||||
</ha-timeline>
|
||||
`);
|
||||
|
@ -636,13 +680,12 @@ export class HaAutomationTracer extends LitElement {
|
|||
this.hass.locale,
|
||||
this.hass.config
|
||||
);
|
||||
const renderRuntime = () => `(runtime:
|
||||
${(
|
||||
const renderRuntime = () =>
|
||||
(
|
||||
(new Date(this.trace!.timestamp.finish!).getTime() -
|
||||
new Date(this.trace!.timestamp.start).getTime()) /
|
||||
1000
|
||||
).toFixed(2)}
|
||||
seconds)`;
|
||||
).toFixed(2);
|
||||
|
||||
let entry: {
|
||||
description: TemplateResult | string;
|
||||
|
@ -652,57 +695,90 @@ export class HaAutomationTracer extends LitElement {
|
|||
|
||||
if (this.trace.state === "running") {
|
||||
entry = {
|
||||
description: "Still running",
|
||||
description: this.hass.localize(
|
||||
"ui.panel.config.automation.trace.messages.still_running"
|
||||
),
|
||||
icon: mdiProgressClock,
|
||||
};
|
||||
} else if (this.trace.state === "debugged") {
|
||||
entry = {
|
||||
description: "Debugged",
|
||||
description: this.hass.localize(
|
||||
"ui.panel.config.automation.trace.messages.debugged"
|
||||
),
|
||||
icon: mdiProgressWrench,
|
||||
};
|
||||
} else if (this.trace.script_execution === "finished") {
|
||||
entry = {
|
||||
description: `Finished at ${renderFinishedAt()} ${renderRuntime()}`,
|
||||
description: this.hass.localize(
|
||||
"ui.panel.config.automation.trace.messages.finished",
|
||||
{
|
||||
time: renderFinishedAt(),
|
||||
executiontime: renderRuntime(),
|
||||
}
|
||||
),
|
||||
icon: mdiCircle,
|
||||
};
|
||||
} else if (this.trace.script_execution === "aborted") {
|
||||
entry = {
|
||||
description: `Aborted at ${renderFinishedAt()} ${renderRuntime()}`,
|
||||
description: this.hass.localize(
|
||||
"ui.panel.config.automation.trace.messages.aborted",
|
||||
{
|
||||
time: renderFinishedAt(),
|
||||
executiontime: renderRuntime(),
|
||||
}
|
||||
),
|
||||
icon: mdiAlertCircle,
|
||||
};
|
||||
} else if (this.trace.script_execution === "cancelled") {
|
||||
entry = {
|
||||
description: `Cancelled at ${renderFinishedAt()} ${renderRuntime()}`,
|
||||
description: this.hass.localize(
|
||||
"ui.panel.config.automation.trace.messages.cancelled",
|
||||
{
|
||||
time: renderFinishedAt(),
|
||||
executiontime: renderRuntime(),
|
||||
}
|
||||
),
|
||||
icon: mdiAlertCircle,
|
||||
};
|
||||
} else {
|
||||
let reason: string;
|
||||
let message:
|
||||
| "stopped_failed_conditions"
|
||||
| "stopped_failed_single"
|
||||
| "stopped_failed_max_runs"
|
||||
| "stopped_error"
|
||||
| "stopped_unknown_reason";
|
||||
let isError = false;
|
||||
let extra: TemplateResult | undefined;
|
||||
|
||||
switch (this.trace.script_execution) {
|
||||
case "failed_conditions":
|
||||
reason = "a condition failed";
|
||||
message = "stopped_failed_conditions";
|
||||
break;
|
||||
case "failed_single":
|
||||
reason = "only a single execution is allowed";
|
||||
message = "stopped_failed_single";
|
||||
break;
|
||||
case "failed_max_runs":
|
||||
reason = "maximum number of parallel runs reached";
|
||||
message = "stopped_failed_max_runs";
|
||||
break;
|
||||
case "error":
|
||||
reason = "an error was encountered";
|
||||
isError = true;
|
||||
message = "stopped_error";
|
||||
extra = html`<br /><br />${this.trace.error!}`;
|
||||
break;
|
||||
default:
|
||||
reason = `of unknown reason "${this.trace.script_execution}"`;
|
||||
isError = true;
|
||||
message = "stopped_unknown_reason";
|
||||
}
|
||||
|
||||
entry = {
|
||||
description: html`Stopped because ${reason} at ${renderFinishedAt()}
|
||||
${renderRuntime()}${extra || ""}`,
|
||||
description: html`${this.hass.localize(
|
||||
`ui.panel.config.automation.trace.messages.${message}`,
|
||||
{
|
||||
time: renderFinishedAt(),
|
||||
executiontime: renderRuntime(),
|
||||
}
|
||||
)}
|
||||
${extra || ""}`,
|
||||
icon: mdiAlertCircle,
|
||||
className: isError ? "error" : undefined,
|
||||
};
|
||||
|
|
|
@ -219,8 +219,8 @@ export interface NumericStateCondition extends BaseCondition {
|
|||
condition: "numeric_state";
|
||||
entity_id: string;
|
||||
attribute?: string;
|
||||
above?: number;
|
||||
below?: number;
|
||||
above?: string | number;
|
||||
below?: string | number;
|
||||
value_template?: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -148,6 +148,11 @@ export const updateCloudPref = (
|
|||
...prefs,
|
||||
});
|
||||
|
||||
export const removeCloudData = (hass: HomeAssistant) =>
|
||||
hass.callWS({
|
||||
type: "cloud/remove_data",
|
||||
});
|
||||
|
||||
export const updateCloudGoogleEntityConfig = (
|
||||
hass: HomeAssistant,
|
||||
entity_id: string,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||
import { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
export interface CloudTTSInfo {
|
||||
|
@ -27,27 +26,21 @@ export const getCloudTtsLanguages = (info?: CloudTTSInfo) => {
|
|||
return languages;
|
||||
};
|
||||
|
||||
export const getCloudTtsSupportedGenders = (
|
||||
export const getCloudTtsSupportedVoices = (
|
||||
language: string,
|
||||
info: CloudTTSInfo | undefined,
|
||||
localize: LocalizeFunc
|
||||
info: CloudTTSInfo | undefined
|
||||
) => {
|
||||
const genders: Array<[string, string]> = [];
|
||||
const voices: Array<string> = [];
|
||||
|
||||
if (!info) {
|
||||
return genders;
|
||||
return voices;
|
||||
}
|
||||
|
||||
for (const [curLang, gender] of info.languages) {
|
||||
for (const [curLang, voice] of info.languages) {
|
||||
if (curLang === language) {
|
||||
genders.push([
|
||||
gender,
|
||||
gender === "male" || gender === "female"
|
||||
? localize(`ui.components.media-browser.tts.gender_${gender}`)
|
||||
: gender,
|
||||
]);
|
||||
voices.push(voice);
|
||||
}
|
||||
}
|
||||
|
||||
return genders.sort((a, b) => caseInsensitiveStringCompare(a[1], b[1]));
|
||||
return voices.sort((a, b) => caseInsensitiveStringCompare(a, b));
|
||||
};
|
||||
|
|
|
@ -18,6 +18,7 @@ export interface ConfigEntry {
|
|||
supports_options: boolean;
|
||||
supports_remove_device: boolean;
|
||||
supports_unload: boolean;
|
||||
supports_reconfigure: boolean;
|
||||
pref_disable_new_entities: boolean;
|
||||
pref_disable_polling: boolean;
|
||||
disabled_by: "user" | null;
|
||||
|
|
|
@ -26,13 +26,18 @@ const HEADERS = {
|
|||
"HA-Frontend-Base": `${location.protocol}//${location.host}`,
|
||||
};
|
||||
|
||||
export const createConfigFlow = (hass: HomeAssistant, handler: string) =>
|
||||
export const createConfigFlow = (
|
||||
hass: HomeAssistant,
|
||||
handler: string,
|
||||
entry_id?: string
|
||||
) =>
|
||||
hass.callApi<DataEntryFlowStep>(
|
||||
"POST",
|
||||
"config/config_entries/flow",
|
||||
{
|
||||
handler,
|
||||
show_advanced_options: Boolean(hass.userData?.showAdvanced),
|
||||
entry_id,
|
||||
},
|
||||
HEADERS
|
||||
);
|
||||
|
|
|
@ -33,6 +33,7 @@ export interface DataEntryFlowStepForm {
|
|||
description_placeholders?: Record<string, string>;
|
||||
last_step: boolean | null;
|
||||
preview?: string;
|
||||
translation_domain?: string;
|
||||
}
|
||||
|
||||
export interface DataEntryFlowStepExternal {
|
||||
|
@ -42,6 +43,7 @@ export interface DataEntryFlowStepExternal {
|
|||
step_id: string;
|
||||
url: string;
|
||||
description_placeholders: Record<string, string>;
|
||||
translation_domain?: string;
|
||||
}
|
||||
|
||||
export interface DataEntryFlowStepCreateEntry {
|
||||
|
@ -53,6 +55,7 @@ export interface DataEntryFlowStepCreateEntry {
|
|||
result?: ConfigEntry;
|
||||
description: string;
|
||||
description_placeholders?: Record<string, string>;
|
||||
translation_domain?: string;
|
||||
}
|
||||
|
||||
export interface DataEntryFlowStepAbort {
|
||||
|
@ -61,6 +64,7 @@ export interface DataEntryFlowStepAbort {
|
|||
handler: string;
|
||||
reason: string;
|
||||
description_placeholders?: Record<string, string>;
|
||||
translation_domain?: string;
|
||||
}
|
||||
|
||||
export interface DataEntryFlowStepProgress {
|
||||
|
@ -70,6 +74,7 @@ export interface DataEntryFlowStepProgress {
|
|||
step_id: string;
|
||||
progress_action: string;
|
||||
description_placeholders?: Record<string, string>;
|
||||
translation_domain?: string;
|
||||
}
|
||||
|
||||
export interface DataEntryFlowStepMenu {
|
||||
|
@ -80,6 +85,7 @@ export interface DataEntryFlowStepMenu {
|
|||
/** If array, use value to lookup translations in strings.json */
|
||||
menu_options: string[] | Record<string, string>;
|
||||
description_placeholders?: Record<string, string>;
|
||||
translation_domain?: string;
|
||||
}
|
||||
|
||||
export type DataEntryFlowStep =
|
||||
|
|
|
@ -331,6 +331,9 @@ export const getReferencedStatisticIds = (
|
|||
}
|
||||
}
|
||||
}
|
||||
if (!(includeTypes && !includeTypes.includes("device"))) {
|
||||
statIDs.push(...prefs.device_consumption.map((d) => d.stat_consumption));
|
||||
}
|
||||
|
||||
return statIDs;
|
||||
};
|
||||
|
@ -383,6 +386,7 @@ const getEnergyData = async (
|
|||
"solar",
|
||||
"battery",
|
||||
"gas",
|
||||
"device",
|
||||
]);
|
||||
const waterStatIds = getReferencedStatisticIds(prefs, info, ["water"]);
|
||||
|
||||
|
@ -777,7 +781,7 @@ export const getEnergyGasUnit = (
|
|||
: "ft³";
|
||||
};
|
||||
|
||||
export const getEnergyWaterUnit = (hass: HomeAssistant): string | undefined =>
|
||||
export const getEnergyWaterUnit = (hass: HomeAssistant): string =>
|
||||
hass.config.unit_system.length === "km" ? "L" : "gal";
|
||||
|
||||
export const energyStatisticHelpUrl =
|
||||
|
|
|
@ -70,6 +70,7 @@ export const DOMAIN_ATTRIBUTES_UNITS = {
|
|||
brightness: "%",
|
||||
},
|
||||
sun: {
|
||||
azimuth: "°",
|
||||
elevation: "°",
|
||||
},
|
||||
vacuum: {
|
||||
|
|
|
@ -81,7 +81,7 @@ export interface EntityHistoryState {
|
|||
/** attributes */
|
||||
a: { [key: string]: any };
|
||||
/** last_changed; if set, also applies to lu */
|
||||
lc: number;
|
||||
lc?: number;
|
||||
/** last_updated */
|
||||
lu: number;
|
||||
}
|
||||
|
@ -419,17 +419,37 @@ const BLANK_UNIT = " ";
|
|||
export const computeHistory = (
|
||||
hass: HomeAssistant,
|
||||
stateHistory: HistoryStates,
|
||||
entityIds: string[],
|
||||
localize: LocalizeFunc,
|
||||
sensorNumericalDeviceClasses: string[],
|
||||
splitDeviceClasses = false
|
||||
): HistoryResult => {
|
||||
const lineChartDevices: { [unit: string]: HistoryStates } = {};
|
||||
const timelineDevices: TimelineEntity[] = [];
|
||||
if (!stateHistory) {
|
||||
|
||||
const localStateHistory: HistoryStates = {};
|
||||
|
||||
// Create a limited history from stateObj if entity has no recorded history.
|
||||
const allEntities = new Set([...entityIds, ...Object.keys(stateHistory)]);
|
||||
allEntities.forEach((entity) => {
|
||||
if (entity in stateHistory) {
|
||||
localStateHistory[entity] = stateHistory[entity];
|
||||
} else if (hass.states[entity]) {
|
||||
localStateHistory[entity] = [
|
||||
{
|
||||
s: hass.states[entity].state,
|
||||
a: hass.states[entity].attributes,
|
||||
lu: new Date(hass.states[entity].last_updated).getTime() / 1000,
|
||||
},
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
if (!localStateHistory) {
|
||||
return { line: [], timeline: [] };
|
||||
}
|
||||
Object.keys(stateHistory).forEach((entityId) => {
|
||||
const stateInfo = stateHistory[entityId];
|
||||
Object.keys(localStateHistory).forEach((entityId) => {
|
||||
const stateInfo = localStateHistory[entityId];
|
||||
if (stateInfo.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -198,8 +198,9 @@ export const entryIcon = async (
|
|||
if (entry.icon) {
|
||||
return entry.icon;
|
||||
}
|
||||
const stateObj = hass.states[entry.entity_id] as HassEntity | undefined;
|
||||
const domain = computeDomain(entry.entity_id);
|
||||
return getEntityIcon(hass, domain, undefined, undefined, entry);
|
||||
return getEntityIcon(hass, domain, stateObj, undefined, entry);
|
||||
};
|
||||
|
||||
const getEntityIcon = async (
|
||||
|
|
|
@ -43,6 +43,7 @@ export interface IntegrationManifest {
|
|||
| "cloud_push"
|
||||
| "local_polling"
|
||||
| "local_push";
|
||||
single_config_entry?: boolean;
|
||||
}
|
||||
export interface IntegrationSetup {
|
||||
domain: string;
|
||||
|
|
|
@ -11,6 +11,7 @@ export interface Integration {
|
|||
iot_class?: string;
|
||||
supported_by?: string;
|
||||
is_built_in?: boolean;
|
||||
single_config_entry?: boolean;
|
||||
}
|
||||
|
||||
export interface Integrations {
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { getExtendedEntityRegistryEntry } from "./entity_registry";
|
||||
import { showEnterCodeDialogDialog } from "../dialogs/enter-code/show-enter-code-dialog";
|
||||
import { showEnterCodeDialog } from "../dialogs/enter-code/show-enter-code-dialog";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export const FORMAT_TEXT = "text";
|
||||
|
@ -38,7 +38,7 @@ export const callProtectedLockService = async (
|
|||
const defaultCode = lockRegistryEntry?.options?.lock?.default_code;
|
||||
|
||||
if (stateObj!.attributes.code_format && !defaultCode) {
|
||||
const response = await showEnterCodeDialogDialog(element, {
|
||||
const response = await showEnterCodeDialog(element, {
|
||||
codeFormat: "text",
|
||||
codePattern: stateObj!.attributes.code_format,
|
||||
title: hass.localize(`ui.card.lock.${service}`),
|
||||
|
|
|
@ -10,8 +10,10 @@ import {
|
|||
LovelaceCard,
|
||||
} from "../panels/lovelace/types";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { LovelaceSectionConfig } from "./lovelace/config/section";
|
||||
import { fetchConfig, LegacyLovelaceConfig } from "./lovelace/config/types";
|
||||
import { LovelaceViewConfig } from "./lovelace/config/view";
|
||||
import { HuiSection } from "../panels/lovelace/sections/hui-section";
|
||||
|
||||
export interface LovelacePanelConfig {
|
||||
mode: "yaml" | "storage";
|
||||
|
@ -24,10 +26,21 @@ export interface LovelaceViewElement extends HTMLElement {
|
|||
index?: number;
|
||||
cards?: Array<LovelaceCard | HuiErrorCard>;
|
||||
badges?: LovelaceBadge[];
|
||||
sections?: HuiSection[];
|
||||
isStrategy: boolean;
|
||||
setConfig(config: LovelaceViewConfig): void;
|
||||
}
|
||||
|
||||
export interface LovelaceSectionElement extends HTMLElement {
|
||||
hass?: HomeAssistant;
|
||||
lovelace?: Lovelace;
|
||||
viewIndex?: number;
|
||||
index?: number;
|
||||
cards?: Array<LovelaceCard | HuiErrorCard>;
|
||||
isStrategy: boolean;
|
||||
setConfig(config: LovelaceSectionConfig): void;
|
||||
}
|
||||
|
||||
type LovelaceUpdatedEvent = HassEventBase & {
|
||||
event_type: "lovelace_updated";
|
||||
data: {
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { LovelaceLayoutOptions } from "../../../panels/lovelace/types";
|
||||
|
||||
export interface LovelaceCardConfig {
|
||||
index?: number;
|
||||
view_index?: number;
|
||||
view_layout?: any;
|
||||
layout_options?: LovelaceLayoutOptions;
|
||||
type: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import type { LovelaceCardConfig } from "./card";
|
||||
import type { LovelaceStrategyConfig } from "./strategy";
|
||||
|
||||
export interface LovelaceBaseSectionConfig {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig {
|
||||
type?: string;
|
||||
cards?: LovelaceCardConfig[];
|
||||
}
|
||||
|
||||
export interface LovelaceStrategySectionConfig
|
||||
extends LovelaceBaseSectionConfig {
|
||||
strategy: LovelaceStrategyConfig;
|
||||
}
|
||||
|
||||
export type LovelaceSectionRawConfig =
|
||||
| LovelaceSectionConfig
|
||||
| LovelaceStrategySectionConfig;
|
||||
|
||||
export function isStrategySection(
|
||||
section: LovelaceSectionRawConfig
|
||||
): section is LovelaceStrategySectionConfig {
|
||||
return "strategy" in section;
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import type { LovelaceBadgeConfig } from "./badge";
|
||||
import type { LovelaceCardConfig } from "./card";
|
||||
import type { LovelaceSectionRawConfig } from "./section";
|
||||
import type { LovelaceStrategyConfig } from "./strategy";
|
||||
|
||||
export interface ShowViewConfig {
|
||||
|
@ -23,6 +24,7 @@ export interface LovelaceViewConfig extends LovelaceBaseViewConfig {
|
|||
type?: string;
|
||||
badges?: Array<string | LovelaceBadgeConfig>;
|
||||
cards?: LovelaceCardConfig[];
|
||||
sections?: LovelaceSectionRawConfig[];
|
||||
}
|
||||
|
||||
export interface LovelaceStrategyViewConfig extends LovelaceBaseViewConfig {
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
Trigger,
|
||||
} from "./automation";
|
||||
import { BlueprintInput } from "./blueprint";
|
||||
import { computeObjectId } from "../common/entity/compute_object_id";
|
||||
|
||||
export const MODES = ["single", "restart", "queued", "parallel"] as const;
|
||||
export const MODES_MAX = ["queued", "parallel"] as const;
|
||||
|
@ -404,3 +405,11 @@ export const getActionType = (action: Action): ActionType => {
|
|||
}
|
||||
return "unknown";
|
||||
};
|
||||
|
||||
export const hasScriptFields = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string
|
||||
): boolean => {
|
||||
const fields = hass.services.script[computeObjectId(entityId)]?.fields;
|
||||
return fields !== undefined && Object.keys(fields).length > 0;
|
||||
};
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
import { Connection, createCollection } from "home-assistant-js-websocket";
|
||||
import { Store } from "home-assistant-js-websocket/dist/store";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import { AreaRegistryEntry } from "./area_registry";
|
||||
|
||||
const fetchAreaRegistry = (conn: Connection) =>
|
||||
conn.sendMessagePromise<AreaRegistryEntry[]>({
|
||||
type: "config/area_registry/list",
|
||||
});
|
||||
conn
|
||||
.sendMessagePromise<AreaRegistryEntry[]>({
|
||||
type: "config/area_registry/list",
|
||||
})
|
||||
.then((areas) =>
|
||||
areas.sort((ent1, ent2) => stringCompare(ent1.name, ent2.name))
|
||||
);
|
||||
|
||||
const subscribeAreaRegistryUpdates = (
|
||||
conn: Connection,
|
||||
|
|
|
@ -23,7 +23,7 @@ export const showConfigFlowDialog = (
|
|||
loadDevicesAndAreas: true,
|
||||
createFlow: async (hass, handler) => {
|
||||
const [step] = await Promise.all([
|
||||
createConfigFlow(hass, handler),
|
||||
createConfigFlow(hass, handler, dialogParams.entryId),
|
||||
hass.loadFragmentTranslation("config"),
|
||||
hass.loadBackendTranslation("config", handler),
|
||||
hass.loadBackendTranslation("selector", handler),
|
||||
|
@ -44,7 +44,7 @@ export const showConfigFlowDialog = (
|
|||
|
||||
renderAbortDescription(hass, step) {
|
||||
const description = hass.localize(
|
||||
`component.${step.handler}.config.abort.${step.reason}`,
|
||||
`component.${step.translation_domain || step.handler}.config.abort.${step.reason}`,
|
||||
step.description_placeholders
|
||||
);
|
||||
|
||||
|
@ -58,7 +58,7 @@ export const showConfigFlowDialog = (
|
|||
renderShowFormStepHeader(hass, step) {
|
||||
return (
|
||||
hass.localize(
|
||||
`component.${step.handler}.config.step.${step.step_id}.title`,
|
||||
`component.${step.translation_domain || step.handler}.config.step.${step.step_id}.title`,
|
||||
step.description_placeholders
|
||||
) || hass.localize(`component.${step.handler}.title`)
|
||||
);
|
||||
|
@ -66,7 +66,7 @@ export const showConfigFlowDialog = (
|
|||
|
||||
renderShowFormStepDescription(hass, step) {
|
||||
const description = hass.localize(
|
||||
`component.${step.handler}.config.step.${step.step_id}.description`,
|
||||
`component.${step.translation_domain || step.handler}.config.step.${step.step_id}.description`,
|
||||
step.description_placeholders
|
||||
);
|
||||
return description
|
||||
|
@ -84,7 +84,7 @@ export const showConfigFlowDialog = (
|
|||
|
||||
renderShowFormStepFieldHelper(hass, step, field) {
|
||||
const description = hass.localize(
|
||||
`component.${step.handler}.config.step.${step.step_id}.data_description.${field.name}`,
|
||||
`component.${step.translation_domain || step.handler}.config.step.${step.step_id}.data_description.${field.name}`,
|
||||
step.description_placeholders
|
||||
);
|
||||
return description
|
||||
|
@ -95,7 +95,7 @@ export const showConfigFlowDialog = (
|
|||
renderShowFormStepFieldError(hass, step, error) {
|
||||
return (
|
||||
hass.localize(
|
||||
`component.${step.handler}.config.error.${error}`,
|
||||
`component.${step.translation_domain || step.translation_domain || step.handler}.config.error.${error}`,
|
||||
step.description_placeholders
|
||||
) || error
|
||||
);
|
||||
|
@ -131,7 +131,7 @@ export const showConfigFlowDialog = (
|
|||
|
||||
renderExternalStepDescription(hass, step) {
|
||||
const description = hass.localize(
|
||||
`component.${step.handler}.config.${step.step_id}.description`,
|
||||
`component.${step.translation_domain || step.handler}.config.${step.step_id}.description`,
|
||||
step.description_placeholders
|
||||
);
|
||||
|
||||
|
@ -155,7 +155,7 @@ export const showConfigFlowDialog = (
|
|||
|
||||
renderCreateEntryDescription(hass, step) {
|
||||
const description = hass.localize(
|
||||
`component.${step.handler}.config.create_entry.${
|
||||
`component.${step.translation_domain || step.handler}.config.create_entry.${
|
||||
step.description || "default"
|
||||
}`,
|
||||
step.description_placeholders
|
||||
|
@ -190,7 +190,7 @@ export const showConfigFlowDialog = (
|
|||
|
||||
renderShowFormProgressDescription(hass, step) {
|
||||
const description = hass.localize(
|
||||
`component.${step.handler}.config.progress.${step.progress_action}`,
|
||||
`component.${step.translation_domain || step.handler}.config.progress.${step.progress_action}`,
|
||||
step.description_placeholders
|
||||
);
|
||||
return description
|
||||
|
@ -210,7 +210,7 @@ export const showConfigFlowDialog = (
|
|||
|
||||
renderMenuDescription(hass, step) {
|
||||
const description = hass.localize(
|
||||
`component.${step.handler}.config.step.${step.step_id}.description`,
|
||||
`component.${step.translation_domain || step.handler}.config.step.${step.step_id}.description`,
|
||||
step.description_placeholders
|
||||
);
|
||||
return description
|
||||
|
@ -222,7 +222,7 @@ export const showConfigFlowDialog = (
|
|||
|
||||
renderMenuOption(hass, step, option) {
|
||||
return hass.localize(
|
||||
`component.${step.handler}.config.step.${step.step_id}.menu_options.${option}`,
|
||||
`component.${step.translation_domain || step.handler}.config.step.${step.step_id}.menu_options.${option}`,
|
||||
step.description_placeholders
|
||||
);
|
||||
},
|
||||
|
|
|
@ -139,6 +139,7 @@ export interface DataEntryFlowDialogParams {
|
|||
}) => void;
|
||||
flowConfig: FlowConfig;
|
||||
showAdvanced?: boolean;
|
||||
entryId?: string;
|
||||
dialogParentElement?: HTMLElement;
|
||||
}
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ export const showOptionsFlowDialog = (
|
|||
|
||||
renderAbortDescription(hass, step) {
|
||||
const description = hass.localize(
|
||||
`component.${configEntry.domain}.options.abort.${step.reason}`,
|
||||
`component.${step.translation_domain || configEntry.domain}.options.abort.${step.reason}`,
|
||||
step.description_placeholders
|
||||
);
|
||||
|
||||
|
@ -71,7 +71,7 @@ export const showOptionsFlowDialog = (
|
|||
renderShowFormStepHeader(hass, step) {
|
||||
return (
|
||||
hass.localize(
|
||||
`component.${configEntry.domain}.options.step.${step.step_id}.title`,
|
||||
`component.${step.translation_domain || configEntry.domain}.options.step.${step.step_id}.title`,
|
||||
step.description_placeholders
|
||||
) || hass.localize(`ui.dialogs.options_flow.form.header`)
|
||||
);
|
||||
|
@ -79,7 +79,7 @@ export const showOptionsFlowDialog = (
|
|||
|
||||
renderShowFormStepDescription(hass, step) {
|
||||
const description = hass.localize(
|
||||
`component.${configEntry.domain}.options.step.${step.step_id}.description`,
|
||||
`component.${step.translation_domain || configEntry.domain}.options.step.${step.step_id}.description`,
|
||||
step.description_placeholders
|
||||
);
|
||||
return description
|
||||
|
@ -101,7 +101,7 @@ export const showOptionsFlowDialog = (
|
|||
|
||||
renderShowFormStepFieldHelper(hass, step, field) {
|
||||
const description = hass.localize(
|
||||
`component.${configEntry.domain}.options.step.${step.step_id}.data_description.${field.name}`,
|
||||
`component.${step.translation_domain || configEntry.domain}.options.step.${step.step_id}.data_description.${field.name}`,
|
||||
step.description_placeholders
|
||||
);
|
||||
return description
|
||||
|
@ -112,7 +112,7 @@ export const showOptionsFlowDialog = (
|
|||
renderShowFormStepFieldError(hass, step, error) {
|
||||
return (
|
||||
hass.localize(
|
||||
`component.${configEntry.domain}.options.error.${error}`,
|
||||
`component.${step.translation_domain || configEntry.domain}.options.error.${error}`,
|
||||
step.description_placeholders
|
||||
) || error
|
||||
);
|
||||
|
@ -159,7 +159,7 @@ export const showOptionsFlowDialog = (
|
|||
|
||||
renderShowFormProgressDescription(hass, step) {
|
||||
const description = hass.localize(
|
||||
`component.${configEntry.domain}.options.progress.${step.progress_action}`,
|
||||
`component.${step.translation_domain || configEntry.domain}.options.progress.${step.progress_action}`,
|
||||
step.description_placeholders
|
||||
);
|
||||
return description
|
||||
|
@ -183,7 +183,7 @@ export const showOptionsFlowDialog = (
|
|||
|
||||
renderMenuDescription(hass, step) {
|
||||
const description = hass.localize(
|
||||
`component.${configEntry.domain}.options.step.${step.step_id}.description`,
|
||||
`component.${step.translation_domain || configEntry.domain}.options.step.${step.step_id}.description`,
|
||||
step.description_placeholders
|
||||
);
|
||||
return description
|
||||
|
@ -199,7 +199,7 @@ export const showOptionsFlowDialog = (
|
|||
|
||||
renderMenuOption(hass, step, option) {
|
||||
return hass.localize(
|
||||
`component.${configEntry.domain}.options.step.${step.step_id}.menu_options.${option}`,
|
||||
`component.${step.translation_domain || configEntry.domain}.options.step.${step.step_id}.menu_options.${option}`,
|
||||
step.description_placeholders
|
||||
);
|
||||
},
|
||||
|
|
|
@ -10,7 +10,7 @@ export interface EnterCodeDialogParams {
|
|||
cancel?: () => void;
|
||||
}
|
||||
|
||||
export const showEnterCodeDialogDialog = (
|
||||
export const showEnterCodeDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: EnterCodeDialogParams
|
||||
) =>
|
||||
|
|
|
@ -89,7 +89,12 @@ class DialogBox extends LitElement {
|
|||
</div>
|
||||
${confirmPrompt &&
|
||||
html`
|
||||
<mwc-button @click=${this._dismiss} slot="secondaryAction">
|
||||
<mwc-button
|
||||
@click=${this._dismiss}
|
||||
slot="secondaryAction"
|
||||
?dialogInitialFocus=${!this._params.prompt &&
|
||||
this._params.destructive}
|
||||
>
|
||||
${this._params.dismissText
|
||||
? this._params.dismissText
|
||||
: this.hass.localize("ui.dialogs.generic.cancel")}
|
||||
|
@ -97,7 +102,8 @@ class DialogBox extends LitElement {
|
|||
`}
|
||||
<mwc-button
|
||||
@click=${this._confirm}
|
||||
?dialogInitialFocus=${!this._params.prompt}
|
||||
?dialogInitialFocus=${!this._params.prompt &&
|
||||
!this._params.destructive}
|
||||
slot="primaryAction"
|
||||
class=${classMap({
|
||||
destructive: this._params.destructive || false,
|
||||
|
|
|
@ -16,6 +16,8 @@ export class HaMoreInfoStateHeader extends LitElement {
|
|||
|
||||
@property({ attribute: false }) public stateOverride?: string;
|
||||
|
||||
@property({ attribute: false }) public changedOverride?: number;
|
||||
|
||||
@state() private _absoluteTime = false;
|
||||
|
||||
private _localizeState(): TemplateResult | string {
|
||||
|
@ -50,13 +52,13 @@ export class HaMoreInfoStateHeader extends LitElement {
|
|||
? html`
|
||||
<ha-absolute-time
|
||||
.hass=${this.hass}
|
||||
.datetime=${this.stateObj.last_changed}
|
||||
.datetime=${this.changedOverride ?? this.stateObj.last_changed}
|
||||
></ha-absolute-time>
|
||||
`
|
||||
: html`
|
||||
<ha-relative-time
|
||||
.hass=${this.hass}
|
||||
.datetime=${this.stateObj.last_changed}
|
||||
.datetime=${this.changedOverride ?? this.stateObj.last_changed}
|
||||
capitalize
|
||||
></ha-relative-time>
|
||||
`}
|
||||
|
|
|
@ -322,6 +322,8 @@ export class HaMoreInfoLightFavoriteColors extends LitElement {
|
|||
position: absolute;
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
inset-inline-end: -6px;
|
||||
inset-inline-start: initial;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
outline: none;
|
||||
|
|
|
@ -446,6 +446,8 @@ class LightRgbColorPicker extends LitElement {
|
|||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
inset-inline-end: 0;
|
||||
inset-inline-start: initial;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ export const DOMAINS_WITH_NEW_MORE_INFO = [
|
|||
"light",
|
||||
"lock",
|
||||
"siren",
|
||||
"script",
|
||||
"switch",
|
||||
"valve",
|
||||
"water_heater",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue