diff --git a/src/common/entity/state_color.ts b/src/common/entity/state_color.ts index 32d0be425b..efcac098f2 100644 --- a/src/common/entity/state_color.ts +++ b/src/common/entity/state_color.ts @@ -1,6 +1,7 @@ /** Return an color representing a state. */ import { HassEntity } from "home-assistant-js-websocket"; import { UNAVAILABLE } from "../../data/entity"; +import { computeGroupDomain, GroupEntity } from "../../data/group"; import { computeCssVariable } from "../../resources/css-variables"; import { slugify } from "../string/slugify"; import { batteryStateColorProperty } from "./color/battery_color"; @@ -52,11 +53,11 @@ export const stateColorCss = (stateObj: HassEntity, state?: string) => { }; export const domainStateColorProperties = ( + domain: string, stateObj: HassEntity, state?: string ): string[] => { const compareState = state !== undefined ? state : stateObj.state; - const domain = computeDomain(stateObj.entity_id); const active = stateActive(stateObj, state); const properties: string[] = []; @@ -95,8 +96,16 @@ export const stateColorProperties = ( } } + // Special rules for group coloring + if (domain === "group") { + const groupDomain = computeGroupDomain(stateObj as GroupEntity); + if (groupDomain && STATE_COLORED_DOMAIN.has(groupDomain)) { + return domainStateColorProperties(groupDomain, stateObj, state); + } + } + if (STATE_COLORED_DOMAIN.has(domain)) { - return domainStateColorProperties(stateObj, state); + return domainStateColorProperties(domain, stateObj, state); } return undefined; diff --git a/src/components/ha-control-slider.ts b/src/components/ha-control-slider.ts index 6b31e6d2e6..2cc172969a 100644 --- a/src/components/ha-control-slider.ts +++ b/src/components/ha-control-slider.ts @@ -244,6 +244,7 @@ export class HaControlSlider extends LitElement { })} >
+ ${this.mode === "cursor" ? this.value != null ? html` @@ -310,6 +311,13 @@ export class HaControlSlider extends LitElement { background: var(--control-slider-background); opacity: var(--control-slider-background-opacity); } + ::slotted([slot="background"]) { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + } .slider .slider-track-bar { --border-radius: var(--control-slider-border-radius); --handle-size: 4px; @@ -424,6 +432,7 @@ export class HaControlSlider extends LitElement { bottom: 0; left: calc(var(--value, 0) * (100% - var(--cursor-size))); width: var(--cursor-size); + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); } .slider .slider-track-cursor:after { height: 50%; diff --git a/src/data/entity_attributes.ts b/src/data/entity_attributes.ts index dc082e0b63..7e3fae87aa 100644 --- a/src/data/entity_attributes.ts +++ b/src/data/entity_attributes.ts @@ -1,4 +1,5 @@ export const STATE_ATTRIBUTES = [ + "entity_id", "assumed_state", "attribution", "custom_ui_more_info", diff --git a/src/dialogs/more-info/components/cover/ha-more-info-cover-buttons.ts b/src/dialogs/more-info/components/cover/ha-more-info-cover-buttons.ts new file mode 100644 index 0000000000..99ee617ecb --- /dev/null +++ b/src/dialogs/more-info/components/cover/ha-more-info-cover-buttons.ts @@ -0,0 +1,311 @@ +import { mdiArrowBottomLeft, mdiArrowTopRight, mdiStop } from "@mdi/js"; +import { + css, + CSSResultGroup, + html, + LitElement, + nothing, + TemplateResult, +} from "lit"; +import { customElement, property } from "lit/decorators"; +import { repeat } from "lit/directives/repeat"; +import memoizeOne from "memoize-one"; +import { + computeCloseIcon, + computeOpenIcon, +} from "../../../../common/entity/cover_icon"; +import { supportsFeature } from "../../../../common/entity/supports-feature"; +import "../../../../components/ha-control-button"; +import "../../../../components/ha-control-button-group"; +import "../../../../components/ha-control-slider"; +import "../../../../components/ha-svg-icon"; +import { + canClose, + canCloseTilt, + canOpen, + canOpenTilt, + canStop, + canStopTilt, + CoverEntity, + CoverEntityFeature, +} from "../../../../data/cover"; +import { HomeAssistant } from "../../../../types"; + +type CoverButton = + | "open" + | "close" + | "stop" + | "open-tilt" + | "close-tilt" + | "none"; + +type CoverLayout = { + type: "line" | "cross"; + buttons: CoverButton[]; +}; + +export const getCoverLayout = memoizeOne( + (stateObj: CoverEntity): CoverLayout => { + const supportsOpen = supportsFeature(stateObj, CoverEntityFeature.OPEN); + const supportsClose = supportsFeature(stateObj, CoverEntityFeature.CLOSE); + const supportsStop = supportsFeature(stateObj, CoverEntityFeature.STOP); + const supportsOpenTilt = supportsFeature( + stateObj, + CoverEntityFeature.OPEN_TILT + ); + const supportsCloseTilt = supportsFeature( + stateObj, + CoverEntityFeature.CLOSE_TILT + ); + const supportsStopTilt = supportsFeature( + stateObj, + CoverEntityFeature.STOP_TILT + ); + + if ( + (supportsOpen || supportsClose) && + (supportsOpenTilt || supportsCloseTilt) + ) { + return { + type: "cross", + buttons: [ + supportsOpen ? "open" : "none", + supportsCloseTilt ? "close-tilt" : "none", + supportsStop || supportsStopTilt ? "stop" : "none", + supportsOpenTilt ? "open-tilt" : "none", + supportsClose ? "close" : "none", + ], + }; + } + + if (supportsOpen || supportsClose) { + const buttons: CoverButton[] = []; + if (supportsOpen) buttons.push("open"); + if (supportsStop) buttons.push("stop"); + if (supportsClose) buttons.push("close"); + return { + type: "line", + buttons, + }; + } + + if (supportsOpenTilt || supportsCloseTilt) { + const buttons: CoverButton[] = []; + if (supportsOpenTilt) buttons.push("open-tilt"); + if (supportsStopTilt) buttons.push("stop"); + if (supportsCloseTilt) buttons.push("close-tilt"); + return { + type: "line", + buttons, + }; + } + + return { + type: "line", + buttons: [], + }; + } +); + +@customElement("ha-more-info-cover-buttons") +export class HaMoreInfoCoverButtons extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public stateObj!: CoverEntity; + + private _onOpenTap(ev): void { + ev.stopPropagation(); + this.hass!.callService("cover", "open_cover", { + entity_id: this.stateObj!.entity_id, + }); + } + + private _onCloseTap(ev): void { + ev.stopPropagation(); + this.hass!.callService("cover", "close_cover", { + entity_id: this.stateObj!.entity_id, + }); + } + + private _onOpenTiltTap(ev): void { + ev.stopPropagation(); + this.hass!.callService("cover", "open_cover_tilt", { + entity_id: this.stateObj!.entity_id, + }); + } + + private _onCloseTiltTap(ev): void { + ev.stopPropagation(); + this.hass!.callService("cover", "close_cover_tilt", { + entity_id: this.stateObj!.entity_id, + }); + } + + private _onStopTap(ev): void { + ev.stopPropagation(); + if (supportsFeature(this.stateObj, CoverEntityFeature.STOP)) { + this.hass!.callService("cover", "stop_cover", { + entity_id: this.stateObj!.entity_id, + }); + } + if (supportsFeature(this.stateObj, CoverEntityFeature.STOP_TILT)) { + this.hass!.callService("cover", "stop_cover_tilt", { + entity_id: this.stateObj!.entity_id, + }); + } + } + + protected renderButton(button: CoverButton | undefined) { + if (button === "open") { + return html` + + + + `; + } + if (button === "close") { + return html` + + + + `; + } + if (button === "stop") { + return html` + + + + `; + } + if (button === "open-tilt") { + return html` + + + + `; + } + if (button === "close-tilt") { + return html` + + + + `; + } + return nothing; + } + + protected render(): TemplateResult { + const layout = getCoverLayout(this.stateObj); + + return html` + ${layout.type === "line" + ? html` + + ${repeat( + layout.buttons, + (action) => action, + (action) => this.renderButton(action) + )} + + ` + : nothing} + ${layout.type === "cross" + ? html` +
+ ${repeat( + layout.buttons, + (action) => action, + (action) => this.renderButton(action) + )} +
+ ` + : nothing} + `; + } + + static get styles(): CSSResultGroup { + return css` + ha-control-button-group { + height: 45vh; + max-height: 320px; + min-height: 200px; + --control-button-group-spacing: 6px; + --control-button-group-thickness: 100px; + } + .cross-container { + height: 45vh; + max-height: 320px; + min-height: 200px; + display: grid; + grid-gap: 10px; + grid-template-columns: repeat(3, min(100px, 25vw, 15vh)); + grid-template-rows: repeat(3, min(100px, 25vw, 15vh)); + grid-template-areas: ". open ." "close-tilt stop open-tilt" ". close ."; + } + .cross-container > * { + width: 100%; + height: 100%; + } + .cross-container > [data-button="open"] { + grid-area: open; + } + .cross-container > [data-button="close"] { + grid-area: close; + } + .cross-container > [data-button="open-tilt"] { + grid-area: open-tilt; + } + .cross-container > [data-button="close-tilt"] { + grid-area: close-tilt; + } + .cross-container > [data-button="stop"] { + grid-area: stop; + } + ha-control-button { + --control-button-border-radius: 18px; + --mdc-icon-size: 24px; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-more-info-cover-buttons": HaMoreInfoCoverButtons; + } +} diff --git a/src/dialogs/more-info/components/cover/ha-more-info-cover-position.ts b/src/dialogs/more-info/components/cover/ha-more-info-cover-position.ts new file mode 100644 index 0000000000..ea30c823c6 --- /dev/null +++ b/src/dialogs/more-info/components/cover/ha-more-info-cover-position.ts @@ -0,0 +1,87 @@ +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { styleMap } from "lit/directives/style-map"; +import { computeAttributeNameDisplay } from "../../../../common/entity/compute_attribute_display"; +import { stateColorCss } from "../../../../common/entity/state_color"; +import "../../../../components/ha-control-slider"; +import { CoverEntity } from "../../../../data/cover"; +import { UNAVAILABLE } from "../../../../data/entity"; +import { HomeAssistant } from "../../../../types"; + +@customElement("ha-more-info-cover-position") +export class HaMoreInfoCoverPosition extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public stateObj!: CoverEntity; + + @state() value?: number; + + protected updated(changedProp: Map): void { + if (changedProp.has("stateObj")) { + const currentPosition = this.stateObj?.attributes.current_position; + this.value = + currentPosition != null ? Math.round(currentPosition) : undefined; + } + } + + private _valueChanged(ev: CustomEvent) { + const value = (ev.detail as any).value; + if (isNaN(value)) return; + + this.hass.callService("cover", "set_cover_position", { + entity_id: this.stateObj!.entity_id, + position: value, + }); + } + + protected render(): TemplateResult { + const color = stateColorCss(this.stateObj); + + return html` + + + `; + } + + static get styles(): CSSResultGroup { + return css` + ha-control-slider { + height: 45vh; + max-height: 320px; + min-height: 200px; + /* Force inactive state to be colored for the slider */ + --state-cover-inactive-color: var(--state-cover-active-color); + --control-slider-thickness: 100px; + --control-slider-border-radius: 24px; + --control-slider-color: var(--primary-color); + --control-slider-background: var(--disabled-color); + --control-slider-background-opacity: 0.2; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-more-info-cover-position": HaMoreInfoCoverPosition; + } +} diff --git a/src/dialogs/more-info/components/cover/ha-more-info-cover-tilt-position.ts b/src/dialogs/more-info/components/cover/ha-more-info-cover-tilt-position.ts new file mode 100644 index 0000000000..b9b52d65de --- /dev/null +++ b/src/dialogs/more-info/components/cover/ha-more-info-cover-tilt-position.ts @@ -0,0 +1,128 @@ +import { + css, + CSSResultGroup, + html, + LitElement, + TemplateResult, + unsafeCSS, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { styleMap } from "lit/directives/style-map"; +import { computeAttributeNameDisplay } from "../../../../common/entity/compute_attribute_display"; +import { stateColorCss } from "../../../../common/entity/state_color"; +import "../../../../components/ha-control-slider"; +import { CoverEntity } from "../../../../data/cover"; +import { UNAVAILABLE } from "../../../../data/entity"; +import { HomeAssistant } from "../../../../types"; + +function generateTiltSliderTrackBackgroundGradient() { + const count = 24; + const minStrokeWidth = 0.2; + const gradient: [number, string][] = []; + + for (let i = 0; i < count; i++) { + const stopOffset1 = i / count; + const stopOffset2 = + stopOffset1 + + (i / count ** 2) * (1 - minStrokeWidth) + + minStrokeWidth / count; + + if (i !== 0) { + gradient.push([stopOffset1, "transparent"]); + } + gradient.push([stopOffset1, "var(--control-slider-color)"]); + gradient.push([stopOffset2, "var(--control-slider-color)"]); + gradient.push([stopOffset2, "transparent"]); + } + + return unsafeCSS( + gradient + .map(([stop, color]) => `${color} ${(stop as number) * 100}%`) + .join(", ") + ); +} + +const GRADIENT = generateTiltSliderTrackBackgroundGradient(); + +@customElement("ha-more-info-cover-tilt-position") +export class HaMoreInfoCoverTiltPosition extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public stateObj!: CoverEntity; + + @state() value?: number; + + protected updated(changedProp: Map): void { + if (changedProp.has("stateObj")) { + this.value = + this.stateObj.attributes.current_tilt_position != null + ? Math.round(this.stateObj.attributes.current_tilt_position) + : undefined; + } + } + + private _valueChanged(ev: CustomEvent) { + const value = (ev.detail as any).value; + if (isNaN(value)) return; + + this.hass.callService("cover", "set_cover_tilt_position", { + entity_id: this.stateObj!.entity_id, + tilt_position: value, + }); + } + + protected render(): TemplateResult { + const color = stateColorCss(this.stateObj); + const isUnavailable = this.stateObj.state === UNAVAILABLE; + + return html` + +
+
+ `; + } + + static get styles(): CSSResultGroup { + return css` + ha-control-slider { + height: 45vh; + max-height: 320px; + min-height: 200px; + /* Force inactive state to be colored for the slider */ + --state-cover-inactive-color: var(--state-cover-active-color); + --control-slider-thickness: 100px; + --control-slider-border-radius: 24px; + --control-slider-color: var(--primary-color); + --control-slider-background-opacity: 0.2; + --control-slider-background: var(--control-slider-color); + } + .gradient { + background: -webkit-linear-gradient(top, ${GRADIENT}); + opacity: 0.6; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-more-info-cover-tilt-position": HaMoreInfoCoverTiltPosition; + } +} diff --git a/src/dialogs/more-info/components/cover/ha-more-info-cover-toggle.ts b/src/dialogs/more-info/components/cover/ha-more-info-cover-toggle.ts new file mode 100644 index 0000000000..0ee9b54172 --- /dev/null +++ b/src/dialogs/more-info/components/cover/ha-more-info-cover-toggle.ts @@ -0,0 +1,169 @@ +import { HassEntity } from "home-assistant-js-websocket"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import { styleMap } from "lit/directives/style-map"; +import { domainIcon } from "../../../../common/entity/domain_icon"; +import { stateColorCss } from "../../../../common/entity/state_color"; +import "../../../../components/ha-control-button"; +import "../../../../components/ha-control-switch"; +import { UNAVAILABLE, UNKNOWN } from "../../../../data/entity"; +import { forwardHaptic } from "../../../../data/haptics"; +import { HomeAssistant } from "../../../../types"; + +@customElement("ha-more-info-cover-toggle") +export class HaMoreInfoCoverToggle extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public stateObj!: HassEntity; + + private _valueChanged(ev) { + const checked = ev.target.checked as boolean; + + if (checked) { + this._turnOn(); + } else { + this._turnOff(); + } + } + + private _turnOn() { + this._callService(true); + } + + private _turnOff() { + this._callService(false); + } + + private async _callService(turnOn): Promise { + if (!this.hass || !this.stateObj) { + return; + } + forwardHaptic("light"); + + await this.hass.callService( + "cover", + turnOn ? "open_cover" : "close_cover", + { + entity_id: this.stateObj.entity_id, + } + ); + } + + protected render(): TemplateResult { + const onColor = stateColorCss(this.stateObj, "open"); + const offColor = stateColorCss(this.stateObj, "closed"); + + const isOn = + this.stateObj.state === "open" || + this.stateObj.state === "closing" || + this.stateObj.state === "opening"; + const isOff = this.stateObj.state === "closed"; + + if ( + this.stateObj.attributes.assumed_state || + this.stateObj.state === UNKNOWN + ) { + return html` +
+ + + + + + +
+ `; + } + + return html` + + + `; + } + + static get styles(): CSSResultGroup { + return css` + ha-control-switch { + height: 45vh; + max-height: 320px; + min-height: 200px; + --control-switch-thickness: 100px; + --control-switch-border-radius: 24px; + --control-switch-padding: 6px; + --mdc-icon-size: 24px; + } + .buttons { + display: flex; + flex-direction: column; + width: 100px; + height: 45vh; + max-height: 320px; + min-height: 200px; + padding: 6px; + box-sizing: border-box; + } + ha-control-button { + flex: 1; + width: 100%; + --control-button-border-radius: 18px; + --mdc-icon-size: 24px; + } + ha-control-button.active { + --control-button-icon-color: white; + --control-button-background-color: var(--color); + --control-button-background-opacity: 1; + } + ha-control-button:not(:last-child) { + margin-bottom: 6px; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-more-info-cover-toggle": HaMoreInfoCoverToggle; + } +} diff --git a/src/dialogs/more-info/components/ha-more-info-control-style.ts b/src/dialogs/more-info/components/ha-more-info-control-style.ts index 81032af738..5f33a1a53d 100644 --- a/src/dialogs/more-info/components/ha-more-info-control-style.ts +++ b/src/dialogs/more-info/components/ha-more-info-control-style.ts @@ -14,6 +14,10 @@ export const moreInfoControlStyle = css` align-items: center; } + .controls:not(:last-child) { + margin-bottom: 24px; + } + .controls > *:not(:last-child) { margin-bottom: 24px; } diff --git a/src/dialogs/more-info/const.ts b/src/dialogs/more-info/const.ts index 9454bc39aa..e6f48400d2 100644 --- a/src/dialogs/more-info/const.ts +++ b/src/dialogs/more-info/const.ts @@ -17,6 +17,7 @@ export const EDITABLE_DOMAINS_WITH_ID = ["scene", "automation"]; export const EDITABLE_DOMAINS_WITH_UNIQUE_ID = ["script"]; /** Domains with with new more info design. */ export const DOMAINS_WITH_NEW_MORE_INFO = [ + "cover", "fan", "input_boolean", "light", diff --git a/src/dialogs/more-info/controls/more-info-cover.ts b/src/dialogs/more-info/controls/more-info-cover.ts index 2b01a2fcad..659862a9b1 100644 --- a/src/dialogs/more-info/controls/more-info-cover.ts +++ b/src/dialogs/more-info/controls/more-info-cover.ts @@ -1,89 +1,195 @@ -import { css, CSSResult, html, LitElement, nothing } from "lit"; -import { customElement, property } from "lit/decorators"; -import { attributeClassNames } from "../../../common/entity/attribute_class_names"; +import { mdiMenu, mdiSwapVertical } from "@mdi/js"; import { - FeatureClassNames, - featureClassNames, -} from "../../../common/entity/feature_class_names"; + css, + CSSResultGroup, + html, + LitElement, + nothing, + PropertyValues, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { computeStateDisplay } from "../../../common/entity/compute_state_display"; import { supportsFeature } from "../../../common/entity/supports-feature"; +import { blankBeforePercent } from "../../../common/translations/blank_before_percent"; import "../../../components/ha-attributes"; -import "../../../components/ha-cover-tilt-controls"; -import "../../../components/ha-labeled-slider"; -import { - CoverEntity, - CoverEntityFeature, - isTiltOnly, -} from "../../../data/cover"; -import { HomeAssistant } from "../../../types"; - -export const FEATURE_CLASS_NAMES: FeatureClassNames = { - [CoverEntityFeature.SET_POSITION]: "has-set_position", - [CoverEntityFeature.OPEN_TILT]: "has-open_tilt", - [CoverEntityFeature.CLOSE_TILT]: "has-close_tilt", - [CoverEntityFeature.STOP_TILT]: "has-stop_tilt", - [CoverEntityFeature.SET_TILT_POSITION]: "has-set_tilt_position", -}; +import { CoverEntity, CoverEntityFeature } from "../../../data/cover"; +import type { HomeAssistant } from "../../../types"; +import "../components/cover/ha-more-info-cover-buttons"; +import "../components/cover/ha-more-info-cover-position"; +import "../components/cover/ha-more-info-cover-tilt-position"; +import "../components/cover/ha-more-info-cover-toggle"; +import { moreInfoControlStyle } from "../components/ha-more-info-control-style"; +import "../components/ha-more-info-state-header"; @customElement("more-info-cover") class MoreInfoCover extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ attribute: false }) public stateObj!: CoverEntity; + @property({ attribute: false }) public stateObj?: CoverEntity; + + @state() private _displayedPosition?: number; + + @state() private _mode?: "position" | "button"; + + private _toggleMode() { + this._mode = this._mode === "position" ? "button" : "position"; + } + + private _positionChanged(ev) { + const value = (ev.detail as any).value; + if (isNaN(value)) return; + this._displayedPosition = value; + } + + protected willUpdate(changedProps: PropertyValues): void { + super.willUpdate(changedProps); + if (changedProps.has("stateObj") && this.stateObj) { + if (supportsFeature(this.stateObj, CoverEntityFeature.SET_POSITION)) { + const currentPosition = this.stateObj?.attributes.current_position; + this._displayedPosition = + currentPosition != null ? Math.round(currentPosition) : undefined; + } + if (!this._mode) { + this._mode = + supportsFeature(this.stateObj, CoverEntityFeature.SET_POSITION) || + supportsFeature(this.stateObj, CoverEntityFeature.SET_TILT_POSITION) + ? "position" + : "button"; + } + } + } + + private get _stateOverride() { + if (this._displayedPosition == null) return undefined; + + const tempState = { + ...this.stateObj, + state: this._displayedPosition ? "open" : "closed", + attributes: { + ...this.stateObj!.attributes, + current_position: this._displayedPosition, + }, + } as CoverEntity; + + const stateDisplay = computeStateDisplay( + this.hass.localize, + tempState!, + this.hass.locale, + this.hass.entities + ); + + return this._displayedPosition && this._displayedPosition !== 100 + ? `${stateDisplay} - ${Math.round( + this._displayedPosition + )}${blankBeforePercent(this.hass!.locale)}%` + : stateDisplay; + } protected render() { - if (!this.stateObj) { + if (!this.hass || !this.stateObj) { return nothing; } - const _isTiltOnly = isTiltOnly(this.stateObj); + const supportsPosition = supportsFeature( + this.stateObj, + CoverEntityFeature.SET_POSITION + ); + + const supportsTiltPosition = supportsFeature( + this.stateObj, + CoverEntityFeature.SET_TILT_POSITION + ); + + const supportsOpenClose = + supportsFeature(this.stateObj, CoverEntityFeature.OPEN) || + supportsFeature(this.stateObj, CoverEntityFeature.CLOSE) || + supportsFeature(this.stateObj, CoverEntityFeature.STOP); + + const supportsTilt = + supportsFeature(this.stateObj, CoverEntityFeature.OPEN_TILT) || + supportsFeature(this.stateObj, CoverEntityFeature.CLOSE_TILT) || + supportsFeature(this.stateObj, CoverEntityFeature.STOP_TILT); + + const supportsOpenCloseWithoutStop = + supportsFeature(this.stateObj, CoverEntityFeature.OPEN) && + supportsFeature(this.stateObj, CoverEntityFeature.CLOSE) && + !supportsFeature(this.stateObj, CoverEntityFeature.STOP) && + !supportsFeature(this.stateObj, CoverEntityFeature.OPEN_TILT) && + !supportsFeature(this.stateObj, CoverEntityFeature.CLOSE_TILT); return html` -
-
- -
- -
- ${supportsFeature(this.stateObj, CoverEntityFeature.SET_TILT_POSITION) - ? // Either render the labeled slider and put the tilt buttons into its slot - // or (if tilt position is not supported and therefore no slider is shown) - // render a title
(same style as for a labeled slider) and directly put - // the tilt controls on the more-info. - html` - ${!_isTiltOnly - ? html` ` - : nothing} - ` - : !_isTiltOnly - ? html` -
- ${this.hass.localize("ui.card.cover.tilt_position")} -
- - ` - : nothing} + +
+
+ ${ + this._mode === "position" + ? html` + ${supportsPosition + ? html` + + ` + : nothing} + ${supportsTiltPosition + ? html` + + ` + : nothing} + ` + : nothing + } + ${ + this._mode === "button" + ? html` + ${supportsOpenCloseWithoutStop + ? html` + + ` + : supportsOpenClose || supportsTilt + ? html` + + ` + : nothing} + ` + : nothing + } +
+ ${ + (supportsPosition || supportsTiltPosition) && + (supportsOpenClose || supportsTilt) + ? html` +
+ +
+ ` + : nothing + }
* { + margin: 0 8px; + } + `, ]; - return classes.join(" "); - } - - private _coverPositionSliderChanged(ev) { - this.hass.callService("cover", "set_cover_position", { - entity_id: this.stateObj.entity_id, - position: ev.target.value, - }); - } - - private _coverTiltPositionSliderChanged(ev) { - this.hass.callService("cover", "set_cover_tilt_position", { - entity_id: this.stateObj.entity_id, - tilt_position: ev.target.value, - }); - } - - static get styles(): CSSResult { - return css` - .current_position, - .tilt { - max-height: 0px; - overflow: hidden; - } - - .has-set_position .current_position, - .has-current_position .current_position, - .has-open_tilt .tilt, - .has-close_tilt .tilt, - .has-stop_tilt .tilt, - .has-set_tilt_position .tilt, - .has-current_tilt_position .tilt { - max-height: 208px; - } - - /* from ha-labeled-slider for consistent look */ - .title { - margin: 5px 0 8px; - color: var(--primary-text-color); - } - `; } } diff --git a/src/dialogs/more-info/controls/more-info-group.ts b/src/dialogs/more-info/controls/more-info-group.ts index 2e2b069e68..0f5131c395 100644 --- a/src/dialogs/more-info/controls/more-info-group.ts +++ b/src/dialogs/more-info/controls/more-info-group.ts @@ -59,6 +59,7 @@ class MoreInfoGroup extends LitElement { attributes: { ...baseStateObj.attributes, friendly_name: this.stateObj.attributes.friendly_name, + entity_id: this.stateObj.attributes.entity_id, }, }; const type = domainMoreInfoType(groupDomain); diff --git a/src/resources/ha-style.ts b/src/resources/ha-style.ts index 0628b3dc59..3bb765c1cd 100644 --- a/src/resources/ha-style.ts +++ b/src/resources/ha-style.ts @@ -96,8 +96,8 @@ documentContainer.innerHTML = ` --disabled-color: #bdbdbd; --red-color: #f44336; --pink-color: #e91e63; - --purple-color: #9c27b0; - --deep-purple-color: #673ab7; + --purple-color: #926bc7; + --deep-purple-color: #6e41ab; --indigo-color: #3f51b5; --blue-color: #2196f3; --light-blue-color: #03a9f4; diff --git a/src/translations/en.json b/src/translations/en.json index 4c4d1bdf74..eef6d37035 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -891,7 +891,11 @@ "close_cover": "Close cover", "open_tilt_cover": "Open cover tilt", "close_tilt_cover": "Close cover tilt", - "stop_cover": "Stop cover" + "stop_cover": "Stop cover", + "switch_mode": { + "button": "Switch to button mode", + "position": "Switch to position mode" + } }, "zone": { "graph_unit": "People home"