Compare commits

..

16 Commits

Author SHA1 Message Date
Paul Bottein f75d3f887e Add buttons on mobile to move sections 2024-03-01 15:37:23 +01:00
Paul Bottein c05824c641 Revert "Transform helper to warning for edit view type"
This reverts commit 3abdffda9c.
2024-03-01 14:57:08 +01:00
Paul Bottein 3abdffda9c Transform helper to warning for edit view type 2024-03-01 14:55:34 +01:00
Paul Bottein 67da851efc Use max column count instead of max width for section grid (#19932) 2024-03-01 13:09:21 +01:00
Paul Bottein 5463a27255 Add badges support to sections view (#19929) 2024-03-01 13:09:10 +01:00
renovate[bot] ec0434c9b0 Update dependency hls.js to v1.5.7 (#19927)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-01 11:58:00 +01:00
renovate[bot] 7d8cb5c863 Update typescript-eslint monorepo to v7.1.0 (#19922) 2024-02-29 18:32:16 -05:00
Bram Kragten 4f01348ffb Improve error display in automation/script traces (#19920) 2024-02-29 13:09:02 -05:00
Paul Bottein 2af3400464 Fix section editing after disconnect/reconnect (#19917)
* Fix section editing after disconnect/reconnect

* Update src/components/ha-sortable.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-02-29 14:12:19 +00:00
renovate[bot] b6e220a4c5 Update vaadinWebComponents monorepo to v24.3.7 (#19919)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-29 13:54:55 +01:00
renovate[bot] d5d45f100e Update dependency open to v10.0.4 (#19918)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-29 13:54:22 +01:00
renovate[bot] 6b9ca60c47 Update octokit monorepo to v7 (major) (#19914)
Update octokit monorepo to v7

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-29 13:54:02 +01:00
Simon Lamon bc445a1e27 Lokalize automation trace area (#19836)
* Translate automation trace timeline area

* Fix undefined changed_variables

* change naming options in triggered_by

* Split messages for stopped_by

* remove stopped message
2024-02-29 13:51:18 +01:00
dependabot[bot] a087b4c43e Bump ip from 1.1.8 to 1.1.9 (#19915) 2024-02-29 01:20:20 -05:00
Paul Bottein 8f67ddf968 Add allow changing type of empty views (#19912) 2024-02-28 21:51:21 +01:00
Simon Lamon 9ef07484dd Replace paper-toast with mwc-snackbar (#19579)
* toast

* Fixes

* Linting

* Remove empty styles

* PR feedback

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-02-28 21:50:58 +01:00
43 changed files with 1295 additions and 1847 deletions
+1 -4
View File
@@ -28,7 +28,7 @@ class HcLaunchScreen extends LitElement {
:host {
display: block;
height: 100vh;
background-color: #f2f4f9;
background-color: white;
font-size: 24px;
}
.container {
@@ -43,9 +43,6 @@ class HcLaunchScreen extends LitElement {
max-width: 80%;
object-fit: cover;
}
.status {
color: #1d2126;
}
`;
}
}
-1
View File
@@ -4,7 +4,6 @@ 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),
-482
View File
@@ -1,482 +0,0 @@
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",
},
},
"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.moon_phase": {
entity_id: "sensor.moon_phase",
state: "waning_crescent",
attributes: {
options: [
"new_moon",
"waxing_crescent",
"first_quarter",
"waxing_gibbous",
"full_moon",
"waning_gibbous",
"last_quarter",
"waning_crescent",
],
device_class: "enum",
icon: "mdi:moon-waning-crescent",
friendly_name: "Moon Phase",
},
},
"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,
},
},
});
-12
View File
@@ -1,12 +0,0 @@
import { DemoConfig } from "../types";
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",
lovelace: demoLovelaceSections,
entities: demoEntitiesSections,
theme: () => ({}),
};
-280
View File
@@ -1,280 +0,0 @@
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: "EV 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.moon_phase",
color: "indigo",
name: "Moon",
},
{
features: [
{
type: "target-temperature",
},
],
type: "tile",
entity: "climate.ground_floor",
state_content: ["preset_mode", "current_temperature"],
},
{
features: [
{
type: "target-temperature",
},
],
type: "tile",
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
View File
@@ -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": [
@@ -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() {
+1
View File
@@ -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() {
+9 -9
View File
@@ -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",
@@ -86,11 +87,10 @@
"@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.7",
"@vaadin/vaadin-themable-mixin": "24.3.7",
"@vibrant/color": "3.2.1-alpha.1",
"@vibrant/core": "3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
@@ -110,7 +110,7 @@
"element-internals-polyfill": "1.3.10",
"fuse.js": "7.0.0",
"google-timezones-json": "1.2.0",
"hls.js": "1.5.6",
"hls.js": "1.5.7",
"home-assistant-js-websocket": "9.1.0",
"idb-keyval": "6.2.1",
"intl-messageformat": "10.5.11",
@@ -159,8 +159,8 @@
"@bundle-stats/plugin-webpack-filter": "4.10.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",
"@octokit/auth-oauth-device": "7.0.0",
"@octokit/plugin-retry": "7.0.1",
"@octokit/rest": "20.0.2",
"@open-wc/dev-server-hmr": "0.1.4",
"@rollup/plugin-babel": "6.0.4",
@@ -185,8 +185,8 @@
"@types/tar": "6.1.11",
"@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29",
"@typescript-eslint/eslint-plugin": "7.0.2",
"@typescript-eslint/parser": "7.0.2",
"@typescript-eslint/eslint-plugin": "7.1.0",
"@typescript-eslint/parser": "7.1.0",
"@web/dev-server": "0.1.38",
"@web/dev-server-rollup": "0.4.1",
"babel-loader": "9.1.3",
@@ -224,7 +224,7 @@
"map-stream": "0.0.7",
"mocha": "10.3.0",
"object-hash": "3.0.0",
"open": "10.0.3",
"open": "10.0.4",
"pinst": "3.0.0",
"prettier": "3.2.5",
"rollup": "2.79.1",
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20240304.0"
version = "20240228.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"
+2 -29
View File
@@ -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 {
+11 -10
View File
@@ -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>`}
+32 -8
View File
@@ -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);
+5
View File
@@ -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>
`;
+123 -47
View File
@@ -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,
};
+2 -2
View File
@@ -3,7 +3,7 @@ import {
HassEntityBase,
} from "home-assistant-js-websocket";
import { getExtendedEntityRegistryEntry } from "./entity_registry";
import { showEnterCodeDialog } from "../dialogs/enter-code/show-enter-code-dialog";
import { showEnterCodeDialogDialog } 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 showEnterCodeDialog(element, {
const response = await showEnterCodeDialogDialog(element, {
codeFormat: "text",
codePattern: stateObj!.attributes.code_format,
title: hass.localize(`ui.card.lock.${service}`),
@@ -10,7 +10,7 @@ export interface EnterCodeDialogParams {
cancel?: () => void;
}
export const showEnterCodeDialog = (
export const showEnterCodeDialogDialog = (
element: HTMLElement,
dialogParams: EnterCodeDialogParams
) =>
@@ -8,7 +8,7 @@ import "../../../components/ha-state-icon";
import { AlarmControlPanelEntity } from "../../../data/alarm_control_panel";
import "../../../state-control/alarm_control_panel/ha-state-control-alarm_control_panel-modes";
import type { HomeAssistant } from "../../../types";
import { showEnterCodeDialog } from "../../enter-code/show-enter-code-dialog";
import { showEnterCodeDialogDialog } from "../../enter-code/show-enter-code-dialog";
import "../components/ha-more-info-state-header";
import { moreInfoControlStyle } from "../components/more-info-control-style";
@@ -22,7 +22,7 @@ class MoreInfoAlarmControlPanel extends LitElement {
let code: string | undefined;
if (this.stateObj!.attributes.code_format) {
const response = await showEnterCodeDialog(this, {
const response = await showEnterCodeDialogDialog(this, {
codeFormat: this.stateObj!.attributes.code_format,
title: this.hass.localize("ui.card.alarm_control_panel.disarm"),
submitText: this.hass.localize("ui.card.alarm_control_panel.disarm"),
+2 -2
View File
@@ -341,7 +341,7 @@ class DialogRestart extends LitElement {
showToast(this, {
message: this.hass.localize("ui.dialogs.restart.reboot.rebooting"),
duration: 0,
duration: -1,
});
try {
@@ -380,7 +380,7 @@ class DialogRestart extends LitElement {
showToast(this, {
message: this.hass.localize("ui.dialogs.restart.shutdown.shutting_down"),
duration: 0,
duration: -1,
});
try {
+58 -51
View File
@@ -1,10 +1,11 @@
import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { html, LitElement, nothing } from "lit";
import { property, state, query } from "lit/decorators";
import { mdiClose } from "@mdi/js";
import { computeRTL } from "../common/util/compute_rtl";
import "../components/ha-toast";
import type { HaToast } from "../components/ha-toast";
import type { HomeAssistant } from "../types";
import "../components/ha-button";
export interface ShowToastParams {
message: string;
@@ -21,72 +22,78 @@ export interface ToastActionParams {
class NotificationManager extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _action?: ToastActionParams;
@state() private _parameters?: ShowToastParams;
@state() private _noCancelOnOutsideClick = false;
@query("ha-toast") private _toast!: HaToast | undefined;
@query("ha-toast") private _toast!: HaToast;
public async showDialog({
message,
action,
duration,
dismissable,
}: ShowToastParams) {
let toast = this._toast;
// Can happen on initial load
if (!toast) {
public async showDialog(parameters: ShowToastParams) {
if (this._parameters) {
this._parameters = undefined;
await this.updateComplete;
toast = this._toast;
}
toast.setAttribute("dir", computeRTL(this.hass) ? "rtl" : "ltr");
this._action = action || undefined;
this._noCancelOnOutsideClick =
dismissable === undefined ? false : !dismissable;
toast.hide();
toast.show({
text: message,
duration: duration === undefined ? 3000 : duration,
});
if (!parameters || parameters.duration === 0) {
return;
}
this._parameters = parameters;
if (
this._parameters.duration === undefined ||
(this._parameters.duration > 0 && this._parameters.duration <= 4000)
) {
this._parameters.duration = 4000;
}
}
protected render(): TemplateResult {
public shouldUpdate(changedProperties) {
return !this._toast || changedProperties.has("_parameters");
}
private _toastClosed() {
this._parameters = undefined;
}
protected render() {
if (!this._parameters) {
return nothing;
}
return html`
<ha-toast .noCancelOnOutsideClick=${this._noCancelOnOutsideClick}>
${this._action
<ha-toast
leading
open
dir=${computeRTL(this.hass) ? "rtl" : "ltr"}
.labelText=${this._parameters.message}
.timeoutMs=${this._parameters.duration!}
@MDCSnackbar:closed=${this._toastClosed}
>
${this._parameters?.action
? html`
<mwc-button
.label=${this._action.text}
<ha-button
slot="action"
.label=${this._parameters?.action.text}
@click=${this.buttonClicked}
></mwc-button>
></ha-button>
`
: ""}
: nothing}
${this._parameters?.dismissable
? html`<ha-icon-button
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
dialogAction="close"
slot="dismiss"
></ha-icon-button>`
: nothing}
</ha-toast>
`;
}
private buttonClicked() {
this._toast.hide();
if (this._action) {
this._action.action();
this._toast?.close("action");
if (this._parameters?.action) {
this._parameters?.action.action();
}
}
static get styles(): CSSResultGroup {
return css`
ha-toast {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
}
mwc-button {
color: var(--primary-color);
font-weight: bold;
margin-left: 8px;
}
`;
}
}
customElements.define("notification-manager", NotificationManager);
@@ -93,7 +93,7 @@ export class HaAutomationTrace extends LitElement {
let devButtons: TemplateResult | string = "";
if (__DEV__) {
devButtons = html`<div style="position: absolute; right: 0;">
devButtons = html`<div style="position: absolute; right: 0; z-index: 1;">
<button @click=${this._importTrace}>Import trace</button>
<button @click=${this._loadLocalStorageTrace}>Load stored trace</button>
</div>`;
+1 -1
View File
@@ -811,7 +811,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
"ui.panel.config.script.editor.id_already_exists_save_error"
),
dismissable: false,
duration: 0,
duration: -1,
action: {
action: () => {},
text: this.hass.localize("ui.dialogs.generic.ok"),
@@ -21,7 +21,7 @@ import { UNAVAILABLE } from "../../../data/entity";
import { HomeAssistant } from "../../../types";
import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { AlarmModesCardFeatureConfig } from "./types";
import { showEnterCodeDialog } from "../../../dialogs/enter-code/show-enter-code-dialog";
import { showEnterCodeDialogDialog } from "../../../dialogs/enter-code/show-enter-code-dialog";
export const supportsAlarmModesCardFeature = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
@@ -131,7 +131,7 @@ class HuiAlarmModeCardFeature
) {
const disarm = mode === "disarmed";
const response = await showEnterCodeDialog(this, {
const response = await showEnterCodeDialogDialog(this, {
codeFormat: this.stateObj!.attributes.code_format,
title: this.hass!.localize(
`ui.card.alarm_control_panel.${disarm ? "disarm" : "arm"}`
@@ -96,10 +96,7 @@ export class HuiEnergyDevicesDetailGraphCard
}
protected willUpdate(changedProps: PropertyValues) {
if (
(changedProps.has("_hiddenStats") || changedProps.has("_config")) &&
this._data
) {
if (changedProps.has("_hiddenStats") && this._data) {
this._processStatistics();
}
}
@@ -220,17 +217,17 @@ export class HuiEnergyDevicesDetailGraphCard
const datasets: ChartDataset<"bar", ScatterDataPoint[]>[] = [];
const datasetExtras: ChartDatasetExtra[] = [];
const { data: processedData, dataExtras: processedDataExtras } =
this._processDataSet(
datasets.push(
...this._processDataSet(
data,
energyData.statsMetadata,
energyData.prefs.device_consumption,
sorted_devices
);
)
);
datasets.push(...processedData);
datasetExtras.push(...processedDataExtras);
const items = datasets.length;
datasetExtras.push(...Array<ChartDatasetExtra>(items).fill({}));
if (compareData) {
// Add empty dataset to align the bars
@@ -250,19 +247,18 @@ export class HuiEnergyDevicesDetailGraphCard
show_legend: false,
});
const {
data: processedCompareData,
dataExtras: processedCompareDataExtras,
} = this._processDataSet(
compareData,
energyData.statsMetadata,
energyData.prefs.device_consumption,
sorted_devices,
true
datasets.push(
...this._processDataSet(
compareData,
energyData.statsMetadata,
energyData.prefs.device_consumption,
sorted_devices,
true
)
);
datasetExtras.push(
...Array<ChartDatasetExtra>(items).fill({ show_legend: false })
);
datasets.push(...processedCompareData);
datasetExtras.push(...processedCompareDataExtras);
}
this._start = energyData.start;
@@ -285,7 +281,6 @@ export class HuiEnergyDevicesDetailGraphCard
compare = false
) {
const data: ChartDataset<"bar", ScatterDataPoint[]>[] = [];
const dataExtras: ChartDatasetExtra[] = [];
devices.forEach((source, idx) => {
const color = getColorByIndex(idx);
@@ -322,30 +317,23 @@ export class HuiEnergyDevicesDetailGraphCard
}
}
const order = sorted_devices.indexOf(source.stat_consumption);
const itemExceedsMax = !!(
this._config?.max_devices && order >= this._config.max_devices
);
data.push({
label: getStatisticLabel(
this.hass,
source.stat_consumption,
statisticsMetaData[source.stat_consumption]
),
hidden:
this._hiddenStats.has(source.stat_consumption) || itemExceedsMax,
hidden: this._hiddenStats.has(source.stat_consumption),
borderColor: compare ? color + "7F" : color,
backgroundColor: compare ? color + "32" : color + "7F",
data: consumptionData,
order: 1 + order,
order: 1 + sorted_devices.indexOf(source.stat_consumption),
stack: "devices",
pointStyle: compare ? false : "circle",
xAxisID: compare ? "xAxisCompare" : undefined,
});
dataExtras.push({ show_legend: !compare && !itemExceedsMax });
});
return { data, dataExtras };
return data;
}
static get styles(): CSSResultGroup {
@@ -177,7 +177,7 @@ class HuiEnergyDistrubutionCard
if (hasBattery) {
batteryFromGrid = solarConsumption * -1;
if (batteryFromGrid > totalFromGrid) {
batteryToGrid = batteryFromGrid - totalFromGrid;
batteryToGrid = Math.min(0, batteryFromGrid - totalFromGrid);
batteryFromGrid = totalFromGrid;
}
}
@@ -150,7 +150,7 @@ class HuiEnergySelfSufficiencyGaugeCard
if (hasBattery) {
batteryFromGrid = solarConsumption * -1;
if (batteryFromGrid > totalFromGrid) {
batteryToGrid = batteryFromGrid - totalFromGrid;
batteryToGrid = Math.min(0, batteryFromGrid - totalFromGrid);
batteryFromGrid = totalFromGrid;
}
}
@@ -434,8 +434,10 @@ export class HuiEnergyUsageGraphCard
if (summedData.to_battery) {
grid_to_battery[start] = used_solar[start] * -1;
if (grid_to_battery[start] > (summedData.from_grid?.[start] || 0)) {
battery_to_grid[start] =
grid_to_battery[start] - (summedData.from_grid?.[start] || 0);
battery_to_grid[start] = Math.min(
0,
grid_to_battery[start] - (summedData.from_grid?.[start] || 0)
);
grid_to_battery[start] = summedData.from_grid?.[start];
}
}
@@ -112,9 +112,9 @@ export const computeSection = (
({
type: "tile",
entity,
show_entity_picture:
["person", "camera", "image"].includes(computeDomain(entity)) ||
undefined,
show_entity_picture: ["person", "camera", "image"].includes(
computeDomain(entity)
),
}) as TileCardConfig
),
...sectionOptions,
@@ -1,9 +1,3 @@
export { showEnterCodeDialog } from "../../dialogs/enter-code/show-enter-code-dialog";
export {
showAlertDialog,
showConfirmationDialog,
showPromptDialog,
} from "../../dialogs/generic/show-dialog-box";
export { importMoreInfoControl } from "../../dialogs/more-info/state_more_info_control";
export { createBadgeElement } from "./create-element/create-badge-element";
export { createCardElement } from "./create-element/create-card-element";
@@ -131,15 +131,11 @@ export class HuiDialogSuggestCard extends LitElement {
</mwc-button>
${!this._params.yaml
? html`
${!(this._sectionConfig && this._viewSupportsSection)
? html`
<mwc-button slot="primaryAction" @click=${this._pickCard}>
${this.hass!.localize(
"ui.panel.lovelace.editor.suggest_card.create_own"
)}
</mwc-button>
`
: nothing}
<mwc-button slot="primaryAction" @click=${this._pickCard}
>${this.hass!.localize(
"ui.panel.lovelace.editor.suggest_card.create_own"
)}</mwc-button
>
<mwc-button
slot="primaryAction"
.disabled=${this._saving}
@@ -158,7 +154,7 @@ export class HuiDialogSuggestCard extends LitElement {
)}
</mwc-button>
`
: nothing}
: ""}
</ha-dialog>
`;
}
@@ -23,6 +23,7 @@ import "../../../../components/ha-dialog";
import "../../../../components/ha-dialog-header";
import "../../../../components/ha-yaml-editor";
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
import { LovelaceBadgeConfig } from "../../../../data/lovelace/config/badge";
import {
LovelaceViewConfig,
isStrategyView,
@@ -60,6 +61,8 @@ export class HuiDialogEditView extends LitElement {
@state() private _config?: LovelaceViewConfig;
@state() private _badges?: LovelaceBadgeConfig[];
@state() private _saving = false;
@state() private _curTab?: string;
@@ -85,6 +88,7 @@ export class HuiDialogEditView extends LitElement {
if (this._yamlMode && changedProperties.has("_yamlMode")) {
const viewConfig = {
...this._config,
badges: this._badges,
};
this._editor?.setValue(viewConfig);
}
@@ -95,6 +99,7 @@ export class HuiDialogEditView extends LitElement {
if (this._params.viewIndex === undefined) {
this._config = {};
this._badges = [];
this._dirty = false;
return;
}
@@ -103,15 +108,19 @@ export class HuiDialogEditView extends LitElement {
if (isStrategyView(view)) {
const { strategy, ...viewConfig } = view;
this._config = viewConfig;
this._badges = [];
return;
}
this._config = view;
const { badges, ...viewConfig } = view;
this._config = viewConfig;
this._badges = badges ? processEditorEntities(badges) : [];
}
public closeDialog(): void {
this._curTabIndex = 0;
this._params = undefined;
this._config = {};
this._badges = [];
this._yamlMode = false;
this._dirty = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
@@ -157,7 +166,7 @@ export class HuiDialogEditView extends LitElement {
break;
case "tab-badges":
content = html`
${this._config?.badges?.length
${this._badges?.length
? html`
${VIEWS_NO_BADGE_SUPPORT.includes(this._type)
? html`
@@ -169,7 +178,7 @@ export class HuiDialogEditView extends LitElement {
`
: nothing}
<div class="preview-badges">
${this._config.badges.map(
${this._badges.map(
(badgeConfig) => html`
<hui-badge-preview
.hass=${this.hass}
@@ -182,7 +191,7 @@ export class HuiDialogEditView extends LitElement {
: nothing}
<hui-entity-editor
.hass=${this.hass}
.entities=${this._config?.badges || []}
.entities=${this._badges}
@entities-changed=${this._badgesChanged}
></hui-entity-editor>
`;
@@ -202,13 +211,6 @@ export class HuiDialogEditView extends LitElement {
}
}
const isCompatibleViewType =
this._config?.type === SECTION_VIEW_LAYOUT
? this._config?.type === SECTION_VIEW_LAYOUT &&
!this._config?.cards?.length
: this._config?.type !== SECTION_VIEW_LAYOUT &&
!this._config?.sections?.length;
return html`
<ha-dialog
open
@@ -267,19 +269,6 @@ export class HuiDialogEditView extends LitElement {
: ``}
</mwc-list-item>
</ha-button-menu>
${!isCompatibleViewType
? html`
<ha-alert class="incompatible" alert-type="warning">
${this._config?.type === SECTION_VIEW_LAYOUT
? this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.type_warning_sections"
)
: this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.type_warning_others"
)}
</ha-alert>
`
: nothing}
${!this._yamlMode
? html`<paper-tabs
scrollable
@@ -321,10 +310,7 @@ export class HuiDialogEditView extends LitElement {
: nothing}
<mwc-button
slot="primaryAction"
?disabled=${!this._config ||
this._saving ||
!this._dirty ||
!isCompatibleViewType}
?disabled=${!this._config || this._saving || !this._dirty}
@click=${this._save}
>
${this._saving
@@ -412,12 +398,13 @@ export class HuiDialogEditView extends LitElement {
this._saving = true;
const viewConf = {
const viewConf: LovelaceViewConfig = {
...this._config,
badges: this._badges,
};
if (viewConf.type === SECTION_VIEW_LAYOUT && !viewConf.sections?.length) {
viewConf.sections = [{ type: "grid", cards: [] }];
viewConf.sections = [{ cards: [] }];
} else if (!viewConf.cards?.length) {
viewConf.cards = [];
}
@@ -478,13 +465,10 @@ export class HuiDialogEditView extends LitElement {
}
private _badgesChanged(ev: EntitiesEditorEvent): void {
if (!this.hass || !ev.detail || !ev.detail.entities) {
if (!this._badges || !this.hass || !ev.detail || !ev.detail.entities) {
return;
}
this._config = {
...this._config,
badges: processEditorEntities(ev.detail.entities),
};
this._badges = processEditorEntities(ev.detail.entities);
this._dirty = true;
}
@@ -495,6 +479,7 @@ export class HuiDialogEditView extends LitElement {
}
const { badges, ...config } = ev.detail.value;
this._config = config;
this._badges = badges;
this._dirty = true;
}
@@ -569,9 +554,6 @@ export class HuiDialogEditView extends LitElement {
margin: 12px 16px;
flex-wrap: wrap;
}
.incompatible {
display: block;
}
@media all and (min-width: 600px) {
ha-dialog {
@@ -37,7 +37,7 @@ export class HuiViewEditor extends LitElement {
private _suggestedPath = false;
private _schema = memoizeOne(
(localize: LocalizeFunc) =>
(localize: LocalizeFunc, currentType: string, isNew: boolean) =>
[
{ name: "title", selector: { text: {} } },
{
@@ -64,6 +64,11 @@ export class HuiViewEditor extends LitElement {
label: localize(
`ui.panel.lovelace.editor.edit_view.types.${type}`
),
disabled:
!isNew &&
(currentType === SECTION_VIEW_LAYOUT
? type !== SECTION_VIEW_LAYOUT
: type === SECTION_VIEW_LAYOUT),
})),
},
},
@@ -90,12 +95,16 @@ export class HuiViewEditor extends LitElement {
: this._config.type || DEFAULT_VIEW_LAYOUT;
}
private get _isEmpty(): boolean {
return !this._config.sections?.length && !this._config.cards?.length;
}
protected render() {
if (!this.hass) {
return nothing;
}
const schema = this._schema(this.hass.localize);
const schema = this._schema(this.hass.localize, this._type, this._isEmpty);
const data = {
...this._config,
@@ -159,6 +168,15 @@ export class HuiViewEditor extends LitElement {
return this.hass.localize(
"ui.panel.lovelace.editor.edit_view.subview_helper"
);
case "type":
if (this._isEmpty) return undefined;
return this._type === "sections"
? this.hass.localize(
"ui.panel.lovelace.editor.edit_view.type_helper_others"
)
: this.hass.localize(
"ui.panel.lovelace.editor.edit_view.type_helper_sections"
);
default:
return undefined;
}
+1 -1
View File
@@ -221,7 +221,7 @@ export class LovelacePanel extends LitElement {
action: () => this._fetchConfig(false),
text: this.hass!.localize("ui.common.refresh"),
},
duration: 0,
duration: -1,
dismissable: false,
});
}
+1 -1
View File
@@ -132,7 +132,7 @@ class LovelaceFullConfigEditor extends LitElement {
"ui.panel.lovelace.editor.raw_editor.reload"
),
},
duration: 0,
duration: -1,
dismissable: false,
});
}
@@ -96,7 +96,6 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
(_cardConfig, idx) => {
const card = this.cards![idx];
(card as any).editMode = editMode;
(card as any).lovelace = this.lovelace;
const size = card && (card as any).getGridSize?.();
return html`
<div
@@ -216,14 +215,6 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
grid-column: span var(--column-size, 4);
}
.card:has(> *) {
display: block;
}
.card:has(> *[hidden]) {
display: none;
}
.add {
outline: none;
grid-row: span var(--row-size, 1);
@@ -291,12 +291,6 @@ export class MasonryView extends LitElement implements LovelaceViewElement {
padding-top: 4px;
}
.badges {
margin: 8px 16px;
font-size: 85%;
text-align: center;
}
#columns {
display: flex;
flex-direction: row;
+84 -15
View File
@@ -1,4 +1,11 @@
import { mdiArrowAll, mdiDelete, mdiPencil, mdiViewGridPlus } from "@mdi/js";
import {
mdiArrowAll,
mdiArrowDown,
mdiArrowUp,
mdiDelete,
mdiPencil,
mdiViewGridPlus,
} from "@mdi/js";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
@@ -21,6 +28,8 @@ import {
} from "../editor/lovelace-path";
import { HuiSection } from "../sections/hui-section";
import type { Lovelace, LovelaceBadge } from "../types";
import { isTouch } from "../../../util/is_touch";
import { listenMediaQuery } from "../../../common/dom/media_query";
@customElement("hui-sections-view")
export class SectionsView extends LitElement implements LovelaceViewElement {
@@ -38,6 +47,23 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
@state() private _config?: LovelaceViewConfig;
@state() private _narrow = false;
private _unsubMql?: () => void;
public connectedCallback() {
super.connectedCallback();
this._unsubMql = listenMediaQuery("(max-width: 600px)", (matches) => {
this._narrow = matches;
});
}
public disconnectedCallback() {
super.disconnectedCallback();
this._unsubMql?.();
this._unsubMql = undefined;
}
public setConfig(config: LovelaceViewConfig): void {
this._config = config;
}
@@ -58,12 +84,14 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
const editMode = this.lovelace.editMode;
const supportDnD = !(isTouch && this._narrow);
return html`
${this.badges.length > 0
? html`<div class="badges">${this.badges}</div>`
: ""}
<ha-sortable
.disabled=${!editMode}
.disabled=${!editMode && !supportDnD}
@item-moved=${this._sectionMoved}
group="section"
handle-selector=".handle"
@@ -90,11 +118,28 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
? html`
<div class="section-overlay">
<div class="section-actions">
<ha-svg-icon
aria-hidden="true"
class="handle"
.path=${mdiArrowAll}
></ha-svg-icon>
${supportDnD
? html`
<ha-svg-icon
aria-hidden="true"
class="handle"
.path=${mdiArrowAll}
></ha-svg-icon>
`
: html`
<ha-icon-button
.label=${"Down"}
@click=${this._moveDown}
.index=${idx}
.path=${mdiArrowDown}
></ha-icon-button>
<ha-icon-button
.label=${"Up"}
@click=${this._moveUp}
.index=${idx}
.path=${mdiArrowUp}
></ha-icon-button>
`}
<ha-icon-button
.label=${this.hass.localize("ui.common.edit")}
@click=${this._editSection}
@@ -119,13 +164,13 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
${editMode
? html`
<button
class="create"
@click=${this._createSection}
class="add"
@click=${this._addSection}
aria-label=${this.hass.localize(
"ui.panel.lovelace.editor.section.create_section"
"ui.panel.lovelace.editor.section.add_section"
)}
.title=${this.hass.localize(
"ui.panel.lovelace.editor.section.create_section"
"ui.panel.lovelace.editor.section.add_section"
)}
>
<ha-svg-icon .path=${mdiViewGridPlus}></ha-svg-icon>
@@ -137,7 +182,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
`;
}
private _createSection(): void {
private _addSection(): void {
const newConfig = addSection(this.lovelace!.config, this.index!, {
type: "grid",
cards: [],
@@ -231,6 +276,30 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
this.lovelace!.saveConfig(newConfig);
}
private _moveDown(ev) {
ev.stopPropagation();
const { index } = ev.currentTarget;
const newConfig = moveSection(
this.lovelace!.config,
[this.index!, index],
[this.index!, index + 1]
);
this.lovelace!.saveConfig(newConfig);
}
private _moveUp(ev) {
ev.stopPropagation();
const { index } = ev.currentTarget;
const newConfig = moveSection(
this.lovelace!.config,
[this.index!, index],
[this.index!, index - 1]
);
this.lovelace!.saveConfig(newConfig);
}
static get styles(): CSSResultGroup {
return css`
:host {
@@ -283,7 +352,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
.section-actions {
position: absolute;
top: 0;
top: 20px;
right: 0;
opacity: 1;
display: flex;
@@ -303,7 +372,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
padding: 8px;
}
.create {
.add {
margin-top: calc(66px + 8px);
outline: none;
background: none;
@@ -316,7 +385,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
box-sizing: content-box;
}
.create:focus {
.add:focus {
border: 2px solid var(--primary-color);
}
@@ -13,7 +13,7 @@ import {
AlarmMode,
} from "../../data/alarm_control_panel";
import { UNAVAILABLE } from "../../data/entity";
import { showEnterCodeDialog } from "../../dialogs/enter-code/show-enter-code-dialog";
import { showEnterCodeDialogDialog } from "../../dialogs/enter-code/show-enter-code-dialog";
import { HomeAssistant } from "../../types";
@customElement("ha-state-control-alarm_control_panel-modes")
@@ -56,7 +56,7 @@ export class HaStateControlAlarmControlPanelModes extends LitElement {
) {
const disarm = mode === "disarmed";
const response = await showEnterCodeDialog(this, {
const response = await showEnterCodeDialogDialog(this, {
codeFormat: this.stateObj!.attributes.code_format,
title: this.hass!.localize(
`ui.card.alarm_control_panel.${disarm ? "disarm" : "arm"}`
+5 -5
View File
@@ -38,7 +38,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
message:
this.hass!.localize("ui.notification_toast.starting") ||
"Home Assistant is starting, not everything will be available until it is finished.",
duration: 0,
duration: -1,
dismissable: false,
action: {
text:
@@ -97,7 +97,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
}
showToast(this, {
message: "",
duration: 1,
duration: 0,
});
}
@@ -108,7 +108,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
this._disconnectedTimeout = undefined;
showToast(this, {
message: this.hass!.localize("ui.notification_toast.connection_lost"),
duration: 0,
duration: -1,
dismissable: false,
});
}, 1000);
@@ -124,7 +124,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
message:
this.hass!.localize("ui.notification_toast.wrapping_up_startup") ||
`Wrapping up startup, not everything will be available until it is finished.`,
duration: 0,
duration: -1,
dismissable: false,
action: {
text:
@@ -148,7 +148,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
integration: domainToName(this.hass!.localize, integration),
}) ||
`Starting ${integration}, not everything will be available until it is finished.`,
duration: 0,
duration: -1,
dismissable: false,
action: {
text:
+26 -3
View File
@@ -3195,6 +3195,29 @@
"no_logbook_entries": "No Logbook entries found for this step.",
"no_variables_changed": "No variables changed",
"unable_to_find_config": "Unable to find config"
},
"messages": {
"no_action_executed": "{name}: No action executed",
"default_action_executed": "{name}: Default action executed",
"action_executed": "{action} action executed",
"option_executed": "Option {option} executed",
"error": "Error: {error}",
"execute_in_parallel": "Execute in parallel",
"if": "If",
"choose": "Choose",
"still_running": "Still running",
"debugged": "Debugged",
"finished": "Finished at {time} (runtime: {executiontime} seconds)",
"aborted": "Aborted at {time} (runtime: {executiontime} seconds)",
"cancelled": "Cancelled at {time} (runtime: {executiontime} seconds)",
"stopped_failed_conditions": "Stopped because a condition failed at {time} (runtime: {executiontime} seconds)",
"stopped_failed_single": "Stopped because only a single execution is allowed at {time} (runtime: {executiontime} seconds)",
"stopped_failed_max_runs": "Stopped because maximum number of parallel runs reached at {time} (runtime: {executiontime} seconds)",
"stopped_error": "Stopped because an error was encountered at {time} (runtime: {executiontime} seconds)",
"stopped_unknown_reason": "Stopped because of unknown reason {reason} at {time} (runtime: {executiontime} seconds)",
"disabled": "(disabled)",
"triggered_by": "{triggeredBy, select, \n alias {{alias} triggered}\n other {Triggered} \n} {triggeredPath, select, \n trigger {by the {trigger}}\n other {manually} \n} at {time}",
"path_error": "Unable to extract path {path}. Download trace and report as bug."
}
}
},
@@ -5112,8 +5135,8 @@
"select_users": "Select which users should see this view in the navigation"
},
"type": "View type",
"type_warning_sections": "You can not change your view to use the 'sections' view type because migration is not supported yet. Start from scratch with a new view if you want to experiment with the 'sections' view.",
"type_warning_others": "You can not change your view to an other type because migration is not supported yet. Start from scratch with a new view if you want to use another view type.",
"type_helper_sections": "You can not change your view to use the 'sections' view type because migration is not supported yet. Start from scratch with a new view if you want to experiment with the 'sections' view.",
"type_helper_others": "You can not change your view to an other type because migration is not supported yet. Start from scratch with a new view if you want to use another view type.",
"types": {
"masonry": "Masonry (default)",
@@ -5176,7 +5199,7 @@
"section": {
"unnamed_section": "Unnamed section",
"add_card": "[%key:ui::panel::lovelace::editor::edit_card::add%]",
"create_section": "Create section"
"add_section": "Add section"
},
"delete_section": {
"title": "Delete section",
+1 -1
View File
@@ -48,7 +48,7 @@ export const registerServiceWorker = async (
action: () => installingWorker.postMessage({ type: "skipWaiting" }),
text: "reload",
},
duration: 0,
duration: -1,
dismissable: false,
});
});
+842 -763
View File
File diff suppressed because it is too large Load Diff