mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
Add cover and light controls to tile card (#14244)
This commit is contained in:
parent
fe4191aea9
commit
a958c6296b
@ -65,9 +65,6 @@ export class HaBarSlider extends LitElement {
|
|||||||
@property({ type: Number })
|
@property({ type: Number })
|
||||||
public max = 100;
|
public max = 100;
|
||||||
|
|
||||||
@property()
|
|
||||||
public label?: string;
|
|
||||||
|
|
||||||
private _mc?: HammerManager;
|
private _mc?: HammerManager;
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true })
|
@property({ type: Boolean, reflect: true })
|
||||||
|
@ -5,14 +5,12 @@ import { classMap } from "lit/directives/class-map";
|
|||||||
import { computeCloseIcon, computeOpenIcon } from "../common/entity/cover_icon";
|
import { computeCloseIcon, computeOpenIcon } from "../common/entity/cover_icon";
|
||||||
import { supportsFeature } from "../common/entity/supports-feature";
|
import { supportsFeature } from "../common/entity/supports-feature";
|
||||||
import {
|
import {
|
||||||
|
canClose,
|
||||||
|
canOpen,
|
||||||
|
canStop,
|
||||||
CoverEntity,
|
CoverEntity,
|
||||||
CoverEntityFeature,
|
CoverEntityFeature,
|
||||||
isClosing,
|
|
||||||
isFullyClosed,
|
|
||||||
isFullyOpen,
|
|
||||||
isOpening,
|
|
||||||
} from "../data/cover";
|
} from "../data/cover";
|
||||||
import { UNAVAILABLE } from "../data/entity";
|
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import "./ha-icon-button";
|
import "./ha-icon-button";
|
||||||
|
|
||||||
@ -37,7 +35,7 @@ class HaCoverControls extends LitElement {
|
|||||||
"ui.dialogs.more_info_control.cover.open_cover"
|
"ui.dialogs.more_info_control.cover.open_cover"
|
||||||
)}
|
)}
|
||||||
@click=${this._onOpenTap}
|
@click=${this._onOpenTap}
|
||||||
.disabled=${this._computeOpenDisabled()}
|
.disabled=${!canOpen(this.stateObj)}
|
||||||
.path=${computeOpenIcon(this.stateObj)}
|
.path=${computeOpenIcon(this.stateObj)}
|
||||||
>
|
>
|
||||||
</ha-icon-button>
|
</ha-icon-button>
|
||||||
@ -50,7 +48,7 @@ class HaCoverControls extends LitElement {
|
|||||||
)}
|
)}
|
||||||
.path=${mdiStop}
|
.path=${mdiStop}
|
||||||
@click=${this._onStopTap}
|
@click=${this._onStopTap}
|
||||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
.disabled=${!canStop(this.stateObj)}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
@ -60,7 +58,7 @@ class HaCoverControls extends LitElement {
|
|||||||
"ui.dialogs.more_info_control.cover.close_cover"
|
"ui.dialogs.more_info_control.cover.close_cover"
|
||||||
)}
|
)}
|
||||||
@click=${this._onCloseTap}
|
@click=${this._onCloseTap}
|
||||||
.disabled=${this._computeClosedDisabled()}
|
.disabled=${!canClose(this.stateObj)}
|
||||||
.path=${computeCloseIcon(this.stateObj)}
|
.path=${computeCloseIcon(this.stateObj)}
|
||||||
>
|
>
|
||||||
</ha-icon-button>
|
</ha-icon-button>
|
||||||
@ -68,27 +66,6 @@ class HaCoverControls extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeOpenDisabled(): boolean {
|
|
||||||
if (this.stateObj.state === UNAVAILABLE) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const assumedState = this.stateObj.attributes.assumed_state === true;
|
|
||||||
return (
|
|
||||||
(isFullyOpen(this.stateObj) || isOpening(this.stateObj)) && !assumedState
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _computeClosedDisabled(): boolean {
|
|
||||||
if (this.stateObj.state === UNAVAILABLE) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const assumedState = this.stateObj.attributes.assumed_state === true;
|
|
||||||
return (
|
|
||||||
(isFullyClosed(this.stateObj) || isClosing(this.stateObj)) &&
|
|
||||||
!assumedState
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _onOpenTap(ev): void {
|
private _onOpenTap(ev): void {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.hass.callService("cover", "open_cover", {
|
this.hass.callService("cover", "open_cover", {
|
||||||
|
@ -4,12 +4,12 @@ import { customElement, property } from "lit/decorators";
|
|||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { supportsFeature } from "../common/entity/supports-feature";
|
import { supportsFeature } from "../common/entity/supports-feature";
|
||||||
import {
|
import {
|
||||||
|
canCloseTilt,
|
||||||
|
canOpenTilt,
|
||||||
|
canStopTilt,
|
||||||
CoverEntity,
|
CoverEntity,
|
||||||
CoverEntityFeature,
|
CoverEntityFeature,
|
||||||
isFullyClosedTilt,
|
|
||||||
isFullyOpenTilt,
|
|
||||||
} from "../data/cover";
|
} from "../data/cover";
|
||||||
import { UNAVAILABLE } from "../data/entity";
|
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import "./ha-icon-button";
|
import "./ha-icon-button";
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ class HaCoverTiltControls extends LitElement {
|
|||||||
)}
|
)}
|
||||||
.path=${mdiArrowTopRight}
|
.path=${mdiArrowTopRight}
|
||||||
@click=${this._onOpenTiltTap}
|
@click=${this._onOpenTiltTap}
|
||||||
.disabled=${this._computeOpenDisabled()}
|
.disabled=${!canOpenTilt(this.stateObj)}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
@ -50,7 +50,7 @@ class HaCoverTiltControls extends LitElement {
|
|||||||
)}
|
)}
|
||||||
.path=${mdiStop}
|
.path=${mdiStop}
|
||||||
@click=${this._onStopTiltTap}
|
@click=${this._onStopTiltTap}
|
||||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
.disabled=${!canStopTilt(this.stateObj)}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
@ -64,26 +64,10 @@ class HaCoverTiltControls extends LitElement {
|
|||||||
)}
|
)}
|
||||||
.path=${mdiArrowBottomLeft}
|
.path=${mdiArrowBottomLeft}
|
||||||
@click=${this._onCloseTiltTap}
|
@click=${this._onCloseTiltTap}
|
||||||
.disabled=${this._computeClosedDisabled()}
|
.disabled=${!canCloseTilt(this.stateObj)}
|
||||||
></ha-icon-button>`;
|
></ha-icon-button>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeOpenDisabled(): boolean {
|
|
||||||
if (this.stateObj.state === UNAVAILABLE) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const assumedState = this.stateObj.attributes.assumed_state === true;
|
|
||||||
return isFullyOpenTilt(this.stateObj) && !assumedState;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _computeClosedDisabled(): boolean {
|
|
||||||
if (this.stateObj.state === UNAVAILABLE) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const assumedState = this.stateObj.attributes.assumed_state === true;
|
|
||||||
return isFullyClosedTilt(this.stateObj) && !assumedState;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _onOpenTiltTap(ev): void {
|
private _onOpenTiltTap(ev): void {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.hass.callService("cover", "open_cover_tilt", {
|
this.hass.callService("cover", "open_cover_tilt", {
|
||||||
|
128
src/components/tile/ha-tile-button.ts
Normal file
128
src/components/tile/ha-tile-button.ts
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import { Ripple } from "@material/mwc-ripple";
|
||||||
|
import { RippleHandlers } from "@material/mwc-ripple/ripple-handlers";
|
||||||
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import {
|
||||||
|
customElement,
|
||||||
|
eventOptions,
|
||||||
|
property,
|
||||||
|
queryAsync,
|
||||||
|
state,
|
||||||
|
} from "lit/decorators";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
|
import "../ha-icon";
|
||||||
|
import "../ha-svg-icon";
|
||||||
|
|
||||||
|
@customElement("ha-tile-button")
|
||||||
|
export class HaTileButton extends LitElement {
|
||||||
|
@property({ type: Boolean, reflect: true }) disabled = false;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@queryAsync("mwc-ripple") private _ripple!: Promise<Ripple | null>;
|
||||||
|
|
||||||
|
@state() private _shouldRenderRipple = false;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="button"
|
||||||
|
aria-label=${ifDefined(this.label)}
|
||||||
|
.title=${this.label}
|
||||||
|
.disabled=${Boolean(this.disabled)}
|
||||||
|
@focus=${this.handleRippleFocus}
|
||||||
|
@blur=${this.handleRippleBlur}
|
||||||
|
@mousedown=${this.handleRippleActivate}
|
||||||
|
@mouseup=${this.handleRippleDeactivate}
|
||||||
|
@mouseenter=${this.handleRippleMouseEnter}
|
||||||
|
@mouseleave=${this.handleRippleMouseLeave}
|
||||||
|
@touchstart=${this.handleRippleActivate}
|
||||||
|
@touchend=${this.handleRippleDeactivate}
|
||||||
|
@touchcancel=${this.handleRippleDeactivate}
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
${this._shouldRenderRipple ? html`<mwc-ripple></mwc-ripple>` : ""}
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _rippleHandlers: RippleHandlers = new RippleHandlers(() => {
|
||||||
|
this._shouldRenderRipple = true;
|
||||||
|
return this._ripple;
|
||||||
|
});
|
||||||
|
|
||||||
|
@eventOptions({ passive: true })
|
||||||
|
private handleRippleActivate(evt?: Event) {
|
||||||
|
this._rippleHandlers.startPress(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleRippleDeactivate() {
|
||||||
|
this._rippleHandlers.endPress();
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleRippleMouseEnter() {
|
||||||
|
this._rippleHandlers.startHover();
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleRippleMouseLeave() {
|
||||||
|
this._rippleHandlers.endHover();
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleRippleFocus() {
|
||||||
|
this._rippleHandlers.startFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleRippleBlur() {
|
||||||
|
this._rippleHandlers.endFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
--icon-color: rgb(var(--color, var(--rgb-primary-text-color)));
|
||||||
|
--bg-color: rgba(var(--color, var(--rgb-disabled-color)), 0.2);
|
||||||
|
--mdc-ripple-color: rgba(var(--color, var(--rgb-disabled-color)));
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: none;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
transition: background-color 280ms ease-in-out, transform 180ms ease-out;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
line-height: 0;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.button ::slotted(*) {
|
||||||
|
--mdc-icon-size: 20px;
|
||||||
|
color: var(--icon-color);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.button:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
background-color: rgba(var(--rgb-disabled-color), 0.2);
|
||||||
|
}
|
||||||
|
.button:disabled ::slotted(*) {
|
||||||
|
color: rgb(var(--rgb-disabled-color));
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-tile-button": HaTileButton;
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ import {
|
|||||||
HassEntityBase,
|
HassEntityBase,
|
||||||
} from "home-assistant-js-websocket";
|
} from "home-assistant-js-websocket";
|
||||||
import { supportsFeature } from "../common/entity/supports-feature";
|
import { supportsFeature } from "../common/entity/supports-feature";
|
||||||
|
import { UNAVAILABLE } from "./entity";
|
||||||
|
|
||||||
export const enum CoverEntityFeature {
|
export const enum CoverEntityFeature {
|
||||||
OPEN = 1,
|
OPEN = 1,
|
||||||
@ -57,6 +58,46 @@ export function isTiltOnly(stateObj: CoverEntity) {
|
|||||||
return supportsTilt && !supportsCover;
|
return supportsTilt && !supportsCover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function canOpen(stateObj: CoverEntity) {
|
||||||
|
if (stateObj.state === UNAVAILABLE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const assumedState = stateObj.attributes.assumed_state === true;
|
||||||
|
return (!isFullyOpen(stateObj) && !isOpening(stateObj)) || assumedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canClose(stateObj: CoverEntity): boolean {
|
||||||
|
if (stateObj.state === UNAVAILABLE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const assumedState = stateObj.attributes.assumed_state === true;
|
||||||
|
return (!isFullyClosed(stateObj) && !isClosing(stateObj)) || assumedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canStop(stateObj: CoverEntity): boolean {
|
||||||
|
return stateObj.state !== UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canOpenTilt(stateObj: CoverEntity): boolean {
|
||||||
|
if (stateObj.state === UNAVAILABLE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const assumedState = stateObj.attributes.assumed_state === true;
|
||||||
|
return !isFullyOpenTilt(stateObj) || assumedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canCloseTilt(stateObj: CoverEntity): boolean {
|
||||||
|
if (stateObj.state === UNAVAILABLE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const assumedState = stateObj.attributes.assumed_state === true;
|
||||||
|
return !isFullyClosedTilt(stateObj) || assumedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canStopTilt(stateObj: CoverEntity): boolean {
|
||||||
|
return stateObj.state !== UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
interface CoverEntityAttributes extends HassEntityAttributeBase {
|
interface CoverEntityAttributes extends HassEntityAttributeBase {
|
||||||
current_position?: number;
|
current_position?: number;
|
||||||
current_tilt_position?: number;
|
current_tilt_position?: number;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { memoize } from "@fullcalendar/common";
|
import { memoize } from "@fullcalendar/common";
|
||||||
import { mdiHelp } from "@mdi/js";
|
import { mdiHelp } from "@mdi/js";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { computeRgbColor } from "../../../common/color/compute-color";
|
import { computeRgbColor } from "../../../common/color/compute-color";
|
||||||
@ -26,7 +26,11 @@ import { actionHandler } from "../common/directives/action-handler-directive";
|
|||||||
import { findEntities } from "../common/find-entities";
|
import { findEntities } from "../common/find-entities";
|
||||||
import { handleAction } from "../common/handle-action";
|
import { handleAction } from "../common/handle-action";
|
||||||
import "../components/hui-timestamp-display";
|
import "../components/hui-timestamp-display";
|
||||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
import { createTileExtraElement } from "../create-element/create-tile-extra-element";
|
||||||
|
import { supportsTileExtra } from "../tile-extra/tile-extras";
|
||||||
|
import { LovelaceTileExtraConfig } from "../tile-extra/types";
|
||||||
|
import { LovelaceCard, LovelaceCardEditor, LovelaceTileExtra } from "../types";
|
||||||
|
import { HuiErrorCard } from "./hui-error-card";
|
||||||
import { computeTileBadge } from "./tile/badges/tile-badge";
|
import { computeTileBadge } from "./tile/badges/tile-badge";
|
||||||
import { ThermostatCardConfig, TileCardConfig } from "./types";
|
import { ThermostatCardConfig, TileCardConfig } from "./types";
|
||||||
|
|
||||||
@ -69,8 +73,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
|||||||
throw new Error("Specify an entity");
|
throw new Error("Specify an entity");
|
||||||
}
|
}
|
||||||
|
|
||||||
const supportToggle =
|
const supportToggle = DOMAINS_TOGGLE.has(computeDomain(config.entity));
|
||||||
config.entity && DOMAINS_TOGGLE.has(computeDomain(config.entity));
|
|
||||||
|
|
||||||
this._config = {
|
this._config = {
|
||||||
tap_action: {
|
tap_action: {
|
||||||
@ -146,14 +149,14 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
|||||||
return stateColor;
|
return stateColor;
|
||||||
});
|
});
|
||||||
|
|
||||||
render() {
|
protected render(): TemplateResult {
|
||||||
if (!this._config || !this.hass) {
|
if (!this._config || !this.hass) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
const entityId = this._config.entity;
|
const entityId = this._config.entity;
|
||||||
const entity = entityId ? this.hass.states[entityId] : undefined;
|
const stateObj = entityId ? this.hass.states[entityId] : undefined;
|
||||||
|
|
||||||
if (!entity) {
|
if (!stateObj) {
|
||||||
return html`
|
return html`
|
||||||
<ha-card class="disabled">
|
<ha-card class="disabled">
|
||||||
<div class="tile">
|
<div class="tile">
|
||||||
@ -169,36 +172,40 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const domain = computeDomain(entity.entity_id);
|
const domain = computeDomain(stateObj.entity_id);
|
||||||
|
|
||||||
const icon = this._config.icon || entity.attributes.icon;
|
const icon = this._config.icon || stateObj.attributes.icon;
|
||||||
const iconPath = stateIconPath(entity);
|
const iconPath = stateIconPath(stateObj);
|
||||||
|
|
||||||
const name = this._config.name || entity.attributes.friendly_name;
|
const name = this._config.name || stateObj.attributes.friendly_name;
|
||||||
const stateDisplay =
|
const stateDisplay =
|
||||||
(entity.attributes.device_class === SENSOR_DEVICE_CLASS_TIMESTAMP ||
|
(stateObj.attributes.device_class === SENSOR_DEVICE_CLASS_TIMESTAMP ||
|
||||||
TIMESTAMP_STATE_DOMAINS.includes(domain)) &&
|
TIMESTAMP_STATE_DOMAINS.includes(domain)) &&
|
||||||
!UNAVAILABLE_STATES.includes(entity.state)
|
!UNAVAILABLE_STATES.includes(stateObj.state)
|
||||||
? html`
|
? html`
|
||||||
<hui-timestamp-display
|
<hui-timestamp-display
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.ts=${new Date(entity.state)}
|
.ts=${new Date(stateObj.state)}
|
||||||
.format=${this._config.format}
|
.format=${this._config.format}
|
||||||
capitalize
|
capitalize
|
||||||
></hui-timestamp-display>
|
></hui-timestamp-display>
|
||||||
`
|
`
|
||||||
: computeStateDisplay(this.hass!.localize, entity, this.hass.locale);
|
: computeStateDisplay(this.hass!.localize, stateObj, this.hass.locale);
|
||||||
|
|
||||||
const color = this._computeStateColor(entity, this._config.color);
|
const color = this._computeStateColor(stateObj, this._config.color);
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
"--tile-color": color,
|
"--tile-color": color,
|
||||||
};
|
};
|
||||||
|
|
||||||
const imageUrl = this._config.show_entity_picture
|
const imageUrl = this._config.show_entity_picture
|
||||||
? this._getImageUrl(entity)
|
? this._getImageUrl(stateObj)
|
||||||
: undefined;
|
: undefined;
|
||||||
const badge = computeTileBadge(entity, this.hass);
|
const badge = computeTileBadge(stateObj, this.hass);
|
||||||
|
|
||||||
|
const supportedExtras = this._config.extras?.filter((extra) =>
|
||||||
|
supportsTileExtra(stateObj, extra.type)
|
||||||
|
);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card style=${styleMap(style)}>
|
<ha-card style=${styleMap(style)}>
|
||||||
@ -246,15 +253,54 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
|||||||
.actionHandler=${actionHandler()}
|
.actionHandler=${actionHandler()}
|
||||||
></ha-tile-info>
|
></ha-tile-info>
|
||||||
</div>
|
</div>
|
||||||
|
${supportedExtras?.length
|
||||||
|
? html`
|
||||||
|
<div class="extras">
|
||||||
|
${supportedExtras.map((extraConf) =>
|
||||||
|
this.renderExtra(extraConf, stateObj)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: null}
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _extrasElements = new WeakMap<
|
||||||
|
LovelaceTileExtraConfig,
|
||||||
|
LovelaceTileExtra | HuiErrorCard
|
||||||
|
>();
|
||||||
|
|
||||||
|
private _getExtraElement(extra: LovelaceTileExtraConfig) {
|
||||||
|
if (!this._extrasElements.has(extra)) {
|
||||||
|
const element = createTileExtraElement(extra);
|
||||||
|
this._extrasElements.set(extra, element);
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._extrasElements.get(extra)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderExtra(
|
||||||
|
extraConf: LovelaceTileExtraConfig,
|
||||||
|
stateObj: HassEntity
|
||||||
|
): TemplateResult {
|
||||||
|
const element = this._getExtraElement(extraConf);
|
||||||
|
|
||||||
|
if (this.hass) {
|
||||||
|
element.hass = this.hass;
|
||||||
|
(element as LovelaceTileExtra).stateObj = stateObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`<div class="extra">${element}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
:host {
|
:host {
|
||||||
--tile-color: var(--rgb-disabled-color);
|
--tile-color: var(--rgb-disabled-color);
|
||||||
--tile-tap-padding: 6px;
|
--tile-tap-padding: 6px;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
ha-card {
|
ha-card {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
} from "../entity-rows/types";
|
} from "../entity-rows/types";
|
||||||
import { LovelaceHeaderFooterConfig } from "../header-footer/types";
|
import { LovelaceHeaderFooterConfig } from "../header-footer/types";
|
||||||
import { HaDurationData } from "../../../components/ha-duration-input";
|
import { HaDurationData } from "../../../components/ha-duration-input";
|
||||||
|
import { LovelaceTileExtraConfig } from "../tile-extra/types";
|
||||||
|
|
||||||
export interface AlarmPanelCardConfig extends LovelaceCardConfig {
|
export interface AlarmPanelCardConfig extends LovelaceCardConfig {
|
||||||
entity: string;
|
entity: string;
|
||||||
@ -501,4 +502,5 @@ export interface TileCardConfig extends LovelaceCardConfig {
|
|||||||
show_entity_picture?: string;
|
show_entity_picture?: string;
|
||||||
tap_action?: ActionConfig;
|
tap_action?: ActionConfig;
|
||||||
icon_tap_action?: ActionConfig;
|
icon_tap_action?: ActionConfig;
|
||||||
|
extras?: LovelaceTileExtraConfig[];
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import type { ErrorCardConfig } from "../cards/types";
|
|||||||
import { LovelaceElement, LovelaceElementConfig } from "../elements/types";
|
import { LovelaceElement, LovelaceElementConfig } from "../elements/types";
|
||||||
import { LovelaceRow, LovelaceRowConfig } from "../entity-rows/types";
|
import { LovelaceRow, LovelaceRowConfig } from "../entity-rows/types";
|
||||||
import { LovelaceHeaderFooterConfig } from "../header-footer/types";
|
import { LovelaceHeaderFooterConfig } from "../header-footer/types";
|
||||||
|
import { LovelaceTileExtraConfig } from "../tile-extra/types";
|
||||||
import {
|
import {
|
||||||
LovelaceBadge,
|
LovelaceBadge,
|
||||||
LovelaceCard,
|
LovelaceCard,
|
||||||
@ -18,6 +19,8 @@ import {
|
|||||||
LovelaceHeaderFooter,
|
LovelaceHeaderFooter,
|
||||||
LovelaceHeaderFooterConstructor,
|
LovelaceHeaderFooterConstructor,
|
||||||
LovelaceRowConstructor,
|
LovelaceRowConstructor,
|
||||||
|
LovelaceTileExtra,
|
||||||
|
LovelaceTileExtraConstructor,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
|
||||||
const TIMEOUT = 2000;
|
const TIMEOUT = 2000;
|
||||||
@ -53,6 +56,11 @@ interface CreateElementConfigTypes {
|
|||||||
element: LovelaceViewElement;
|
element: LovelaceViewElement;
|
||||||
constructor: unknown;
|
constructor: unknown;
|
||||||
};
|
};
|
||||||
|
"tile-extra": {
|
||||||
|
config: LovelaceTileExtraConfig;
|
||||||
|
element: LovelaceTileExtra;
|
||||||
|
constructor: LovelaceTileExtraConstructor;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createErrorCardElement = (config: ErrorCardConfig) => {
|
export const createErrorCardElement = (config: ErrorCardConfig) => {
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import { LovelaceTileExtraConfig } from "../tile-extra/types";
|
||||||
|
import {
|
||||||
|
createLovelaceElement,
|
||||||
|
getLovelaceElementClass,
|
||||||
|
} from "./create-element-base";
|
||||||
|
import "../tile-extra/hui-cover-open-close-tile-extra";
|
||||||
|
import "../tile-extra/hui-cover-tilt-tile-extra";
|
||||||
|
|
||||||
|
const TYPES: Set<LovelaceTileExtraConfig["type"]> = new Set([
|
||||||
|
"cover-open-close",
|
||||||
|
"cover-tilt",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const createTileExtraElement = (config: LovelaceTileExtraConfig) =>
|
||||||
|
createLovelaceElement("tile-extra", config, TYPES);
|
||||||
|
|
||||||
|
export const getTileExtraElementClass = (type: string) =>
|
||||||
|
getLovelaceElementClass(type, "tile-extra", TYPES);
|
@ -3,9 +3,18 @@ import { HassEntity } from "home-assistant-js-websocket";
|
|||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { assert, assign, boolean, object, optional, string } from "superstruct";
|
import {
|
||||||
|
any,
|
||||||
|
array,
|
||||||
|
assert,
|
||||||
|
assign,
|
||||||
|
boolean,
|
||||||
|
object,
|
||||||
|
optional,
|
||||||
|
string,
|
||||||
|
} from "superstruct";
|
||||||
import { THEME_COLORS } from "../../../../common/color/compute-color";
|
import { THEME_COLORS } from "../../../../common/color/compute-color";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||||
import { domainIcon } from "../../../../common/entity/domain_icon";
|
import { domainIcon } from "../../../../common/entity/domain_icon";
|
||||||
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
||||||
@ -13,9 +22,14 @@ import "../../../../components/ha-form/ha-form";
|
|||||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import type { TileCardConfig } from "../../cards/types";
|
import type { TileCardConfig } from "../../cards/types";
|
||||||
|
import { LovelaceTileExtraConfig } from "../../tile-extra/types";
|
||||||
import type { LovelaceCardEditor } from "../../types";
|
import type { LovelaceCardEditor } from "../../types";
|
||||||
|
import "../hui-sub-element-editor";
|
||||||
import { actionConfigStruct } from "../structs/action-struct";
|
import { actionConfigStruct } from "../structs/action-struct";
|
||||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||||
|
import { EditSubElementEvent, SubElementEditorConfig } from "../types";
|
||||||
|
import { configElementStyle } from "./config-elements-style";
|
||||||
|
import "./hui-tile-card-extras-editor";
|
||||||
|
|
||||||
const cardConfigStruct = assign(
|
const cardConfigStruct = assign(
|
||||||
baseLovelaceCardConfig,
|
baseLovelaceCardConfig,
|
||||||
@ -27,6 +41,7 @@ const cardConfigStruct = assign(
|
|||||||
show_entity_picture: optional(boolean()),
|
show_entity_picture: optional(boolean()),
|
||||||
tap_action: optional(actionConfigStruct),
|
tap_action: optional(actionConfigStruct),
|
||||||
icon_tap_action: optional(actionConfigStruct),
|
icon_tap_action: optional(actionConfigStruct),
|
||||||
|
extras: optional(array(any())),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -39,13 +54,15 @@ export class HuiTileCardEditor
|
|||||||
|
|
||||||
@state() private _config?: TileCardConfig;
|
@state() private _config?: TileCardConfig;
|
||||||
|
|
||||||
|
@state() private _subElementEditorConfig?: SubElementEditorConfig;
|
||||||
|
|
||||||
public setConfig(config: TileCardConfig): void {
|
public setConfig(config: TileCardConfig): void {
|
||||||
assert(config, cardConfigStruct);
|
assert(config, cardConfigStruct);
|
||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _schema = memoizeOne(
|
private _schema = memoizeOne(
|
||||||
(entity: string, icon?: string, entityState?: HassEntity) =>
|
(entity: string, icon?: string, stateObj?: HassEntity) =>
|
||||||
[
|
[
|
||||||
{ name: "entity", selector: { entity: {} } },
|
{ name: "entity", selector: { entity: {} } },
|
||||||
{
|
{
|
||||||
@ -65,10 +82,10 @@ export class HuiTileCardEditor
|
|||||||
name: "icon",
|
name: "icon",
|
||||||
selector: {
|
selector: {
|
||||||
icon: {
|
icon: {
|
||||||
placeholder: icon || entityState?.attributes.icon,
|
placeholder: icon || stateObj?.attributes.icon,
|
||||||
fallbackPath:
|
fallbackPath:
|
||||||
!icon && !entityState?.attributes.icon && entityState
|
!icon && !stateObj?.attributes.icon && stateObj
|
||||||
? domainIcon(computeDomain(entity), entityState)
|
? domainIcon(computeDomain(entity), stateObj)
|
||||||
: undefined,
|
: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -132,17 +149,33 @@ export class HuiTileCardEditor
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
const entity = this.hass.states[this._config.entity ?? ""] as
|
const stateObj = this.hass.states[this._config.entity ?? ""] as
|
||||||
| HassEntity
|
| HassEntity
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
const schema = this._schema(this._config.entity, this._config.icon, entity);
|
const schema = this._schema(
|
||||||
|
this._config.entity,
|
||||||
|
this._config.icon,
|
||||||
|
stateObj
|
||||||
|
);
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
color: "default",
|
color: "default",
|
||||||
...this._config,
|
...this._config,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (this._subElementEditorConfig) {
|
||||||
|
return html`
|
||||||
|
<hui-sub-element-editor
|
||||||
|
.hass=${this.hass}
|
||||||
|
.config=${this._subElementEditorConfig}
|
||||||
|
@go-back=${this._goBack}
|
||||||
|
@config-changed=${this.subElementChanged}
|
||||||
|
>
|
||||||
|
</hui-sub-element-editor>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-form
|
<ha-form
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@ -151,11 +184,24 @@ export class HuiTileCardEditor
|
|||||||
.computeLabel=${this._computeLabelCallback}
|
.computeLabel=${this._computeLabelCallback}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
></ha-form>
|
></ha-form>
|
||||||
|
<hui-tile-card-extras-editor
|
||||||
|
.hass=${this.hass}
|
||||||
|
.stateObj=${stateObj}
|
||||||
|
.extras=${this._config!.extras ?? []}
|
||||||
|
@extras-changed=${this._extrasChanged}
|
||||||
|
@edit-detail-element=${this._editDetailElement}
|
||||||
|
></hui-tile-card-extras-editor>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent): void {
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
const config = {
|
ev.stopPropagation();
|
||||||
|
if (!this._config || !this.hass) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config: TileCardConfig = {
|
||||||
|
extras: this._config.extras,
|
||||||
...ev.detail.value,
|
...ev.detail.value,
|
||||||
};
|
};
|
||||||
if (ev.detail.value.color === "default") {
|
if (ev.detail.value.color === "default") {
|
||||||
@ -164,6 +210,62 @@ export class HuiTileCardEditor
|
|||||||
fireEvent(this, "config-changed", { config });
|
fireEvent(this, "config-changed", { config });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _extrasChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (!this._config || !this.hass) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const extras = ev.detail.extras as LovelaceTileExtraConfig[];
|
||||||
|
const config: TileCardConfig = {
|
||||||
|
...this._config,
|
||||||
|
extras,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (extras.length === 0) {
|
||||||
|
delete config.extras;
|
||||||
|
}
|
||||||
|
|
||||||
|
fireEvent(this, "config-changed", { config });
|
||||||
|
}
|
||||||
|
|
||||||
|
private subElementChanged(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (!this._config || !this.hass) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = ev.detail.config;
|
||||||
|
|
||||||
|
const newConfigExtras = this._config!.extras
|
||||||
|
? [...this._config!.extras]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
newConfigExtras.splice(this._subElementEditorConfig!.index!, 1);
|
||||||
|
this._goBack();
|
||||||
|
} else {
|
||||||
|
newConfigExtras[this._subElementEditorConfig!.index!] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._config = { ...this._config!, extras: newConfigExtras };
|
||||||
|
|
||||||
|
this._subElementEditorConfig = {
|
||||||
|
...this._subElementEditorConfig!,
|
||||||
|
elementConfig: value,
|
||||||
|
};
|
||||||
|
|
||||||
|
fireEvent(this, "config-changed", { config: this._config });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _editDetailElement(ev: HASSDomEvent<EditSubElementEvent>): void {
|
||||||
|
this._subElementEditorConfig = ev.detail.subElementConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _goBack(): void {
|
||||||
|
this._subElementEditorConfig = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
private _computeLabelCallback = (
|
private _computeLabelCallback = (
|
||||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||||
) => {
|
) => {
|
||||||
@ -183,12 +285,19 @@ export class HuiTileCardEditor
|
|||||||
};
|
};
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
return css`
|
return [
|
||||||
.container {
|
configElementStyle,
|
||||||
display: flex;
|
css`
|
||||||
flex-direction: column;
|
.container {
|
||||||
}
|
display: flex;
|
||||||
`;
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
ha-form {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,345 @@
|
|||||||
|
import {
|
||||||
|
mdiDelete,
|
||||||
|
mdiDrag,
|
||||||
|
mdiListBox,
|
||||||
|
mdiPencil,
|
||||||
|
mdiPlus,
|
||||||
|
mdiWindowShutter,
|
||||||
|
} from "@mdi/js";
|
||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { repeat } from "lit/directives/repeat";
|
||||||
|
import type { SortableEvent } from "sortablejs";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||||
|
import "../../../../components/entity/ha-entity-picker";
|
||||||
|
import "../../../../components/ha-icon-button";
|
||||||
|
import "../../../../components/ha-svg-icon";
|
||||||
|
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||||
|
import {
|
||||||
|
loadSortable,
|
||||||
|
SortableInstance,
|
||||||
|
} from "../../../../resources/sortable.ondemand";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
import { getTileExtraElementClass } from "../../create-element/create-tile-extra-element";
|
||||||
|
import {
|
||||||
|
isTileExtraEditable,
|
||||||
|
supportsTileExtra,
|
||||||
|
} from "../../tile-extra/tile-extras";
|
||||||
|
import { LovelaceTileExtraConfig } from "../../tile-extra/types";
|
||||||
|
|
||||||
|
const EXTRAS_TYPE: LovelaceTileExtraConfig["type"][] = [
|
||||||
|
"cover-open-close",
|
||||||
|
"cover-tilt",
|
||||||
|
];
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"extras-changed": {
|
||||||
|
extras: LovelaceTileExtraConfig[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("hui-tile-card-extras-editor")
|
||||||
|
export class HuiTileCardExtrasEditor extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public extras?: LovelaceTileExtraConfig[];
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
private _extraKeys = new WeakMap<LovelaceTileExtraConfig, string>();
|
||||||
|
|
||||||
|
private _sortable?: SortableInstance;
|
||||||
|
|
||||||
|
public disconnectedCallback() {
|
||||||
|
this._destroySortable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getKey(extra: LovelaceTileExtraConfig) {
|
||||||
|
if (!this._extraKeys.has(extra)) {
|
||||||
|
this._extraKeys.set(extra, Math.random().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._extraKeys.get(extra)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _supportedExtraTypes() {
|
||||||
|
if (!this.stateObj) return [];
|
||||||
|
|
||||||
|
return EXTRAS_TYPE.filter((type) =>
|
||||||
|
supportsTileExtra(this.stateObj!, type)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this.extras || !this.hass) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-expansion-panel outlined>
|
||||||
|
<h3 slot="header">
|
||||||
|
<ha-svg-icon .path=${mdiListBox}></ha-svg-icon>
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.card.tile.extras.name"
|
||||||
|
)}
|
||||||
|
</h3>
|
||||||
|
<div class="content">
|
||||||
|
${this._supportedExtraTypes.length === 0 && this.extras.length === 0
|
||||||
|
? html`
|
||||||
|
<ha-alert type="info">
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.card.tile.extras.no_compatible_available"
|
||||||
|
)}
|
||||||
|
</ha-alert>
|
||||||
|
`
|
||||||
|
: null}
|
||||||
|
<div class="extras">
|
||||||
|
${repeat(
|
||||||
|
this.extras,
|
||||||
|
(extraConf) => this._getKey(extraConf),
|
||||||
|
(extraConf, index) => html`
|
||||||
|
<div class="extra">
|
||||||
|
<div class="handle">
|
||||||
|
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||||
|
</div>
|
||||||
|
<div class="extra-content">
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
${this.hass!.localize(
|
||||||
|
`ui.panel.lovelace.editor.card.tile.extras.types.${extraConf.type}.label`
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
${this.stateObj &&
|
||||||
|
!supportsTileExtra(this.stateObj, extraConf.type)
|
||||||
|
? html`<span class="secondary">
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.card.tile.extras.not_compatible"
|
||||||
|
)}
|
||||||
|
</span>`
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${isTileExtraEditable(extraConf.type)
|
||||||
|
? html`<ha-icon-button
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
`ui.panel.lovelace.editor.card.tile.extras.edit`
|
||||||
|
)}
|
||||||
|
.path=${mdiPencil}
|
||||||
|
class="edit-icon"
|
||||||
|
.index=${index}
|
||||||
|
@click=${this._editExtra}
|
||||||
|
></ha-icon-button>`
|
||||||
|
: null}
|
||||||
|
<ha-icon-button
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
`ui.panel.lovelace.editor.card.tile.extras.remove`
|
||||||
|
)}
|
||||||
|
.path=${mdiDelete}
|
||||||
|
class="remove-icon"
|
||||||
|
.index=${index}
|
||||||
|
@click=${this._removeExtra}
|
||||||
|
></ha-icon-button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
${this._supportedExtraTypes.length > 0
|
||||||
|
? html`
|
||||||
|
<ha-button-menu
|
||||||
|
fixed
|
||||||
|
@action=${this._addExtra}
|
||||||
|
@closed=${stopPropagation}
|
||||||
|
>
|
||||||
|
<mwc-button
|
||||||
|
slot="trigger"
|
||||||
|
outlined
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
`ui.panel.lovelace.editor.card.tile.extras.add`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||||
|
</mwc-button>
|
||||||
|
${this._supportedExtraTypes.map(
|
||||||
|
(extraType) => html`<mwc-list-item .value=${extraType}>
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="graphic"
|
||||||
|
.path=${mdiWindowShutter}
|
||||||
|
></ha-svg-icon>
|
||||||
|
${this.hass!.localize(
|
||||||
|
`ui.panel.lovelace.editor.card.tile.extras.types.${extraType}.label`
|
||||||
|
)}
|
||||||
|
</mwc-list-item>`
|
||||||
|
)}
|
||||||
|
</ha-button-menu>
|
||||||
|
`
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
</ha-expansion-panel>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(): void {
|
||||||
|
this._createSortable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _createSortable() {
|
||||||
|
const Sortable = await loadSortable();
|
||||||
|
this._sortable = new Sortable(this.shadowRoot!.querySelector(".extras")!, {
|
||||||
|
animation: 150,
|
||||||
|
fallbackClass: "sortable-fallback",
|
||||||
|
handle: ".handle",
|
||||||
|
onChoose: (evt: SortableEvent) => {
|
||||||
|
(evt.item as any).placeholder =
|
||||||
|
document.createComment("sort-placeholder");
|
||||||
|
evt.item.after((evt.item as any).placeholder);
|
||||||
|
},
|
||||||
|
onEnd: (evt: SortableEvent) => {
|
||||||
|
// put back in original location
|
||||||
|
if ((evt.item as any).placeholder) {
|
||||||
|
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||||
|
delete (evt.item as any).placeholder;
|
||||||
|
}
|
||||||
|
this._rowMoved(evt);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _destroySortable() {
|
||||||
|
this._sortable?.destroy();
|
||||||
|
this._sortable = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _addExtra(ev: CustomEvent): Promise<void> {
|
||||||
|
const index = ev.detail.index as number;
|
||||||
|
|
||||||
|
if (index == null) return;
|
||||||
|
|
||||||
|
const value = this._supportedExtraTypes[index];
|
||||||
|
const elClass = await getTileExtraElementClass(value);
|
||||||
|
|
||||||
|
let newExtra: LovelaceTileExtraConfig;
|
||||||
|
if (elClass && elClass.getStubConfig) {
|
||||||
|
newExtra = await elClass.getStubConfig(this.hass!);
|
||||||
|
} else {
|
||||||
|
newExtra = { type: value } as LovelaceTileExtraConfig;
|
||||||
|
}
|
||||||
|
const newConfigExtra = this.extras!.concat(newExtra);
|
||||||
|
fireEvent(this, "extras-changed", { extras: newConfigExtra });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _rowMoved(ev: SortableEvent): void {
|
||||||
|
if (ev.oldIndex === ev.newIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newExtras = this.extras!.concat();
|
||||||
|
|
||||||
|
newExtras.splice(ev.newIndex!, 0, newExtras.splice(ev.oldIndex!, 1)[0]);
|
||||||
|
|
||||||
|
fireEvent(this, "extras-changed", { extras: newExtras });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _removeExtra(ev: CustomEvent): void {
|
||||||
|
const index = (ev.currentTarget as any).index;
|
||||||
|
const newExtras = this.extras!.concat();
|
||||||
|
|
||||||
|
newExtras.splice(index, 1);
|
||||||
|
|
||||||
|
fireEvent(this, "extras-changed", { extras: newExtras });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _editExtra(ev: CustomEvent): void {
|
||||||
|
const index = (ev.currentTarget as any).index;
|
||||||
|
fireEvent(this, "edit-detail-element", {
|
||||||
|
subElementConfig: {
|
||||||
|
index,
|
||||||
|
type: "tile-extra",
|
||||||
|
elementConfig: this.extras![index],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
sortableStyles,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
ha-expansion-panel {
|
||||||
|
display: block;
|
||||||
|
--expansion-panel-content-padding: 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
ha-svg-icon,
|
||||||
|
ha-icon {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
ha-button-menu {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.extra {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.extra .handle {
|
||||||
|
padding-right: 8px;
|
||||||
|
cursor: move;
|
||||||
|
padding-inline-end: 8px;
|
||||||
|
padding-inline-start: initial;
|
||||||
|
direction: var(--direction);
|
||||||
|
}
|
||||||
|
.extra .handle > * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra-content {
|
||||||
|
height: 60px;
|
||||||
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra-content div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-icon,
|
||||||
|
.edit-icon {
|
||||||
|
--mdc-icon-button-size: 36px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hui-tile-card-extras-editor": HuiTileCardExtrasEditor;
|
||||||
|
}
|
||||||
|
}
|
@ -27,9 +27,14 @@ import type { LovelaceGenericElementEditor } from "../types";
|
|||||||
import "./config-elements/hui-generic-entity-row-editor";
|
import "./config-elements/hui-generic-entity-row-editor";
|
||||||
import { GUISupportError } from "./gui-support-error";
|
import { GUISupportError } from "./gui-support-error";
|
||||||
import { EditSubElementEvent, GUIModeChangedEvent } from "./types";
|
import { EditSubElementEvent, GUIModeChangedEvent } from "./types";
|
||||||
|
import { LovelaceTileExtraConfig } from "../tile-extra/types";
|
||||||
|
|
||||||
export interface ConfigChangedEvent {
|
export interface ConfigChangedEvent {
|
||||||
config: LovelaceCardConfig | LovelaceRowConfig | LovelaceHeaderFooterConfig;
|
config:
|
||||||
|
| LovelaceCardConfig
|
||||||
|
| LovelaceRowConfig
|
||||||
|
| LovelaceHeaderFooterConfig
|
||||||
|
| LovelaceTileExtraConfig;
|
||||||
error?: string;
|
error?: string;
|
||||||
guiModeAvailable?: boolean;
|
guiModeAvailable?: boolean;
|
||||||
}
|
}
|
||||||
@ -44,7 +49,11 @@ declare global {
|
|||||||
|
|
||||||
export interface UIConfigChangedEvent extends Event {
|
export interface UIConfigChangedEvent extends Event {
|
||||||
detail: {
|
detail: {
|
||||||
config: LovelaceCardConfig | LovelaceRowConfig | LovelaceHeaderFooterConfig;
|
config:
|
||||||
|
| LovelaceCardConfig
|
||||||
|
| LovelaceRowConfig
|
||||||
|
| LovelaceHeaderFooterConfig
|
||||||
|
| LovelaceTileExtraConfig;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import { mdiArrowLeft } from "@mdi/js";
|
import { mdiArrowLeft } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state, query } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
|
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
@ -10,6 +10,7 @@ import type { LovelaceHeaderFooterConfig } from "../header-footer/types";
|
|||||||
import "./entity-row-editor/hui-row-element-editor";
|
import "./entity-row-editor/hui-row-element-editor";
|
||||||
import "./header-footer-editor/hui-header-footer-element-editor";
|
import "./header-footer-editor/hui-header-footer-element-editor";
|
||||||
import type { HuiElementEditor } from "./hui-element-editor";
|
import type { HuiElementEditor } from "./hui-element-editor";
|
||||||
|
import "./tile-extra/hui-tile-extra-element-editor";
|
||||||
import type { GUIModeChangedEvent, SubElementEditorConfig } from "./types";
|
import type { GUIModeChangedEvent, SubElementEditorConfig } from "./types";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@ -79,6 +80,16 @@ export class HuiSubElementEditor extends LitElement {
|
|||||||
@GUImode-changed=${this._handleGUIModeChanged}
|
@GUImode-changed=${this._handleGUIModeChanged}
|
||||||
></hui-headerfooter-element-editor>
|
></hui-headerfooter-element-editor>
|
||||||
`
|
`
|
||||||
|
: this.config.type === "tile-extra"
|
||||||
|
? html`
|
||||||
|
<hui-tile-extra-element-editor
|
||||||
|
class="editor"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this.config.elementConfig}
|
||||||
|
@config-changed=${this._handleConfigChanged}
|
||||||
|
@GUImode-changed=${this._handleGUIModeChanged}
|
||||||
|
></hui-tile-extra-element-editor>
|
||||||
|
`
|
||||||
: ""}
|
: ""}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
import { customElement } from "lit/decorators";
|
||||||
|
import { getTileExtraElementClass } from "../../create-element/create-tile-extra-element";
|
||||||
|
import { LovelaceTileExtraConfig } from "../../tile-extra/types";
|
||||||
|
import type { LovelaceTileExtraEditor } from "../../types";
|
||||||
|
import { HuiElementEditor } from "../hui-element-editor";
|
||||||
|
|
||||||
|
@customElement("hui-tile-extra-element-editor")
|
||||||
|
export class HuiTileExtraElementEditor extends HuiElementEditor<LovelaceTileExtraConfig> {
|
||||||
|
protected async getConfigElement(): Promise<
|
||||||
|
LovelaceTileExtraEditor | undefined
|
||||||
|
> {
|
||||||
|
const elClass = await getTileExtraElementClass(this.configElementType!);
|
||||||
|
|
||||||
|
// Check if a GUI editor exists
|
||||||
|
if (elClass && elClass.getConfigElement) {
|
||||||
|
return elClass.getConfigElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hui-tile-extra-element-editor": HuiTileExtraElementEditor;
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ import {
|
|||||||
} from "../../../data/lovelace";
|
} from "../../../data/lovelace";
|
||||||
import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types";
|
import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types";
|
||||||
import { LovelaceHeaderFooterConfig } from "../header-footer/types";
|
import { LovelaceHeaderFooterConfig } from "../header-footer/types";
|
||||||
|
import { LovelaceTileExtraConfig } from "../tile-extra/types";
|
||||||
|
|
||||||
export interface YamlChangedEvent extends Event {
|
export interface YamlChangedEvent extends Event {
|
||||||
detail: {
|
detail: {
|
||||||
@ -74,8 +75,11 @@ export interface CardPickTarget extends EventTarget {
|
|||||||
|
|
||||||
export interface SubElementEditorConfig {
|
export interface SubElementEditorConfig {
|
||||||
index?: number;
|
index?: number;
|
||||||
elementConfig?: LovelaceRowConfig | LovelaceHeaderFooterConfig;
|
elementConfig?:
|
||||||
type: "header" | "footer" | "row";
|
| LovelaceRowConfig
|
||||||
|
| LovelaceHeaderFooterConfig
|
||||||
|
| LovelaceTileExtraConfig;
|
||||||
|
type: "header" | "footer" | "row" | "tile-extra";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EditSubElementEvent {
|
export interface EditSubElementEvent {
|
||||||
|
@ -0,0 +1,143 @@
|
|||||||
|
import { mdiStop } from "@mdi/js";
|
||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import {
|
||||||
|
computeCloseIcon,
|
||||||
|
computeOpenIcon,
|
||||||
|
} from "../../../common/entity/cover_icon";
|
||||||
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
|
import "../../../components/tile/ha-tile-button";
|
||||||
|
import {
|
||||||
|
canClose,
|
||||||
|
canOpen,
|
||||||
|
canStop,
|
||||||
|
CoverEntityFeature,
|
||||||
|
} from "../../../data/cover";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { LovelaceTileExtra } from "../types";
|
||||||
|
import { CoverOpenCloseTileExtraConfig } from "./types";
|
||||||
|
|
||||||
|
@customElement("hui-cover-open-close-tile-extra")
|
||||||
|
class HuiCoverOpenCloseTileExtra
|
||||||
|
extends LitElement
|
||||||
|
implements LovelaceTileExtra
|
||||||
|
{
|
||||||
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||||
|
|
||||||
|
@state() private _config?: CoverOpenCloseTileExtraConfig;
|
||||||
|
|
||||||
|
static getStubConfig(): CoverOpenCloseTileExtraConfig {
|
||||||
|
return {
|
||||||
|
type: "cover-open-close",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public setConfig(config: CoverOpenCloseTileExtraConfig): void {
|
||||||
|
if (!config) {
|
||||||
|
throw new Error("Invalid configuration");
|
||||||
|
}
|
||||||
|
this._config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 _onStopTap(ev): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.hass!.callService("cover", "stop_cover", {
|
||||||
|
entity_id: this.stateObj!.entity_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this._config || !this.hass || !this.stateObj) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="container">
|
||||||
|
${supportsFeature(this.stateObj, CoverEntityFeature.OPEN)
|
||||||
|
? html`
|
||||||
|
<ha-tile-button
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.cover.open_cover"
|
||||||
|
)}
|
||||||
|
@click=${this._onOpenTap}
|
||||||
|
.disabled=${!canOpen(this.stateObj)}
|
||||||
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
.path=${computeOpenIcon(this.stateObj)}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</ha-tile-button>
|
||||||
|
`
|
||||||
|
: null}
|
||||||
|
${supportsFeature(this.stateObj, CoverEntityFeature.STOP)
|
||||||
|
? html`<ha-tile-button
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.cover.stop_cover"
|
||||||
|
)}
|
||||||
|
@click=${this._onStopTap}
|
||||||
|
.disabled=${!canStop(this.stateObj)}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiStop}></ha-svg-icon>
|
||||||
|
</ha-tile-button> `
|
||||||
|
: null}
|
||||||
|
${supportsFeature(this.stateObj, CoverEntityFeature.CLOSE)
|
||||||
|
? html`
|
||||||
|
<ha-tile-button
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.cover.close_cover"
|
||||||
|
)}
|
||||||
|
@click=${this._onCloseTap}
|
||||||
|
.disabled=${!canClose(this.stateObj)}
|
||||||
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
.path=${computeCloseIcon(this.stateObj)}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</ha-tile-button>
|
||||||
|
`
|
||||||
|
: undefined}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 0 12px 12px 12px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
ha-tile-button {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
ha-tile-button:not(:last-child) {
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-inline-end: 12px;
|
||||||
|
margin-inline-start: initial;
|
||||||
|
direction: var(--direction);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hui-cover-open-close-tile-extra": HuiCoverOpenCloseTileExtra;
|
||||||
|
}
|
||||||
|
}
|
132
src/panels/lovelace/tile-extra/hui-cover-tilt-tile-extra.ts
Normal file
132
src/panels/lovelace/tile-extra/hui-cover-tilt-tile-extra.ts
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import { mdiArrowBottomLeft, mdiArrowTopRight, mdiStop } from "@mdi/js";
|
||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
|
import "../../../components/tile/ha-tile-button";
|
||||||
|
import {
|
||||||
|
canCloseTilt,
|
||||||
|
canOpenTilt,
|
||||||
|
canStopTilt,
|
||||||
|
CoverEntityFeature,
|
||||||
|
} from "../../../data/cover";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { LovelaceTileExtra } from "../types";
|
||||||
|
import { CoverTiltTileExtraConfig } from "./types";
|
||||||
|
|
||||||
|
@customElement("hui-cover-tilt-tile-extra")
|
||||||
|
class HuiCoverTiltTileExtra extends LitElement implements LovelaceTileExtra {
|
||||||
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||||
|
|
||||||
|
@state() private _config?: CoverTiltTileExtraConfig;
|
||||||
|
|
||||||
|
static getStubConfig(): CoverTiltTileExtraConfig {
|
||||||
|
return {
|
||||||
|
type: "cover-tilt",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public setConfig(config: CoverTiltTileExtraConfig): void {
|
||||||
|
if (!config) {
|
||||||
|
throw new Error("Invalid configuration");
|
||||||
|
}
|
||||||
|
this._config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onOpenTap(ev): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.hass!.callService("cover", "open_cover_tilt", {
|
||||||
|
entity_id: this.stateObj!.entity_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onCloseTap(ev): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.hass!.callService("cover", "close_cover_tilt", {
|
||||||
|
entity_id: this.stateObj!.entity_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onStopTap(ev): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.hass!.callService("cover", "stop_cover_tilt", {
|
||||||
|
entity_id: this.stateObj!.entity_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this._config || !this.hass || !this.stateObj) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="container">
|
||||||
|
${supportsFeature(this.stateObj, CoverEntityFeature.OPEN_TILT)
|
||||||
|
? html`
|
||||||
|
<ha-tile-button
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.cover.open_tilt_cover"
|
||||||
|
)}
|
||||||
|
@click=${this._onOpenTap}
|
||||||
|
.disabled=${!canOpenTilt(this.stateObj)}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiArrowTopRight}></ha-svg-icon>
|
||||||
|
</ha-tile-button>
|
||||||
|
`
|
||||||
|
: null}
|
||||||
|
${supportsFeature(this.stateObj, CoverEntityFeature.STOP_TILT)
|
||||||
|
? html`<ha-tile-button
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.cover.stop_cover"
|
||||||
|
)}
|
||||||
|
@click=${this._onStopTap}
|
||||||
|
.disabled=${!canStopTilt(this.stateObj)}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiStop}></ha-svg-icon>
|
||||||
|
</ha-tile-button> `
|
||||||
|
: null}
|
||||||
|
${supportsFeature(this.stateObj, CoverEntityFeature.CLOSE_TILT)
|
||||||
|
? html`
|
||||||
|
<ha-tile-button
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.cover.close_tilt_cover"
|
||||||
|
)}
|
||||||
|
@click=${this._onCloseTap}
|
||||||
|
.disabled=${!canCloseTilt(this.stateObj)}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiArrowBottomLeft}></ha-svg-icon>
|
||||||
|
</ha-tile-button>
|
||||||
|
`
|
||||||
|
: undefined}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 0 12px 12px 12px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
ha-tile-button {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
ha-tile-button:not(:last-child) {
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-inline-end: 12px;
|
||||||
|
margin-inline-start: initial;
|
||||||
|
direction: var(--direction);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hui-cover-tilt-tile-extra": HuiCoverTiltTileExtra;
|
||||||
|
}
|
||||||
|
}
|
34
src/panels/lovelace/tile-extra/tile-extras.ts
Normal file
34
src/panels/lovelace/tile-extra/tile-extras.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
|
import { CoverEntityFeature } from "../../../data/cover";
|
||||||
|
import { LovelaceTileExtraConfig } from "./types";
|
||||||
|
|
||||||
|
type TileExtraType = LovelaceTileExtraConfig["type"];
|
||||||
|
export type SupportsTileExtra = (stateObj: HassEntity) => boolean;
|
||||||
|
|
||||||
|
const TILE_EXTRAS_SUPPORT: Record<TileExtraType, SupportsTileExtra> = {
|
||||||
|
"cover-open-close": (stateObj) =>
|
||||||
|
computeDomain(stateObj.entity_id) === "cover" &&
|
||||||
|
(supportsFeature(stateObj, CoverEntityFeature.OPEN) ||
|
||||||
|
supportsFeature(stateObj, CoverEntityFeature.CLOSE)),
|
||||||
|
"cover-tilt": (stateObj) =>
|
||||||
|
computeDomain(stateObj.entity_id) === "cover" &&
|
||||||
|
(supportsFeature(stateObj, CoverEntityFeature.OPEN_TILT) ||
|
||||||
|
supportsFeature(stateObj, CoverEntityFeature.CLOSE_TILT)),
|
||||||
|
};
|
||||||
|
|
||||||
|
const TILE_EXTRAS_EDITABLE: Set<TileExtraType> = new Set([]);
|
||||||
|
|
||||||
|
export const supportsTileExtra = (
|
||||||
|
stateObj: HassEntity,
|
||||||
|
extra: TileExtraType
|
||||||
|
): boolean => {
|
||||||
|
const supportFunction = TILE_EXTRAS_SUPPORT[extra] as
|
||||||
|
| SupportsTileExtra
|
||||||
|
| undefined;
|
||||||
|
return !supportFunction || supportFunction(stateObj);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isTileExtraEditable = (extra: TileExtraType): boolean =>
|
||||||
|
TILE_EXTRAS_EDITABLE.has(extra);
|
11
src/panels/lovelace/tile-extra/types.ts
Normal file
11
src/panels/lovelace/tile-extra/types.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export interface CoverOpenCloseTileExtraConfig {
|
||||||
|
type: "cover-open-close";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CoverTiltTileExtraConfig {
|
||||||
|
type: "cover-tilt";
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LovelaceTileExtraConfig =
|
||||||
|
| CoverOpenCloseTileExtraConfig
|
||||||
|
| CoverTiltTileExtraConfig;
|
@ -1,3 +1,4 @@
|
|||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
LovelaceBadgeConfig,
|
LovelaceBadgeConfig,
|
||||||
LovelaceCardConfig,
|
LovelaceCardConfig,
|
||||||
@ -7,6 +8,7 @@ import { FrontendLocaleData } from "../../data/translation";
|
|||||||
import { Constructor, HomeAssistant } from "../../types";
|
import { Constructor, HomeAssistant } from "../../types";
|
||||||
import { LovelaceRow, LovelaceRowConfig } from "./entity-rows/types";
|
import { LovelaceRow, LovelaceRowConfig } from "./entity-rows/types";
|
||||||
import { LovelaceHeaderFooterConfig } from "./header-footer/types";
|
import { LovelaceHeaderFooterConfig } from "./header-footer/types";
|
||||||
|
import { LovelaceTileExtraConfig } from "./tile-extra/types";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
@ -92,3 +94,19 @@ export interface LovelaceGenericElementEditor extends HTMLElement {
|
|||||||
setConfig(config: any): void;
|
setConfig(config: any): void;
|
||||||
focusYamlEditor?: () => void;
|
focusYamlEditor?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LovelaceTileExtra extends HTMLElement {
|
||||||
|
hass?: HomeAssistant;
|
||||||
|
stateObj?: HassEntity;
|
||||||
|
setConfig(config: LovelaceTileExtraConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LovelaceTileExtraConstructor
|
||||||
|
extends Constructor<LovelaceTileExtra> {
|
||||||
|
getConfigElement?: () => LovelaceTileExtraEditor;
|
||||||
|
getStubConfig?: (hass: HomeAssistant) => LovelaceTileExtraConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LovelaceTileExtraEditor extends LovelaceGenericElementEditor {
|
||||||
|
setConfig(config: LovelaceTileExtraConfig): void;
|
||||||
|
}
|
||||||
|
@ -4210,7 +4210,23 @@
|
|||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
"appearance": "Appearance",
|
"appearance": "Appearance",
|
||||||
"default_color": "Default color (state)",
|
"default_color": "Default color (state)",
|
||||||
"show_entity_picture": "Show entity picture"
|
"show_entity_picture": "Show entity picture",
|
||||||
|
"extras": {
|
||||||
|
"name": "Extras",
|
||||||
|
"not_compatible": "Not compatible",
|
||||||
|
"no_compatible_available": "No compatible extras available for this entity",
|
||||||
|
"add": "Add extra",
|
||||||
|
"edit": "Edit extra",
|
||||||
|
"remove": "Remove extra",
|
||||||
|
"types": {
|
||||||
|
"cover-open-close": {
|
||||||
|
"label": "Cover open/close"
|
||||||
|
},
|
||||||
|
"cover-tilt": {
|
||||||
|
"label": "Cover tilt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"vertical-stack": {
|
"vertical-stack": {
|
||||||
"name": "Vertical Stack",
|
"name": "Vertical Stack",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user