diff --git a/cast/public/images/favicon.ico b/cast/public/images/favicon.ico index 6d12158c18b1..038be624465c 100644 Binary files a/cast/public/images/favicon.ico and b/cast/public/images/favicon.ico differ diff --git a/cast/public/images/google-nest-hub.png b/cast/public/images/google-nest-hub.png index d5a4ccf93739..d214552360ae 100644 Binary files a/cast/public/images/google-nest-hub.png and b/cast/public/images/google-nest-hub.png differ diff --git a/cast/public/images/ha-cast-icon.png b/cast/public/images/ha-cast-icon.png index 52db6718f42c..d26142baafdd 100644 Binary files a/cast/public/images/ha-cast-icon.png and b/cast/public/images/ha-cast-icon.png differ diff --git a/cast/src/html/launcher-faq.html.template b/cast/src/html/launcher-faq.html.template index e220a1e1ce26..70aeb6a90548 100644 --- a/cast/src/html/launcher-faq.html.template +++ b/cast/src/html/launcher-faq.html.template @@ -212,13 +212,8 @@ Chromecast is a technology developed by Google, and is available on:

diff --git a/cast/src/receiver/entrypoint.ts b/cast/src/receiver/entrypoint.ts index c52c13704850..2807d597a7ec 100644 --- a/cast/src/receiver/entrypoint.ts +++ b/cast/src/receiver/entrypoint.ts @@ -6,13 +6,58 @@ import { castContext } from "./cast_context"; import { HcMain } from "./layout/hc-main"; import { ReceivedMessage } from "./types"; -const controller = new HcMain(); -document.body.append(controller); +const lovelaceController = new HcMain(); +document.body.append(lovelaceController); + +const mediaPlayer = document.createElement("cast-media-player"); +mediaPlayer.style.display = "none"; +document.body.append(mediaPlayer); +const playerStylesAdded = false; + +let controls: HTMLElement | null; + +const setTouchControlsVisibility = (visible: boolean) => { + if (!castContext.getDeviceCapabilities().touch_input_supported) { + return; + } + controls = + controls || + (document.body.querySelector("touch-controls") as HTMLElement | null); + if (controls) { + controls.style.display = visible ? "initial" : "none"; + } +}; + +const showLovelaceController = () => { + mediaPlayer.style.display = "none"; + lovelaceController.style.display = "initial"; + document.body.setAttribute("style", "overflow-y: auto !important"); + setTouchControlsVisibility(false); +}; + +const showMediaPlayer = () => { + lovelaceController.style.display = "none"; + mediaPlayer.style.display = "initial"; + document.body.removeAttribute("style"); + setTouchControlsVisibility(true); + if (!playerStylesAdded) { + const style = document.createElement("style"); + style.innerHTML = ` + body { + --logo-image: url('https://www.home-assistant.io/images/home-assistant-logo.svg'); + --theme-hue: 200; + --progress-color: #03a9f4; + --splash-image: url('https://home-assistant.io/images/cast/splash.png'); + --splash-size: cover; + } + `; + document.head.appendChild(style); + } +}; const options = new cast.framework.CastReceiverOptions(); options.disableIdleTimeout = true; options.customNamespaces = { - // @ts-ignore [CAST_NS]: cast.framework.system.MessageType.JSON, }; @@ -30,13 +75,61 @@ options.uiConfig = new cast.framework.ui.UiConfig(); // @ts-ignore options.uiConfig.touchScreenOptimizedApp = true; +castContext.setInactivityTimeout(86400); // 1 day + castContext.addCustomMessageListener( CAST_NS, // @ts-ignore (ev: ReceivedMessage) => { + // We received a show Lovelace command, stop media from playing, hide media player and show Lovelace controller + if ( + playerManager.getPlayerState() !== + cast.framework.messages.PlayerState.IDLE + ) { + playerManager.stop(); + } else { + showLovelaceController(); + } const msg = ev.data; msg.senderId = ev.senderId; - controller.processIncomingMessage(msg); + lovelaceController.processIncomingMessage(msg); + } +); + +const playerManager = castContext.getPlayerManager(); + +playerManager.setMessageInterceptor( + cast.framework.messages.MessageType.LOAD, + (loadRequestData) => { + // We received a play media command, hide Lovelace and show media player + showMediaPlayer(); + const media = loadRequestData.media; + // Special handling if it came from Google Assistant + if (media.entity) { + media.contentId = media.entity; + media.streamType = cast.framework.messages.StreamType.LIVE; + media.contentType = "application/vnd.apple.mpegurl"; + // @ts-ignore + media.hlsVideoSegmentFormat = + cast.framework.messages.HlsVideoSegmentFormat.FMP4; + } + return loadRequestData; + } +); + +playerManager.addEventListener( + cast.framework.events.EventType.MEDIA_STATUS, + (event) => { + if ( + event.mediaStatus?.playerState === + cast.framework.messages.PlayerState.IDLE && + event.mediaStatus?.idleReason && + event.mediaStatus?.idleReason !== + cast.framework.messages.IdleReason.INTERRUPTED + ) { + // media finished or stopped, return to default Lovelace + showLovelaceController(); + } } ); diff --git a/cast/src/receiver/layout/hc-lovelace.ts b/cast/src/receiver/layout/hc-lovelace.ts index c0d1dc1dfed9..0151f39073f8 100644 --- a/cast/src/receiver/layout/hc-lovelace.ts +++ b/cast/src/receiver/layout/hc-lovelace.ts @@ -9,7 +9,6 @@ import { } from "lit-element"; import { LovelaceConfig } from "../../../../src/data/lovelace"; import { Lovelace } from "../../../../src/panels/lovelace/types"; -import "../../../../src/panels/lovelace/views/hui-panel-view"; import "../../../../src/panels/lovelace/views/hui-view"; import { HomeAssistant } from "../../../../src/types"; import "./hc-launch-screen"; @@ -45,22 +44,14 @@ class HcLovelace extends LitElement { deleteConfig: async () => undefined, setEditMode: () => undefined, }; - return this.lovelaceConfig.views[index].panel - ? html` - - ` - : html` - - `; + return html` + + `; } protected updated(changedProps) { diff --git a/cast/src/receiver/layout/hc-main.ts b/cast/src/receiver/layout/hc-main.ts index 03abab575b36..e035d5dd4e8f 100644 --- a/cast/src/receiver/layout/hc-main.ts +++ b/cast/src/receiver/layout/hc-main.ts @@ -216,9 +216,7 @@ export class HcMain extends HassElement { } this._showDemo = false; this._lovelacePath = msg.viewPath; - if (castContext.getDeviceCapabilities().touch_input_supported) { - this._breakFree(); - } + this._sendStatus(); } @@ -241,9 +239,6 @@ export class HcMain extends HassElement { this._showDemo = true; this._lovelacePath = "overview"; this._sendStatus(); - if (castContext.getDeviceCapabilities().touch_input_supported) { - this._breakFree(); - } }); } @@ -264,14 +259,6 @@ export class HcMain extends HassElement { } } - private _breakFree() { - const controls = document.body.querySelector("touch-controls"); - if (controls) { - controls.remove(); - } - document.body.setAttribute("style", "overflow-y: auto !important"); - } - private sendMessage(senderId: string, response: any) { castContext.sendCustomMessage(CAST_NS, senderId, response); } diff --git a/demo/public/assets/arsaboo/floorplans/ecobee_blank.png b/demo/public/assets/arsaboo/floorplans/ecobee_blank.png index b2f6cec0bb7c..3d5e3edf5df0 100644 Binary files a/demo/public/assets/arsaboo/floorplans/ecobee_blank.png and b/demo/public/assets/arsaboo/floorplans/ecobee_blank.png differ diff --git a/demo/public/assets/arsaboo/floorplans/main.png b/demo/public/assets/arsaboo/floorplans/main.png index 859842ea444b..f544bd4ce479 100644 Binary files a/demo/public/assets/arsaboo/floorplans/main.png and b/demo/public/assets/arsaboo/floorplans/main.png differ diff --git a/demo/public/assets/arsaboo/floorplans/second.png b/demo/public/assets/arsaboo/floorplans/second.png index 08624c75109f..8cb3ace6dbd9 100644 Binary files a/demo/public/assets/arsaboo/floorplans/second.png and b/demo/public/assets/arsaboo/floorplans/second.png differ diff --git a/demo/public/assets/arsaboo/icons/Harmony.png b/demo/public/assets/arsaboo/icons/Harmony.png index f26c4b8e380b..4f7eeee28c85 100644 Binary files a/demo/public/assets/arsaboo/icons/Harmony.png and b/demo/public/assets/arsaboo/icons/Harmony.png differ diff --git a/demo/public/assets/arsaboo/icons/abode_disabled.png b/demo/public/assets/arsaboo/icons/abode_disabled.png index fbe4f3d7f330..01cbaaa1eeed 100644 Binary files a/demo/public/assets/arsaboo/icons/abode_disabled.png and b/demo/public/assets/arsaboo/icons/abode_disabled.png differ diff --git a/demo/public/assets/arsaboo/icons/abode_enabled.png b/demo/public/assets/arsaboo/icons/abode_enabled.png index 1104fdb9bdd0..2e5709e454d2 100644 Binary files a/demo/public/assets/arsaboo/icons/abode_enabled.png and b/demo/public/assets/arsaboo/icons/abode_enabled.png differ diff --git a/demo/public/assets/arsaboo/icons/automation_disabled.png b/demo/public/assets/arsaboo/icons/automation_disabled.png index fe2df80beb6b..ebba057fb17c 100644 Binary files a/demo/public/assets/arsaboo/icons/automation_disabled.png and b/demo/public/assets/arsaboo/icons/automation_disabled.png differ diff --git a/demo/public/assets/arsaboo/icons/automation_enabled.png b/demo/public/assets/arsaboo/icons/automation_enabled.png index fc8d949fb818..17f3a20e91d1 100644 Binary files a/demo/public/assets/arsaboo/icons/automation_enabled.png and b/demo/public/assets/arsaboo/icons/automation_enabled.png differ diff --git a/demo/public/assets/arsaboo/icons/camera_backyard_recording.png b/demo/public/assets/arsaboo/icons/camera_backyard_recording.png index f31d70f3d044..b2a40d61a60d 100644 Binary files a/demo/public/assets/arsaboo/icons/camera_backyard_recording.png and b/demo/public/assets/arsaboo/icons/camera_backyard_recording.png differ diff --git a/demo/public/assets/arsaboo/icons/camera_backyard_streaming.png b/demo/public/assets/arsaboo/icons/camera_backyard_streaming.png index 47957012324e..086649467f0c 100644 Binary files a/demo/public/assets/arsaboo/icons/camera_backyard_streaming.png and b/demo/public/assets/arsaboo/icons/camera_backyard_streaming.png differ diff --git a/demo/public/assets/arsaboo/icons/camera_driveway_recording.png b/demo/public/assets/arsaboo/icons/camera_driveway_recording.png index 391c40085fd8..906509c88853 100644 Binary files a/demo/public/assets/arsaboo/icons/camera_driveway_recording.png and b/demo/public/assets/arsaboo/icons/camera_driveway_recording.png differ diff --git a/demo/public/assets/arsaboo/icons/camera_driveway_streaming.png b/demo/public/assets/arsaboo/icons/camera_driveway_streaming.png index defdb5f4a4c6..33418e1ddee8 100644 Binary files a/demo/public/assets/arsaboo/icons/camera_driveway_streaming.png and b/demo/public/assets/arsaboo/icons/camera_driveway_streaming.png differ diff --git a/demo/public/assets/arsaboo/icons/camera_patio_recording.png b/demo/public/assets/arsaboo/icons/camera_patio_recording.png index a598d8f71164..ee74d93147e5 100644 Binary files a/demo/public/assets/arsaboo/icons/camera_patio_recording.png and b/demo/public/assets/arsaboo/icons/camera_patio_recording.png differ diff --git a/demo/public/assets/arsaboo/icons/camera_patio_streaming.png b/demo/public/assets/arsaboo/icons/camera_patio_streaming.png index e5c8f4ecacb6..42f3cef19af2 100644 Binary files a/demo/public/assets/arsaboo/icons/camera_patio_streaming.png and b/demo/public/assets/arsaboo/icons/camera_patio_streaming.png differ diff --git a/demo/public/assets/arsaboo/icons/camera_porch_recording.png b/demo/public/assets/arsaboo/icons/camera_porch_recording.png index a0a652929f76..33b4dde5f3d2 100644 Binary files a/demo/public/assets/arsaboo/icons/camera_porch_recording.png and b/demo/public/assets/arsaboo/icons/camera_porch_recording.png differ diff --git a/demo/public/assets/arsaboo/icons/camera_porch_streaming.png b/demo/public/assets/arsaboo/icons/camera_porch_streaming.png index aa3f71104c80..2403f37f219a 100644 Binary files a/demo/public/assets/arsaboo/icons/camera_porch_streaming.png and b/demo/public/assets/arsaboo/icons/camera_porch_streaming.png differ diff --git a/demo/public/assets/arsaboo/icons/ecobee_blank.png b/demo/public/assets/arsaboo/icons/ecobee_blank.png index 26c49c1c2a15..0c3169b6c6e2 100644 Binary files a/demo/public/assets/arsaboo/icons/ecobee_blank.png and b/demo/public/assets/arsaboo/icons/ecobee_blank.png differ diff --git a/demo/public/assets/arsaboo/icons/garage_door_closed.png b/demo/public/assets/arsaboo/icons/garage_door_closed.png index 927fba385611..14a8f46b9de0 100644 Binary files a/demo/public/assets/arsaboo/icons/garage_door_closed.png and b/demo/public/assets/arsaboo/icons/garage_door_closed.png differ diff --git a/demo/public/assets/arsaboo/icons/garage_door_open.png b/demo/public/assets/arsaboo/icons/garage_door_open.png index ef309b952e2f..b84dd7259f75 100644 Binary files a/demo/public/assets/arsaboo/icons/garage_door_open.png and b/demo/public/assets/arsaboo/icons/garage_door_open.png differ diff --git a/demo/public/assets/arsaboo/icons/light_bulb_off.png b/demo/public/assets/arsaboo/icons/light_bulb_off.png index 5fa70f4dbd5d..bc9279e1a114 100644 Binary files a/demo/public/assets/arsaboo/icons/light_bulb_off.png and b/demo/public/assets/arsaboo/icons/light_bulb_off.png differ diff --git a/demo/public/assets/arsaboo/icons/light_bulb_on.png b/demo/public/assets/arsaboo/icons/light_bulb_on.png index 06672e700581..9dd281710e1a 100644 Binary files a/demo/public/assets/arsaboo/icons/light_bulb_on.png and b/demo/public/assets/arsaboo/icons/light_bulb_on.png differ diff --git a/demo/public/assets/arsaboo/icons/light_off.png b/demo/public/assets/arsaboo/icons/light_off.png index 164c2c0dc384..60cc3ed777da 100644 Binary files a/demo/public/assets/arsaboo/icons/light_off.png and b/demo/public/assets/arsaboo/icons/light_off.png differ diff --git a/demo/public/assets/arsaboo/icons/light_on.png b/demo/public/assets/arsaboo/icons/light_on.png index ee267600fcee..13ca5ba6baaf 100644 Binary files a/demo/public/assets/arsaboo/icons/light_on.png and b/demo/public/assets/arsaboo/icons/light_on.png differ diff --git a/demo/public/assets/arsaboo/icons/security_armed_red.png b/demo/public/assets/arsaboo/icons/security_armed_red.png index 61cb8fd533a4..f575734aabff 100644 Binary files a/demo/public/assets/arsaboo/icons/security_armed_red.png and b/demo/public/assets/arsaboo/icons/security_armed_red.png differ diff --git a/demo/public/assets/arsaboo/icons/security_disarmed.png b/demo/public/assets/arsaboo/icons/security_disarmed.png index 359dd0856570..838509316e54 100644 Binary files a/demo/public/assets/arsaboo/icons/security_disarmed.png and b/demo/public/assets/arsaboo/icons/security_disarmed.png differ diff --git a/demo/public/assets/arsaboo/icons/tv_disabled.png b/demo/public/assets/arsaboo/icons/tv_disabled.png index cc5dccd4eabc..cf58667629fc 100644 Binary files a/demo/public/assets/arsaboo/icons/tv_disabled.png and b/demo/public/assets/arsaboo/icons/tv_disabled.png differ diff --git a/demo/public/assets/arsaboo/icons/tv_enabled.png b/demo/public/assets/arsaboo/icons/tv_enabled.png index 133f2bc2293f..96f1d38bec88 100644 Binary files a/demo/public/assets/arsaboo/icons/tv_enabled.png and b/demo/public/assets/arsaboo/icons/tv_enabled.png differ diff --git a/demo/public/assets/arsaboo/icons/tv_off2.png b/demo/public/assets/arsaboo/icons/tv_off2.png index 09e6f126041c..b96cb21e2ece 100644 Binary files a/demo/public/assets/arsaboo/icons/tv_off2.png and b/demo/public/assets/arsaboo/icons/tv_off2.png differ diff --git a/demo/public/assets/arsaboo/icons/tv_on2.png b/demo/public/assets/arsaboo/icons/tv_on2.png index 6323ef30eafd..6256275f8e03 100644 Binary files a/demo/public/assets/arsaboo/icons/tv_on2.png and b/demo/public/assets/arsaboo/icons/tv_on2.png differ diff --git a/demo/public/assets/arsaboo/images/camera.backyard.jpg b/demo/public/assets/arsaboo/images/camera.backyard.jpg index c90cd70a5d36..d3e75a0cb83a 100644 Binary files a/demo/public/assets/arsaboo/images/camera.backyard.jpg and b/demo/public/assets/arsaboo/images/camera.backyard.jpg differ diff --git a/demo/public/assets/arsaboo/images/camera.driveway.jpg b/demo/public/assets/arsaboo/images/camera.driveway.jpg index e7b59e6b8acb..530b72ef49f4 100644 Binary files a/demo/public/assets/arsaboo/images/camera.driveway.jpg and b/demo/public/assets/arsaboo/images/camera.driveway.jpg differ diff --git a/demo/public/assets/arsaboo/images/camera.patio.jpg b/demo/public/assets/arsaboo/images/camera.patio.jpg index c286cc8b94bd..618c9d67975a 100644 Binary files a/demo/public/assets/arsaboo/images/camera.patio.jpg and b/demo/public/assets/arsaboo/images/camera.patio.jpg differ diff --git a/demo/public/assets/arsaboo/images/camera.porch.jpg b/demo/public/assets/arsaboo/images/camera.porch.jpg index bfa6461e023e..ac6a868f8199 100644 Binary files a/demo/public/assets/arsaboo/images/camera.porch.jpg and b/demo/public/assets/arsaboo/images/camera.porch.jpg differ diff --git a/demo/public/assets/jimpower/cardbackK.png b/demo/public/assets/jimpower/cardbackK.png index 608e5ef5f6e5..57bec35fe9e6 100644 Binary files a/demo/public/assets/jimpower/cardbackK.png and b/demo/public/assets/jimpower/cardbackK.png differ diff --git a/demo/public/assets/jimpower/home/bus_10.jpg b/demo/public/assets/jimpower/home/bus_10.jpg index a7971bd7a792..55e9ff53fe40 100644 Binary files a/demo/public/assets/jimpower/home/bus_10.jpg and b/demo/public/assets/jimpower/home/bus_10.jpg differ diff --git a/demo/public/assets/jimpower/home/git.png b/demo/public/assets/jimpower/home/git.png index 5c8ec4adec26..fbd32173e088 100644 Binary files a/demo/public/assets/jimpower/home/git.png and b/demo/public/assets/jimpower/home/git.png differ diff --git a/demo/public/assets/jimpower/home/house_4.png b/demo/public/assets/jimpower/home/house_4.png index 2d9e9973f47f..be1ace750b82 100644 Binary files a/demo/public/assets/jimpower/home/house_4.png and b/demo/public/assets/jimpower/home/house_4.png differ diff --git a/demo/public/assets/jimpower/home/james_10.jpg b/demo/public/assets/jimpower/home/james_10.jpg index ae14a5124d3e..4eafc035cdf6 100644 Binary files a/demo/public/assets/jimpower/home/james_10.jpg and b/demo/public/assets/jimpower/home/james_10.jpg differ diff --git a/demo/public/assets/jimpower/home/tina_4.jpg b/demo/public/assets/jimpower/home/tina_4.jpg index b539da52421e..f22d73ef8edf 100644 Binary files a/demo/public/assets/jimpower/home/tina_4.jpg and b/demo/public/assets/jimpower/home/tina_4.jpg differ diff --git a/demo/public/assets/jimpower/security/door_3.png b/demo/public/assets/jimpower/security/door_3.png index 4cc2d9a3b57d..1d5e83133595 100644 Binary files a/demo/public/assets/jimpower/security/door_3.png and b/demo/public/assets/jimpower/security/door_3.png differ diff --git a/demo/public/assets/jimpower/security/leak_2.png b/demo/public/assets/jimpower/security/leak_2.png index e580484eb30a..6dba7ccd17f8 100644 Binary files a/demo/public/assets/jimpower/security/leak_2.png and b/demo/public/assets/jimpower/security/leak_2.png differ diff --git a/demo/public/assets/jimpower/security/motion_3.jpg b/demo/public/assets/jimpower/security/motion_3.jpg index 2788f7cf8a35..be8e60d20a46 100644 Binary files a/demo/public/assets/jimpower/security/motion_3.jpg and b/demo/public/assets/jimpower/security/motion_3.jpg differ diff --git a/demo/public/assets/kernehed/bella.jpg b/demo/public/assets/kernehed/bella.jpg index b7375a66e0a7..0dfd1832bd1e 100644 Binary files a/demo/public/assets/kernehed/bella.jpg and b/demo/public/assets/kernehed/bella.jpg differ diff --git a/demo/public/assets/kernehed/camera.entre.jpg b/demo/public/assets/kernehed/camera.entre.jpg index a3187ff6e3e5..126edb94552b 100644 Binary files a/demo/public/assets/kernehed/camera.entre.jpg and b/demo/public/assets/kernehed/camera.entre.jpg differ diff --git a/demo/public/assets/kernehed/oscar.jpg b/demo/public/assets/kernehed/oscar.jpg index ae014ecba36a..24e492bfbdc8 100644 Binary files a/demo/public/assets/kernehed/oscar.jpg and b/demo/public/assets/kernehed/oscar.jpg differ diff --git a/demo/public/assets/teachingbirds/Stefan_square.jpg b/demo/public/assets/teachingbirds/Stefan_square.jpg index 0fe6e02503e3..1d2810337009 100644 Binary files a/demo/public/assets/teachingbirds/Stefan_square.jpg and b/demo/public/assets/teachingbirds/Stefan_square.jpg differ diff --git a/demo/public/assets/teachingbirds/background_square.png b/demo/public/assets/teachingbirds/background_square.png index bfc9303bfb55..e0f870b16dca 100644 Binary files a/demo/public/assets/teachingbirds/background_square.png and b/demo/public/assets/teachingbirds/background_square.png differ diff --git a/demo/public/assets/teachingbirds/meteogram.png b/demo/public/assets/teachingbirds/meteogram.png index ab1614d3f4a4..11af23598aa5 100644 Binary files a/demo/public/assets/teachingbirds/meteogram.png and b/demo/public/assets/teachingbirds/meteogram.png differ diff --git a/demo/public/assets/teachingbirds/plants.png b/demo/public/assets/teachingbirds/plants.png index 341b41b880dd..846f93ee4d50 100644 Binary files a/demo/public/assets/teachingbirds/plants.png and b/demo/public/assets/teachingbirds/plants.png differ diff --git a/demo/public/assets/teachingbirds/radiator_off.jpg b/demo/public/assets/teachingbirds/radiator_off.jpg index 4d02175db6f0..df57b1d3fba3 100644 Binary files a/demo/public/assets/teachingbirds/radiator_off.jpg and b/demo/public/assets/teachingbirds/radiator_off.jpg differ diff --git a/demo/public/assets/teachingbirds/radiator_on.jpg b/demo/public/assets/teachingbirds/radiator_on.jpg index 67414f3349ec..00abd0f5c1b1 100644 Binary files a/demo/public/assets/teachingbirds/radiator_on.jpg and b/demo/public/assets/teachingbirds/radiator_on.jpg differ diff --git a/demo/public/stub_config/bedroom.png b/demo/public/stub_config/bedroom.png index 2d1e549fa13a..4b3722bd2c65 100644 Binary files a/demo/public/stub_config/bedroom.png and b/demo/public/stub_config/bedroom.png differ diff --git a/demo/public/stub_config/floorplan.png b/demo/public/stub_config/floorplan.png index 859842ea444b..f544bd4ce479 100644 Binary files a/demo/public/stub_config/floorplan.png and b/demo/public/stub_config/floorplan.png differ diff --git a/gallery/public/api/media_player_proxy/media_player.living_room b/gallery/public/api/media_player_proxy/media_player.living_room index 9f1596ce483b..1936201586d1 100644 Binary files a/gallery/public/api/media_player_proxy/media_player.living_room and b/gallery/public/api/media_player_proxy/media_player.living_room differ diff --git a/gallery/public/images/bed.png b/gallery/public/images/bed.png index 2d1e549fa13a..4b3722bd2c65 100644 Binary files a/gallery/public/images/bed.png and b/gallery/public/images/bed.png differ diff --git a/gallery/src/components/more-info-content.ts b/gallery/src/components/more-info-content.ts index 6d7271155eca..549ac4fa2d2e 100644 --- a/gallery/src/components/more-info-content.ts +++ b/gallery/src/components/more-info-content.ts @@ -1,7 +1,7 @@ import { HassEntity } from "home-assistant-js-websocket"; import { property, PropertyValues, UpdatingElement } from "lit-element"; import dynamicContentUpdater from "../../../src/common/dom/dynamic_content_updater"; -import { stateMoreInfoType } from "../../../src/common/entity/state_more_info_type"; +import { stateMoreInfoType } from "../../../src/dialogs/more-info/state_more_info_control"; import "../../../src/dialogs/more-info/controls/more-info-alarm_control_panel"; import "../../../src/dialogs/more-info/controls/more-info-automation"; import "../../../src/dialogs/more-info/controls/more-info-camera"; diff --git a/hassio/src/addon-view/config/hassio-addon-config.ts b/hassio/src/addon-view/config/hassio-addon-config.ts index 82755a98405d..415e3e3e919d 100644 --- a/hassio/src/addon-view/config/hassio-addon-config.ts +++ b/hassio/src/addon-view/config/hassio-addon-config.ts @@ -175,7 +175,7 @@ class HassioAddonConfig extends LitElement { } iron-autogrow-textarea { width: 100%; - font-family: monospace; + font-family: var(--code-font-family, monospace); } .syntaxerror { color: var(--error-color); diff --git a/hassio/src/components/hassio-upload-snapshot.ts b/hassio/src/components/hassio-upload-snapshot.ts new file mode 100644 index 000000000000..94c7828ad767 --- /dev/null +++ b/hassio/src/components/hassio-upload-snapshot.ts @@ -0,0 +1,81 @@ +import "../../../src/components/ha-file-upload"; +import "@material/mwc-icon-button/mwc-icon-button"; +import { mdiFolderUpload } from "@mdi/js"; +import "@polymer/iron-input/iron-input"; +import "@polymer/paper-input/paper-input-container"; +import { + customElement, + html, + internalProperty, + LitElement, + TemplateResult, +} from "lit-element"; +import { fireEvent } from "../../../src/common/dom/fire_event"; +import "../../../src/components/ha-circular-progress"; +import "../../../src/components/ha-svg-icon"; +import { + HassioSnapshot, + uploadSnapshot, +} from "../../../src/data/hassio/snapshot"; +import { HomeAssistant } from "../../../src/types"; +import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; + +declare global { + interface HASSDomEvents { + "snapshot-uploaded": { snapshot: HassioSnapshot }; + } +} + +@customElement("hassio-upload-snapshot") +export class HassioUploadSnapshot extends LitElement { + public hass!: HomeAssistant; + + @internalProperty() public value: string | null = null; + + @internalProperty() private _uploading = false; + + public render(): TemplateResult { + return html` + + `; + } + + private async _uploadFile(ev) { + const file = ev.detail.files[0]; + + if (!["application/x-tar"].includes(file.type)) { + showAlertDialog(this, { + title: "Unsupported file format", + text: "Please choose a Home Assistant snapshot file (.tar)", + confirmText: "ok", + }); + return; + } + this._uploading = true; + try { + const snapshot = await uploadSnapshot(this.hass, file); + fireEvent(this, "snapshot-uploaded", { snapshot: snapshot.data }); + } catch (err) { + showAlertDialog(this, { + title: "Upload failed", + text: err.toString(), + confirmText: "ok", + }); + } finally { + this._uploading = false; + } + } +} + +declare global { + interface HTMLElementTagNameMap { + "hassio-upload-snapshot": HassioUploadSnapshot; + } +} diff --git a/hassio/src/dashboard/hassio-addons.ts b/hassio/src/dashboard/hassio-addons.ts index 31e7450bb211..8c6963cac939 100644 --- a/hassio/src/dashboard/hassio-addons.ts +++ b/hassio/src/dashboard/hassio-addons.ts @@ -10,6 +10,7 @@ import { } from "lit-element"; import { atLeastVersion } from "../../../src/common/config/version"; import { navigate } from "../../../src/common/navigate"; +import { compare } from "../../../src/common/string/compare"; import "../../../src/components/ha-card"; import { HassioAddonInfo } from "../../../src/data/hassio/addon"; import { haStyle } from "../../../src/resources/styles"; @@ -21,25 +22,27 @@ import { hassioStyle } from "../resources/hassio-style"; class HassioAddons extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public addons?: HassioAddonInfo[]; + @property({ attribute: false }) public addons?: HassioAddonInfo[]; protected render(): TemplateResult { return html`

Add-ons

- ${!this.addons + ${!this.addons?.length ? html`
You don't have any add-ons installed yet. Head over to - the add-on store + to get started!
` : this.addons - .sort((a, b) => (a.name > b.name ? 1 : -1)) + .sort((a, b) => compare(a.name, b.name)) .map( (addon) => html` diff --git a/hassio/src/dialogs/snapshot/dialog-hassio-snapshot-upload.ts b/hassio/src/dialogs/snapshot/dialog-hassio-snapshot-upload.ts new file mode 100644 index 000000000000..ed0532d29f25 --- /dev/null +++ b/hassio/src/dialogs/snapshot/dialog-hassio-snapshot-upload.ts @@ -0,0 +1,107 @@ +import { mdiClose } from "@mdi/js"; +import { + css, + CSSResult, + customElement, + html, + internalProperty, + LitElement, + property, + TemplateResult, +} from "lit-element"; +import { fireEvent } from "../../../../src/common/dom/fire_event"; +import "../../../../src/components/ha-header-bar"; +import { HassDialog } from "../../../../src/dialogs/make-dialog-manager"; +import { haStyleDialog } from "../../../../src/resources/styles"; +import type { HomeAssistant } from "../../../../src/types"; +import "../../components/hassio-upload-snapshot"; +import { HassioSnapshotUploadDialogParams } from "./show-dialog-snapshot-upload"; + +@customElement("dialog-hassio-snapshot-upload") +export class DialogHassioSnapshotUpload extends LitElement + implements HassDialog { + @property({ attribute: false }) public hass!: HomeAssistant; + + @internalProperty() private _params?: HassioSnapshotUploadDialogParams; + + public async showDialog( + params: HassioSnapshotUploadDialogParams + ): Promise { + this._params = params; + await this.updateComplete; + } + + public closeDialog(): void { + if (this._params && !this._params.onboarding) { + if (this._params.reloadSnapshot) { + this._params.reloadSnapshot(); + } + } + this._params = undefined; + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + protected render(): TemplateResult { + if (!this._params) { + return html``; + } + + return html` + +
+ + + Upload snapshot + + + + + +
+ +
+ `; + } + + private _snapshotUploaded(ev) { + const snapshot = ev.detail.snapshot; + this._params?.showSnapshot(snapshot.slug); + this.closeDialog(); + } + + static get styles(): CSSResult[] { + return [ + haStyleDialog, + css` + ha-header-bar { + --mdc-theme-on-primary: var(--primary-text-color); + --mdc-theme-primary: var(--mdc-theme-surface); + flex-shrink: 0; + } + /* overrule the ha-style-dialog max-height on small screens */ + @media all and (max-width: 450px), all and (max-height: 500px) { + ha-header-bar { + --mdc-theme-primary: var(--app-header-background-color); + --mdc-theme-on-primary: var(--app-header-text-color, white); + } + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-hassio-snapshot-upload": DialogHassioSnapshotUpload; + } +} diff --git a/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts b/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts index eb70aa51c7ca..790ebc467cb3 100755 --- a/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts +++ b/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts @@ -1,5 +1,5 @@ import "@material/mwc-button"; -import { mdiDelete, mdiDownload, mdiHistory } from "@mdi/js"; +import { mdiClose, mdiDelete, mdiDownload, mdiHistory } from "@mdi/js"; import { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox"; import "@polymer/paper-input/paper-input"; import { @@ -12,7 +12,8 @@ import { property, TemplateResult, } from "lit-element"; -import { createCloseHeading } from "../../../../src/components/ha-dialog"; +import { fireEvent } from "../../../../src/common/dom/fire_event"; +import "../../../../src/components/ha-header-bar"; import "../../../../src/components/ha-svg-icon"; import { getSignedPath } from "../../../../src/data/auth"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; @@ -22,7 +23,7 @@ import { } from "../../../../src/data/hassio/snapshot"; import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box"; import { PolymerChangedEvent } from "../../../../src/polymer-types"; -import { haStyleDialog } from "../../../../src/resources/styles"; +import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { HomeAssistant } from "../../../../src/types"; import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot"; @@ -75,6 +76,8 @@ class HassioSnapshotDialog extends LitElement { @internalProperty() private _error?: string; + @internalProperty() private _onboarding = false; + @internalProperty() private _snapshot?: HassioSnapshotDetail; @internalProperty() private _folders!: FolderItem[]; @@ -90,13 +93,14 @@ class HassioSnapshotDialog extends LitElement { public async showDialog(params: HassioSnapshotDialogParams) { this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug); this._folders = _computeFolders( - this._snapshot.folders + this._snapshot?.folders ).sort((a: FolderItem, b: FolderItem) => (a.name > b.name ? 1 : -1)); this._addons = _computeAddons( - this._snapshot.addons + this._snapshot?.addons ).sort((a: AddonItem, b: AddonItem) => (a.name > b.name ? 1 : -1)); this._dialogParams = params; + this._onboarding = params.onboarding ?? false; } protected render(): TemplateResult { @@ -104,12 +108,17 @@ class HassioSnapshotDialog extends LitElement { return html``; } return html` - + +
+ + + ${this._computeName} + + + + + +
${this._snapshot.type === "full" ? "Full snapshot" @@ -182,11 +191,15 @@ class HassioSnapshotDialog extends LitElement { ${this._error ? html`

Error: ${this._error}

` : ""}
Actions:
- - - - Download Snapshot - + ${!this._onboarding + ? html` + + Download Snapshot + ` + : ""} ` : ""} - - - Delete Snapshot - + ${!this._onboarding + ? html` + + Delete Snapshot + ` + : ""} `; } static get styles(): CSSResult[] { return [ + haStyle, haStyleDialog, css` paper-checkbox { @@ -242,6 +261,18 @@ class HassioSnapshotDialog extends LitElement { .no-margin-top { margin-top: 0; } + ha-header-bar { + --mdc-theme-on-primary: var(--primary-text-color); + --mdc-theme-primary: var(--mdc-theme-surface); + flex-shrink: 0; + } + /* overrule the ha-style-dialog max-height on small screens */ + @media all and (max-width: 450px), all and (max-height: 500px) { + ha-header-bar { + --mdc-theme-primary: var(--app-header-background-color); + --mdc-theme-on-primary: var(--app-header-text-color, white); + } + } `, ]; } @@ -272,6 +303,8 @@ class HassioSnapshotDialog extends LitElement { if ( !(await showConfirmationDialog(this, { title: "Are you sure you want partially to restore this snapshot?", + confirmText: "restore", + dismissText: "cancel", })) ) { return; @@ -300,22 +333,31 @@ class HassioSnapshotDialog extends LitElement { data.password = this._snapshotPassword; } - this.hass - .callApi( - "POST", - - `hassio/snapshots/${this._snapshot!.slug}/restore/partial`, - data - ) - .then( - () => { - alert("Snapshot restored!"); - this._closeDialog(); - }, - (error) => { - this._error = error.body.message; - } - ); + if (!this._onboarding) { + this.hass + .callApi( + "POST", + + `hassio/snapshots/${this._snapshot!.slug}/restore/partial`, + data + ) + .then( + () => { + alert("Snapshot restored!"); + this._closeDialog(); + }, + (error) => { + this._error = error.body.message; + } + ); + } else { + fireEvent(this, "restoring"); + fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/partial`, { + method: "POST", + body: JSON.stringify(data), + }); + this._closeDialog(); + } } private async _fullRestoreClicked() { @@ -323,6 +365,8 @@ class HassioSnapshotDialog extends LitElement { !(await showConfirmationDialog(this, { title: "Are you sure you want to wipe your system and restore this snapshot?", + confirmText: "restore", + dismissText: "cancel", })) ) { return; @@ -331,28 +375,38 @@ class HassioSnapshotDialog extends LitElement { const data = this._snapshot!.protected ? { password: this._snapshotPassword } : undefined; - - this.hass - .callApi( - "POST", - `hassio/snapshots/${this._snapshot!.slug}/restore/full`, - data - ) - .then( - () => { - alert("Snapshot restored!"); - this._closeDialog(); - }, - (error) => { - this._error = error.body.message; - } - ); + if (!this._onboarding) { + this.hass + .callApi( + "POST", + `hassio/snapshots/${this._snapshot!.slug}/restore/full`, + data + ) + .then( + () => { + alert("Snapshot restored!"); + this._closeDialog(); + }, + (error) => { + this._error = error.body.message; + } + ); + } else { + fireEvent(this, "restoring"); + fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/full`, { + method: "POST", + body: JSON.stringify(data), + }); + this._closeDialog(); + } } private async _deleteClicked() { if ( !(await showConfirmationDialog(this, { title: "Are you sure you want to delete this snapshot?", + confirmText: "delete", + dismissText: "cancel", })) ) { return; @@ -363,7 +417,9 @@ class HassioSnapshotDialog extends LitElement { .callApi("POST", `hassio/snapshots/${this._snapshot!.slug}/remove`) .then( () => { - this._dialogParams!.onDelete(); + if (this._dialogParams!.onDelete) { + this._dialogParams!.onDelete(); + } this._closeDialog(); }, (error) => { diff --git a/hassio/src/dialogs/snapshot/show-dialog-hassio-snapshot.ts b/hassio/src/dialogs/snapshot/show-dialog-hassio-snapshot.ts index 478f295b7dea..8631815c2923 100644 --- a/hassio/src/dialogs/snapshot/show-dialog-hassio-snapshot.ts +++ b/hassio/src/dialogs/snapshot/show-dialog-hassio-snapshot.ts @@ -2,7 +2,8 @@ import { fireEvent } from "../../../../src/common/dom/fire_event"; export interface HassioSnapshotDialogParams { slug: string; - onDelete: () => void; + onDelete?: () => void; + onboarding?: boolean; } export const showHassioSnapshotDialog = ( diff --git a/hassio/src/dialogs/snapshot/show-dialog-snapshot-upload.ts b/hassio/src/dialogs/snapshot/show-dialog-snapshot-upload.ts new file mode 100644 index 000000000000..2fe087f8d39a --- /dev/null +++ b/hassio/src/dialogs/snapshot/show-dialog-snapshot-upload.ts @@ -0,0 +1,22 @@ +import { fireEvent } from "../../../../src/common/dom/fire_event"; +import "./dialog-hassio-snapshot-upload"; + +export interface HassioSnapshotUploadDialogParams { + showSnapshot: (slug: string) => void; + reloadSnapshot?: () => Promise; + onboarding?: boolean; +} + +export const showSnapshotUploadDialog = ( + element: HTMLElement, + dialogParams: HassioSnapshotUploadDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-hassio-snapshot-upload", + dialogImport: () => + import( + /* webpackChunkName: "dialog-hassio-snapshot-upload" */ "./dialog-hassio-snapshot-upload" + ), + dialogParams, + }); +}; diff --git a/hassio/src/snapshots/hassio-snapshots.ts b/hassio/src/snapshots/hassio-snapshots.ts index 18f2b1d0ed9a..8f040c906b3c 100644 --- a/hassio/src/snapshots/hassio-snapshots.ts +++ b/hassio/src/snapshots/hassio-snapshots.ts @@ -1,6 +1,12 @@ import "@material/mwc-button"; import "@material/mwc-icon-button"; -import { mdiPackageVariant, mdiPackageVariantClosed, mdiReload } from "@mdi/js"; +import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; +import "@material/mwc-list/mwc-list-item"; +import { + mdiDotsVertical, + mdiPackageVariant, + mdiPackageVariantClosed, +} from "@mdi/js"; import "@polymer/paper-checkbox/paper-checkbox"; import type { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox"; import "@polymer/paper-input/paper-input"; @@ -19,8 +25,10 @@ import { PropertyValues, TemplateResult, } from "lit-element"; +import { atLeastVersion } from "../../../src/common/config/version"; import { fireEvent } from "../../../src/common/dom/fire_event"; import "../../../src/components/buttons/ha-progress-button"; +import "../../../src/components/ha-button-menu"; import "../../../src/components/ha-card"; import "../../../src/components/ha-svg-icon"; import { extractApiErrorMessage } from "../../../src/data/hassio/common"; @@ -39,7 +47,9 @@ import { PolymerChangedEvent } from "../../../src/polymer-types"; import { haStyle } from "../../../src/resources/styles"; import { HomeAssistant, Route } from "../../../src/types"; import "../components/hassio-card-content"; +import "../components/hassio-upload-snapshot"; import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot"; +import { showSnapshotUploadDialog } from "../dialogs/snapshot/show-dialog-snapshot-upload"; import { supervisorTabs } from "../hassio-tabs"; import { hassioStyle } from "../resources/hassio-style"; @@ -101,14 +111,23 @@ class HassioSnapshots extends LitElement { .tabs=${supervisorTabs} > Snapshots - - - - + + + + + Reload + + ${atLeastVersion(this.hass.config.version, 0, 116) + ? html` + Upload snapshot + ` + : ""} +

@@ -257,6 +276,17 @@ class HassioSnapshots extends LitElement { } } + private _handleAction(ev: CustomEvent) { + switch (ev.detail.index) { + case 0: + this.refreshData(); + break; + case 1: + this._showUploadSnapshotDialog(); + break; + } + } + private _handleTextValueChanged(ev: PolymerChangedEvent) { const input = ev.currentTarget as PaperInputElement; this[`_${input.name}`] = ev.detail.value; @@ -362,6 +392,17 @@ class HassioSnapshots extends LitElement { }); } + private _showUploadSnapshotDialog() { + showSnapshotUploadDialog(this, { + showSnapshot: (slug: string) => + showHassioSnapshotDialog(this, { + slug, + onDelete: () => this._updateSnapshots(), + }), + reloadSnapshot: () => this.refreshData(), + }); + } + static get styles(): CSSResultArray { return [ haStyle, diff --git a/hassio/src/system/hassio-system-metrics.ts b/hassio/src/system/hassio-system-metrics.ts new file mode 100644 index 000000000000..e201adfc802c --- /dev/null +++ b/hassio/src/system/hassio-system-metrics.ts @@ -0,0 +1,168 @@ +import "@material/mwc-button"; +import "@material/mwc-list/mwc-list-item"; +import { + css, + CSSResult, + customElement, + html, + internalProperty, + LitElement, + property, + TemplateResult, +} from "lit-element"; +import { classMap } from "lit-html/directives/class-map"; +import memoizeOne from "memoize-one"; +import "../../../src/components/buttons/ha-progress-button"; +import "../../../src/components/ha-bar"; +import "../../../src/components/ha-button-menu"; +import "../../../src/components/ha-card"; +import "../../../src/components/ha-settings-row"; +import { fetchHassioStats, HassioStats } from "../../../src/data/hassio/common"; +import { HassioHostInfo } from "../../../src/data/hassio/host"; +import { haStyle } from "../../../src/resources/styles"; +import { HomeAssistant } from "../../../src/types"; +import { + getValueInPercentage, + roundWithOneDecimal, +} from "../../../src/util/calculate"; +import { hassioStyle } from "../resources/hassio-style"; + +@customElement("hassio-system-metrics") +class HassioSystemMetrics extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public hostInfo!: HassioHostInfo; + + @internalProperty() private _supervisorMetrics?: HassioStats; + + @internalProperty() private _coreMetrics?: HassioStats; + + protected render(): TemplateResult | void { + const usedSpace = this._getUsedSpace(this.hostInfo); + const metrics = [ + { + description: "Core CPU usage", + value: this._coreMetrics?.cpu_percent, + }, + { + description: "Core RAM usage", + value: this._coreMetrics?.memory_percent, + }, + { + description: "Supervisor CPU usage", + value: this._supervisorMetrics?.cpu_percent, + }, + { + description: "Supervisor RAM usage", + value: this._supervisorMetrics?.memory_percent, + }, + { + description: "Used space", + value: usedSpace, + }, + ]; + + return html` + +
+ ${metrics.map((metric) => + this._renderMetric(metric.description, metric.value ?? 0) + )} +
+
+ `; + } + + protected firstUpdated(): void { + this._loadData(); + } + + private _renderMetric(description: string, value: number): TemplateResult { + const roundedValue = roundWithOneDecimal(value); + return html` + + ${description} + +
+ + ${roundedValue}% + + 50, + "target-critical": roundedValue > 85, + })}" + .value=${value} + > +
+
`; + } + + private _getUsedSpace = memoizeOne((hostInfo: HassioHostInfo) => + roundWithOneDecimal( + getValueInPercentage(hostInfo.disk_used, 0, hostInfo.disk_total) + ) + ); + + private async _loadData(): Promise { + const [supervisor, core] = await Promise.all([ + fetchHassioStats(this.hass, "supervisor"), + fetchHassioStats(this.hass, "core"), + ]); + this._supervisorMetrics = supervisor; + this._coreMetrics = core; + } + + static get styles(): CSSResult[] { + return [ + haStyle, + hassioStyle, + css` + ha-card { + height: 100%; + justify-content: space-between; + flex-direction: column; + display: flex; + } + ha-settings-row { + padding: 0; + height: 54px; + width: 100%; + } + ha-settings-row > div[slot="description"] { + white-space: normal; + color: var(--secondary-text-color); + display: flex; + justify-content: space-between; + } + ha-bar { + --ha-bar-primary-color: var( + --hassio-bar-ok-color, + var(--success-color) + ); + } + .target-warning { + --ha-bar-primary-color: var( + --hassio-bar-warning-color, + var(--warning-color) + ); + } + .target-critical { + --ha-bar-primary-color: var( + --hassio-bar-critical-color, + var(--error-color) + ); + } + .value { + width: 42px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hassio-system-metrics": HassioSystemMetrics; + } +} diff --git a/hassio/src/system/hassio-system.ts b/hassio/src/system/hassio-system.ts index f2ffc2cc6288..e7eef9eaaa05 100644 --- a/hassio/src/system/hassio-system.ts +++ b/hassio/src/system/hassio-system.ts @@ -23,6 +23,7 @@ import { hassioStyle } from "../resources/hassio-style"; import "./hassio-host-info"; import "./hassio-supervisor-info"; import "./hassio-supervisor-log"; +import "./hassio-system-metrics"; @customElement("hassio-system") class HassioSystem extends LitElement { @@ -64,6 +65,10 @@ class HassioSystem extends LitElement { .hostInfo=${this.hostInfo} .hassOsInfo=${this.hassOsInfo} > +

diff --git a/package.json b/package.json index dd550682fee0..e176dd4e880a 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,8 @@ "@material/mwc-tab": "^0.18.0", "@material/mwc-tab-bar": "^0.18.0", "@material/top-app-bar": "=8.0.0-canary.096a7a066.0", - "@mdi/js": "5.5.55", - "@mdi/svg": "5.5.55", + "@mdi/js": "5.6.55", + "@mdi/svg": "5.6.55", "@polymer/app-layout": "^3.0.2", "@polymer/app-route": "^3.0.2", "@polymer/app-storage": "^3.0.2", @@ -143,7 +143,7 @@ "@rollup/plugin-node-resolve": "^7.1.3", "@rollup/plugin-replace": "^2.3.2", "@types/chai": "^4.1.7", - "@types/chromecast-caf-receiver": "^3.0.12", + "@types/chromecast-caf-receiver": "^5.0.11", "@types/codemirror": "^0.0.97", "@types/hls.js": "^0.12.3", "@types/js-yaml": "^3.12.1", diff --git a/polymer.json b/polymer.json index d0e3496de7a4..47df9b9c7dda 100644 --- a/polymer.json +++ b/polymer.json @@ -24,7 +24,6 @@ "filesToIgnore": [ "**/*.html", "**/src/panels/config/js/**/*.js", - "**/ha-paper-slider.js", "**/ha-iconset-svg.js", "**/ha-script-editor.js", "**/ha-automation-editor.js", diff --git a/public/static/icons/favicon-apple-180x180.png b/public/static/icons/favicon-apple-180x180.png index 13ca6ab7751e..72d207a25677 100644 Binary files a/public/static/icons/favicon-apple-180x180.png and b/public/static/icons/favicon-apple-180x180.png differ diff --git a/public/static/icons/favicon.ico b/public/static/icons/favicon.ico index 6d12158c18b1..038be624465c 100644 Binary files a/public/static/icons/favicon.ico and b/public/static/icons/favicon.ico differ diff --git a/public/static/images/config_ecobee_thermostat.png b/public/static/images/config_ecobee_thermostat.png index aa6a7b93c4f1..f1bb30d1006f 100644 Binary files a/public/static/images/config_ecobee_thermostat.png and b/public/static/images/config_ecobee_thermostat.png differ diff --git a/public/static/images/config_freebox.png b/public/static/images/config_freebox.png index aa439fbd1531..48d9e48edc14 100644 Binary files a/public/static/images/config_freebox.png and b/public/static/images/config_freebox.png differ diff --git a/public/static/images/logo_automatic.png b/public/static/images/logo_automatic.png index 2e52cd47cfeb..103cf7ac3df5 100644 Binary files a/public/static/images/logo_automatic.png and b/public/static/images/logo_automatic.png differ diff --git a/public/static/images/smart-tv.png b/public/static/images/smart-tv.png index a4154cf93baa..a41fd6761454 100644 Binary files a/public/static/images/smart-tv.png and b/public/static/images/smart-tv.png differ diff --git a/setup.py b/setup.py index c5e5796d6d74..df643228123e 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="home-assistant-frontend", - version="20200918.2", + version="20200930.0", description="The Home Assistant frontend", url="https://github.com/home-assistant/home-assistant-polymer", author="The Home Assistant Authors", diff --git a/src/common/const.ts b/src/common/const.ts index 640c4fb13af6..4b4f8e622543 100644 --- a/src/common/const.ts +++ b/src/common/const.ts @@ -7,6 +7,66 @@ /** Icon to use when no icon specified for domain. */ export const DEFAULT_DOMAIN_ICON = "hass:bookmark"; +/** Icons for each domain */ +export const FIXED_DOMAIN_ICONS = { + alert: "hass:alert", + alexa: "hass:amazon-alexa", + air_quality: "hass:air-filter", + automation: "hass:robot", + calendar: "hass:calendar", + camera: "hass:video", + climate: "hass:thermostat", + configurator: "hass:cog", + conversation: "hass:text-to-speech", + counter: "hass:counter", + device_tracker: "hass:account", + fan: "hass:fan", + google_assistant: "hass:google-assistant", + group: "hass:google-circles-communities", + homeassistant: "hass:home-assistant", + homekit: "hass:home-automation", + image_processing: "hass:image-filter-frames", + input_boolean: "hass:toggle-switch-outline", + input_datetime: "hass:calendar-clock", + input_number: "hass:ray-vertex", + input_select: "hass:format-list-bulleted", + input_text: "hass:form-textbox", + light: "hass:lightbulb", + mailbox: "hass:mailbox", + notify: "hass:comment-alert", + persistent_notification: "hass:bell", + person: "hass:account", + plant: "hass:flower", + proximity: "hass:apple-safari", + remote: "hass:remote", + scene: "hass:palette", + script: "hass:script-text", + sensor: "hass:eye", + simple_alarm: "hass:bell", + sun: "hass:white-balance-sunny", + switch: "hass:flash", + timer: "hass:timer-outline", + updater: "hass:cloud-upload", + vacuum: "hass:robot-vacuum", + water_heater: "hass:thermometer", + weather: "hass:weather-cloudy", + zone: "hass:map-marker-radius", +}; + +export const FIXED_DEVICE_CLASS_ICONS = { + current: "hass:current-ac", + energy: "hass:flash", + humidity: "hass:water-percent", + illuminance: "hass:brightness-5", + temperature: "hass:thermometer", + pressure: "hass:gauge", + power: "hass:flash", + power_factor: "hass:angle-acute", + signal_strength: "hass:wifi", + timestamp: "hass:clock", + voltage: "hass:sine-wave", +}; + /** Domains that have a state card. */ export const DOMAINS_WITH_CARD = [ "climate", @@ -63,6 +123,10 @@ export const DOMAINS_MORE_INFO_NO_HISTORY = ["camera", "configurator", "scene"]; /** States that we consider "off". */ export const STATES_OFF = ["closed", "locked", "off"]; +/** Binary States */ +export const BINARY_STATE_ON = "on"; +export const BINARY_STATE_OFF = "off"; + /** Domains where we allow toggle in Lovelace. */ export const DOMAINS_TOGGLE = new Set([ "fan", diff --git a/src/common/entity/binary_sensor_icon.ts b/src/common/entity/binary_sensor_icon.ts index 5442e432ff31..03c3abaaab43 100644 --- a/src/common/entity/binary_sensor_icon.ts +++ b/src/common/entity/binary_sensor_icon.ts @@ -2,9 +2,9 @@ import { HassEntity } from "home-assistant-js-websocket"; /** Return an icon representing a binary sensor state. */ -export const binarySensorIcon = (state: HassEntity) => { - const is_off = state.state && state.state === "off"; - switch (state.attributes.device_class) { +export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => { + const is_off = state === "off"; + switch (stateObj?.attributes.device_class) { case "battery": return is_off ? "hass:battery" : "hass:battery-outline"; case "battery_charging": @@ -17,8 +17,9 @@ export const binarySensorIcon = (state: HassEntity) => { return is_off ? "hass:door-closed" : "hass:door-open"; case "garage_door": return is_off ? "hass:garage" : "hass:garage-open"; - case "gas": case "power": + return is_off ? "hass:power-off" : "hass:power-on"; + case "gas": case "problem": case "safety": case "smoke": diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index 10de4cb847a1..2a437901ea8c 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -9,14 +9,17 @@ import { computeStateDomain } from "./compute_state_domain"; export const computeStateDisplay = ( localize: LocalizeFunc, stateObj: HassEntity, - language: string + language: string, + state?: string ): string => { - if (stateObj.state === UNKNOWN || stateObj.state === UNAVAILABLE) { - return localize(`state.default.${stateObj.state}`); + const compareState = state !== undefined ? state : stateObj.state; + + if (compareState === UNKNOWN || compareState === UNAVAILABLE) { + return localize(`state.default.${compareState}`); } if (stateObj.attributes.unit_of_measurement) { - return `${stateObj.state} ${stateObj.attributes.unit_of_measurement}`; + return `${compareState} ${stateObj.attributes.unit_of_measurement}`; } const domain = computeStateDomain(stateObj); @@ -56,7 +59,7 @@ export const computeStateDisplay = ( } if (domain === "humidifier") { - if (stateObj.state === "on" && stateObj.attributes.humidity) { + if (compareState === "on" && stateObj.attributes.humidity) { return `${stateObj.attributes.humidity}%`; } } @@ -65,11 +68,11 @@ export const computeStateDisplay = ( // Return device class translation (stateObj.attributes.device_class && localize( - `component.${domain}.state.${stateObj.attributes.device_class}.${stateObj.state}` + `component.${domain}.state.${stateObj.attributes.device_class}.${compareState}` )) || // Return default translation - localize(`component.${domain}.state._.${stateObj.state}`) || + localize(`component.${domain}.state._.${compareState}`) || // We don't know! Return the raw state. - stateObj.state + compareState ); }; diff --git a/src/common/entity/cover_icon.ts b/src/common/entity/cover_icon.ts index 25d3c265a93c..5723ee2d34af 100644 --- a/src/common/entity/cover_icon.ts +++ b/src/common/entity/cover_icon.ts @@ -1,13 +1,12 @@ /** Return an icon representing a cover state. */ import { HassEntity } from "home-assistant-js-websocket"; -import { domainIcon } from "./domain_icon"; -export const coverIcon = (state: HassEntity): string => { - const open = state.state !== "closed"; +export const coverIcon = (state?: string, stateObj?: HassEntity): string => { + const open = state !== "closed"; - switch (state.attributes.device_class) { + switch (stateObj?.attributes.device_class) { case "garage": - switch (state.state) { + switch (state) { case "opening": return "hass:arrow-up-box"; case "closing": @@ -18,7 +17,7 @@ export const coverIcon = (state: HassEntity): string => { return "hass:garage-open"; } case "gate": - switch (state.state) { + switch (state) { case "opening": case "closing": return "hass:gate-arrow-right"; @@ -32,7 +31,7 @@ export const coverIcon = (state: HassEntity): string => { case "damper": return open ? "hass:circle" : "hass:circle-slice-8"; case "shutter": - switch (state.state) { + switch (state) { case "opening": return "hass:arrow-up-box"; case "closing": @@ -44,7 +43,7 @@ export const coverIcon = (state: HassEntity): string => { } case "blind": case "curtain": - switch (state.state) { + switch (state) { case "opening": return "hass:arrow-up-box"; case "closing": @@ -55,7 +54,7 @@ export const coverIcon = (state: HassEntity): string => { return "hass:blinds-open"; } case "window": - switch (state.state) { + switch (state) { case "opening": return "hass:arrow-up-box"; case "closing": @@ -65,7 +64,16 @@ export const coverIcon = (state: HassEntity): string => { default: return "hass:window-open"; } + } + + switch (state) { + case "opening": + return "hass:arrow-up-box"; + case "closing": + return "hass:arrow-down-box"; + case "closed": + return "hass:window-closed"; default: - return domainIcon("cover", state.state); + return "hass:window-open"; } }; diff --git a/src/common/entity/domain_icon.ts b/src/common/entity/domain_icon.ts index 82b8795454a0..e555b0e28604 100644 --- a/src/common/entity/domain_icon.ts +++ b/src/common/entity/domain_icon.ts @@ -1,64 +1,24 @@ +import { HassEntity } from "home-assistant-js-websocket"; /** * Return the icon to be used for a domain. * * Optionally pass in a state to influence the domain icon. */ -import { DEFAULT_DOMAIN_ICON } from "../const"; +import { DEFAULT_DOMAIN_ICON, FIXED_DOMAIN_ICONS } from "../const"; +import { binarySensorIcon } from "./binary_sensor_icon"; +import { coverIcon } from "./cover_icon"; +import { sensorIcon } from "./sensor_icon"; -const fixedIcons = { - alert: "hass:alert", - alexa: "hass:amazon-alexa", - air_quality: "hass:air-filter", - automation: "hass:robot", - calendar: "hass:calendar", - camera: "hass:video", - climate: "hass:thermostat", - configurator: "hass:cog", - conversation: "hass:text-to-speech", - counter: "hass:counter", - device_tracker: "hass:account", - fan: "hass:fan", - google_assistant: "hass:google-assistant", - group: "hass:google-circles-communities", - homeassistant: "hass:home-assistant", - homekit: "hass:home-automation", - humidifier: "hass:air-humidifier", - image_processing: "hass:image-filter-frames", - input_boolean: "hass:toggle-switch-outline", - input_datetime: "hass:calendar-clock", - input_number: "hass:ray-vertex", - input_select: "hass:format-list-bulleted", - input_text: "hass:form-textbox", - light: "hass:lightbulb", - mailbox: "hass:mailbox", - notify: "hass:comment-alert", - persistent_notification: "hass:bell", - person: "hass:account", - plant: "hass:flower", - proximity: "hass:apple-safari", - remote: "hass:remote", - scene: "hass:palette", - script: "hass:script-text", - sensor: "hass:eye", - simple_alarm: "hass:bell", - sun: "hass:white-balance-sunny", - switch: "hass:flash", - timer: "hass:timer-outline", - updater: "hass:cloud-upload", - vacuum: "hass:robot-vacuum", - water_heater: "hass:thermometer", - weather: "hass:weather-cloudy", - zone: "hass:map-marker-radius", -}; - -export const domainIcon = (domain: string, state?: string): string => { - if (domain in fixedIcons) { - return fixedIcons[domain]; - } +export const domainIcon = ( + domain: string, + stateObj?: HassEntity, + state?: string +): string => { + const compareState = state !== undefined ? state : stateObj?.state; switch (domain) { case "alarm_control_panel": - switch (state) { + switch (compareState) { case "armed_home": return "hass:bell-plus"; case "armed_night": @@ -72,30 +32,24 @@ export const domainIcon = (domain: string, state?: string): string => { } case "binary_sensor": - return state && state === "off" - ? "hass:radiobox-blank" - : "hass:checkbox-marked-circle"; + return binarySensorIcon(compareState, stateObj); case "cover": - switch (state) { - case "opening": - return "hass:arrow-up-box"; - case "closing": - return "hass:arrow-down-box"; - case "closed": - return "hass:window-closed"; - default: - return "hass:window-open"; - } + return coverIcon(compareState, stateObj); + + case "humidifier": + return state && state === "off" + ? "hass:air-humidifier-off" + : "hass:air-humidifier"; case "lock": - return state && state === "unlocked" ? "hass:lock-open" : "hass:lock"; + return compareState === "unlocked" ? "hass:lock-open" : "hass:lock"; case "media_player": - return state && state === "playing" ? "hass:cast-connected" : "hass:cast"; + return compareState === "playing" ? "hass:cast-connected" : "hass:cast"; case "zwave": - switch (state) { + switch (compareState) { case "dead": return "hass:emoticon-dead"; case "sleeping": @@ -106,11 +60,32 @@ export const domainIcon = (domain: string, state?: string): string => { return "hass:z-wave"; } - default: - // eslint-disable-next-line - console.warn( - "Unable to find icon for domain " + domain + " (" + state + ")" - ); - return DEFAULT_DOMAIN_ICON; + case "sensor": { + const icon = sensorIcon(stateObj); + if (icon) { + return icon; + } + + break; + } + + case "input_datetime": + if (!stateObj?.attributes.has_date) { + return "hass:clock"; + } + if (!stateObj.attributes.has_time) { + return "hass:calendar"; + } + break; + } + + if (domain in FIXED_DOMAIN_ICONS) { + return FIXED_DOMAIN_ICONS[domain]; } + + // eslint-disable-next-line + console.warn( + "Unable to find icon for domain " + domain + " (" + stateObj + ")" + ); + return DEFAULT_DOMAIN_ICON; }; diff --git a/src/common/entity/input_dateteime_icon.ts b/src/common/entity/input_dateteime_icon.ts deleted file mode 100644 index 75644ad72bfd..000000000000 --- a/src/common/entity/input_dateteime_icon.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** Return an icon representing an input datetime state. */ -import { HassEntity } from "home-assistant-js-websocket"; -import { domainIcon } from "./domain_icon"; - -export const inputDateTimeIcon = (state: HassEntity): string => { - if (!state.attributes.has_date) { - return "hass:clock"; - } - if (!state.attributes.has_time) { - return "hass:calendar"; - } - return domainIcon("input_datetime"); -}; diff --git a/src/common/entity/sensor_icon.ts b/src/common/entity/sensor_icon.ts index 7c8d2640274b..16d5b5c0655f 100644 --- a/src/common/entity/sensor_icon.ts +++ b/src/common/entity/sensor_icon.ts @@ -1,35 +1,23 @@ /** Return an icon representing a sensor state. */ import { HassEntity } from "home-assistant-js-websocket"; -import { UNIT_C, UNIT_F } from "../const"; -import { domainIcon } from "./domain_icon"; +import { FIXED_DEVICE_CLASS_ICONS, UNIT_C, UNIT_F } from "../const"; import { batteryIcon } from "./battery_icon"; -const fixedDeviceClassIcons = { - current: "hass:current-ac", - energy: "hass:flash", - humidity: "hass:water-percent", - illuminance: "hass:brightness-5", - temperature: "hass:thermometer", - pressure: "hass:gauge", - power: "hass:flash", - power_factor: "hass:angle-acute", - signal_strength: "hass:wifi", - voltage: "hass:sine-wave", -}; - -export const sensorIcon = (state: HassEntity) => { - const dclass = state.attributes.device_class; +export const sensorIcon = (stateObj?: HassEntity): string | undefined => { + const dclass = stateObj?.attributes.device_class; - if (dclass && dclass in fixedDeviceClassIcons) { - return fixedDeviceClassIcons[dclass]; + if (dclass && dclass in FIXED_DEVICE_CLASS_ICONS) { + return FIXED_DEVICE_CLASS_ICONS[dclass]; } + if (dclass === "battery") { - return batteryIcon(state); + return stateObj ? batteryIcon(stateObj) : "hass:battery"; } - const unit = state.attributes.unit_of_measurement; + const unit = stateObj?.attributes.unit_of_measurement; if (unit === UNIT_C || unit === UNIT_F) { return "hass:thermometer"; } - return domainIcon("sensor"); + + return undefined; }; diff --git a/src/common/entity/state_icon.ts b/src/common/entity/state_icon.ts index 7291f6863e70..c87c7bdd2f19 100644 --- a/src/common/entity/state_icon.ts +++ b/src/common/entity/state_icon.ts @@ -1,19 +1,8 @@ /** Return an icon representing a state. */ import { HassEntity } from "home-assistant-js-websocket"; import { DEFAULT_DOMAIN_ICON } from "../const"; -import { binarySensorIcon } from "./binary_sensor_icon"; import { computeDomain } from "./compute_domain"; -import { coverIcon } from "./cover_icon"; import { domainIcon } from "./domain_icon"; -import { inputDateTimeIcon } from "./input_dateteime_icon"; -import { sensorIcon } from "./sensor_icon"; - -const domainIcons = { - binary_sensor: binarySensorIcon, - cover: coverIcon, - sensor: sensorIcon, - input_datetime: inputDateTimeIcon, -}; export const stateIcon = (state: HassEntity) => { if (!state) { @@ -23,10 +12,5 @@ export const stateIcon = (state: HassEntity) => { return state.attributes.icon; } - const domain = computeDomain(state.entity_id); - - if (domain in domainIcons) { - return domainIcons[domain](state); - } - return domainIcon(domain, state.state); + return domainIcon(computeDomain(state.entity_id), state); }; diff --git a/src/common/entity/state_more_info_type.ts b/src/common/entity/state_more_info_type.ts deleted file mode 100644 index 170b83052d11..000000000000 --- a/src/common/entity/state_more_info_type.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { HassEntity } from "home-assistant-js-websocket"; -import { DOMAINS_HIDE_MORE_INFO, DOMAINS_WITH_MORE_INFO } from "../const"; -import { computeStateDomain } from "./compute_state_domain"; - -export const stateMoreInfoType = (stateObj: HassEntity) => { - const domain = computeStateDomain(stateObj); - - if (DOMAINS_WITH_MORE_INFO.includes(domain)) { - return domain; - } - if (DOMAINS_HIDE_MORE_INFO.includes(domain)) { - return "hidden"; - } - return "default"; -}; diff --git a/src/components/entity/ha-state-label-badge.ts b/src/components/entity/ha-state-label-badge.ts index 3441979436ee..675a7b739adb 100644 --- a/src/components/entity/ha-state-label-badge.ts +++ b/src/components/entity/ha-state-label-badge.ts @@ -147,7 +147,7 @@ export class HaStateLabelBadge extends LitElement { return "hass:alert-circle"; } // state == 'disarmed' - return domainIcon(domain, state.state); + return domainIcon(domain, state); case "binary_sensor": case "device_tracker": case "updater": diff --git a/src/components/entity/state-info.js b/src/components/entity/state-info.js index 503240d84701..36014db18e3a 100644 --- a/src/components/entity/state-info.js +++ b/src/components/entity/state-info.js @@ -1,12 +1,14 @@ import { html } from "@polymer/polymer/lib/utils/html-tag"; +import "@polymer/paper-tooltip/paper-tooltip"; /* eslint-plugin-disable lit */ +import LocalizeMixin from "../../mixins/localize-mixin"; import { PolymerElement } from "@polymer/polymer/polymer-element"; import { computeStateName } from "../../common/entity/compute_state_name"; import { computeRTL } from "../../common/util/compute_rtl"; import "../ha-relative-time"; import "./state-badge"; -class StateInfo extends PolymerElement { +class StateInfo extends LocalizeMixin(PolymerElement) { static get template() { return html` ${this.styleTemplate} ${this.stateBadgeTemplate} ${this.infoTemplate} @@ -71,13 +73,20 @@ class StateInfo extends PolymerElement {
[[computeStateName(stateObj)]]
-