mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 03:36:44 +00:00
Convert Media Control Card to Typescript (#4761)
* Delete old JS file. Add new TS file. Add Type * Updates * Reviews * Review Updates :) * Updating State Localize * Clean up * Fixing Travis
This commit is contained in:
parent
c7f7ef28bf
commit
2e4c73c087
@ -1,58 +0,0 @@
|
||||
import { createErrorCardConfig } from "./hui-error-card";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
|
||||
export default class LegacyWrapperCard extends HTMLElement {
|
||||
constructor(tag, domain) {
|
||||
super();
|
||||
this._tag = tag.toUpperCase();
|
||||
this._domain = domain;
|
||||
this._element = null;
|
||||
}
|
||||
|
||||
getCardSize() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
setConfig(config) {
|
||||
if (!config.entity) {
|
||||
throw new Error("No entity specified");
|
||||
}
|
||||
|
||||
if (computeDomain(config.entity) !== this._domain) {
|
||||
throw new Error(
|
||||
`Specified entity needs to be of domain ${this._domain}.`
|
||||
);
|
||||
}
|
||||
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
set hass(hass) {
|
||||
const entityId = this._config.entity;
|
||||
|
||||
if (entityId in hass.states) {
|
||||
this._ensureElement(this._tag);
|
||||
this.lastChild.hass = hass;
|
||||
this.lastChild.stateObj = hass.states[entityId];
|
||||
this.lastChild.config = this._config;
|
||||
} else {
|
||||
this._ensureElement("HUI-ERROR-CARD");
|
||||
this.lastChild.setConfig(
|
||||
createErrorCardConfig(
|
||||
`No state available for ${entityId}`,
|
||||
this._config
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_ensureElement(tag) {
|
||||
if (this.lastChild && this.lastChild.tagName === tag) return;
|
||||
|
||||
if (this.lastChild) {
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
|
||||
this.appendChild(document.createElement(tag));
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import "../../../cards/ha-media_player-card";
|
||||
|
||||
import LegacyWrapperCard from "./hui-legacy-wrapper-card";
|
||||
|
||||
class HuiMediaControlCard extends LegacyWrapperCard {
|
||||
static async getConfigElement() {
|
||||
await import(
|
||||
/* webpackChunkName: "hui-media-control-card-editor" */ "../editor/config-elements/hui-media-control-card-editor"
|
||||
);
|
||||
return document.createElement("hui-media-control-card-editor");
|
||||
}
|
||||
|
||||
static getStubConfig() {
|
||||
return { entity: "" };
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super("ha-media_player-card", "media_player");
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("hui-media-control-card", HuiMediaControlCard);
|
320
src/panels/lovelace/cards/hui-media-control-card.ts
Normal file
320
src/panels/lovelace/cards/hui-media-control-card.ts
Normal file
@ -0,0 +1,320 @@
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
property,
|
||||
css,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { OFF_STATES, SUPPORT_PAUSE } from "../../../data/media-player";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { HomeAssistant, MediaEntity } from "../../../types";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { MediaControlCardConfig } from "./types";
|
||||
|
||||
@customElement("hui-media-control-card")
|
||||
export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import(
|
||||
/* webpackChunkName: "hui-media-control-card-editor" */ "../editor/config-elements/hui-media-control-card-editor"
|
||||
);
|
||||
return document.createElement("hui-media-control-card-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(): object {
|
||||
return { entity: "" };
|
||||
}
|
||||
|
||||
@property() public hass?: HomeAssistant;
|
||||
@property() private _config?: MediaControlCardConfig;
|
||||
|
||||
public getCardSize(): number {
|
||||
return 3;
|
||||
}
|
||||
|
||||
public setConfig(config: MediaControlCardConfig): void {
|
||||
if (!config.entity || config.entity.split(".")[0] !== "media_player") {
|
||||
throw new Error("Specify an entity from within the media_player domain.");
|
||||
}
|
||||
|
||||
this._config = { theme: "default", ...config };
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this._config) {
|
||||
return html``;
|
||||
}
|
||||
const stateObj = this.hass.states[this._config.entity] as MediaEntity;
|
||||
|
||||
if (!stateObj) {
|
||||
return html`
|
||||
<hui-warning
|
||||
>${this.hass.localize(
|
||||
"ui.panel.lovelace.warning.entity_not_found",
|
||||
"entity",
|
||||
this._config.entity
|
||||
)}</hui-warning
|
||||
>
|
||||
`;
|
||||
}
|
||||
const image =
|
||||
stateObj.attributes.entity_picture ||
|
||||
"../static/images/card_media_player_bg.png";
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
<div
|
||||
class="${classMap({
|
||||
"no-image": !stateObj.attributes.entity_picture,
|
||||
ratio: true,
|
||||
})}"
|
||||
>
|
||||
<div class="image" style="background-image: url(${image})"></div>
|
||||
<div class="caption">
|
||||
${this._config!.name ||
|
||||
computeStateName(this.hass!.states[this._config!.entity])}
|
||||
<div class="title">
|
||||
${this._computeMediaTitle(stateObj)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${OFF_STATES.includes(stateObj.state)
|
||||
? ""
|
||||
: html`
|
||||
<paper-progress
|
||||
.max="${stateObj.attributes.media_duration}"
|
||||
.value="${stateObj.attributes.media_position}"
|
||||
class="progress"
|
||||
></paper-progress>
|
||||
`}
|
||||
<div class="controls flex">
|
||||
<div class="left flex">
|
||||
<paper-icon-button
|
||||
icon="hass:power"
|
||||
.action=${stateObj.state === "off" ? "turn_on" : "turn_off"}
|
||||
@click=${this._handleClick}
|
||||
></paper-icon-button>
|
||||
</div>
|
||||
<div class="center flex">
|
||||
<paper-icon-button
|
||||
icon="hass:skip-previous"
|
||||
.action=${"media_previous_track"}
|
||||
@click=${this._handleClick}
|
||||
></paper-icon-button>
|
||||
<paper-icon-button
|
||||
class="playPauseButton"
|
||||
icon=${stateObj.state !== "playing"
|
||||
? "hass:play"
|
||||
: supportsFeature(stateObj, SUPPORT_PAUSE)
|
||||
? "hass:pause"
|
||||
: "hass:stop"}
|
||||
.action=${"media_play_pause"}
|
||||
@click=${this._handleClick}
|
||||
></paper-icon-button>
|
||||
<paper-icon-button
|
||||
icon="hass:skip-next"
|
||||
.action=${"media_next_track"}
|
||||
@click=${this._handleClick}
|
||||
></paper-icon-button>
|
||||
</div>
|
||||
<div class="right flex">
|
||||
<paper-icon-button
|
||||
icon="hass:dots-vertical"
|
||||
@click="${this._handleMoreInfo}"
|
||||
></paper-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
return hasConfigOrEntityChanged(this, changedProps);
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
if (!this._config || !this.hass || !changedProps.has("hass")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
const oldConfig = changedProps.get("_config") as
|
||||
| MediaControlCardConfig
|
||||
| undefined;
|
||||
|
||||
if (
|
||||
!oldHass ||
|
||||
!oldConfig ||
|
||||
oldHass.themes !== this.hass.themes ||
|
||||
oldConfig.theme !== this._config.theme
|
||||
) {
|
||||
applyThemesOnElement(this, this.hass.themes, this._config.theme);
|
||||
}
|
||||
}
|
||||
|
||||
private _computeMediaTitle(stateObj: HassEntity): string {
|
||||
let prefix;
|
||||
let suffix;
|
||||
|
||||
switch (stateObj.attributes.media_content_type) {
|
||||
case "music":
|
||||
prefix = stateObj.attributes.media_artist;
|
||||
suffix = stateObj.attributes.media_title;
|
||||
break;
|
||||
case "tvshow":
|
||||
prefix = stateObj.attributes.media_series_title;
|
||||
suffix = stateObj.attributes.media_title;
|
||||
break;
|
||||
default:
|
||||
prefix =
|
||||
stateObj.attributes.media_title ||
|
||||
stateObj.attributes.app_name ||
|
||||
this.hass!.localize(`state.media_player.${stateObj.state}`) ||
|
||||
this.hass!.localize(`state.default.${stateObj.state}`) ||
|
||||
stateObj.state;
|
||||
suffix = "";
|
||||
}
|
||||
|
||||
return prefix && suffix ? `${prefix}: ${suffix}` : prefix || suffix || "";
|
||||
}
|
||||
|
||||
private _handleMoreInfo() {
|
||||
fireEvent(this, "hass-more-info", {
|
||||
entityId: this._config!.entity,
|
||||
});
|
||||
}
|
||||
|
||||
private _handleClick(e: MouseEvent) {
|
||||
this.hass!.callService("media_player", (e.currentTarget! as any).action, {
|
||||
entity_id: this._config!.entity,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.ratio {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
padding-bottom: 56.25%;
|
||||
}
|
||||
|
||||
.image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
transition: filter 0.2s linear;
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.no-image {
|
||||
padding-bottom: 20%;
|
||||
}
|
||||
|
||||
.no-image > .image {
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-color: var(--primary-color);
|
||||
background-size: initial;
|
||||
}
|
||||
|
||||
.no-image > .caption {
|
||||
background-color: initial;
|
||||
}
|
||||
|
||||
.controls {
|
||||
align-content: space-evenly;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.controls > div {
|
||||
width: 33%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
paper-icon-button {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
opacity: var(--dark-primary-opacity);
|
||||
}
|
||||
|
||||
paper-icon-button[disabled] {
|
||||
opacity: var(--dark-disabled-opacity);
|
||||
}
|
||||
|
||||
.playPauseButton {
|
||||
width: 56px !important;
|
||||
height: 56px !important;
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
padding: 8px;
|
||||
transition: background-color 0.5s;
|
||||
}
|
||||
|
||||
.caption {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, var(--dark-secondary-opacity));
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
transition: background-color 0.5s;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.2em;
|
||||
margin: 8px 0 4px;
|
||||
}
|
||||
|
||||
.progress {
|
||||
width: 100%;
|
||||
height: var(--paper-progress-height, 4px);
|
||||
margin-top: calc(-1 * var(--paper-progress-height, 4px));
|
||||
--paper-progress-active-color: var(--accent-color);
|
||||
--paper-progress-container-color: rgba(200, 200, 200, 0.5);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-media-control-card": HuiMediaControlCard;
|
||||
}
|
||||
}
|
@ -11,9 +11,9 @@ import { EntitiesEditorEvent, EditorTarget } from "../types";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { MediaControlCardConfig } from "../../cards/hui-media-control-card";
|
||||
|
||||
import "../../../../components/entity/ha-entity-picker";
|
||||
import { MediaControlCardConfig } from "../../cards/types";
|
||||
|
||||
const cardConfigStruct = struct({
|
||||
type: "string",
|
||||
|
@ -200,6 +200,13 @@ export type CameraEntity = HassEntityBase & {
|
||||
};
|
||||
};
|
||||
|
||||
export type MediaEntity = HassEntityBase & {
|
||||
attributes: HassEntityAttributeBase & {
|
||||
media_duration: number;
|
||||
media_position: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type InputSelectEntity = HassEntityBase & {
|
||||
attributes: HassEntityAttributeBase & {
|
||||
options: string[];
|
||||
|
Loading…
x
Reference in New Issue
Block a user