import { mdiPlayBox, mdiPlus } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../../common/dom/fire_event"; import { supportsFeature } from "../../common/entity/supports-feature"; import { getSignedPath } from "../../data/auth"; import { MediaClassBrowserSettings, MediaPickedEvent, MediaPlayerEntityFeature, } from "../../data/media-player"; import type { MediaSelector, MediaSelectorValue } from "../../data/selector"; import type { HomeAssistant } from "../../types"; import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url"; import "../ha-alert"; import "../ha-form/ha-form"; import type { SchemaUnion } from "../ha-form/types"; import { showMediaBrowserDialog } from "../media-player/show-media-browser-dialog"; const MANUAL_SCHEMA = [ { name: "media_content_id", required: false, selector: { text: {} } }, { name: "media_content_type", required: false, selector: { text: {} } }, ] as const; @customElement("ha-selector-media") export class HaMediaSelector extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public selector!: MediaSelector; @property({ attribute: false }) public value?: MediaSelectorValue; @property() public label?: string; @property() public helper?: string; @property({ type: Boolean, reflect: true }) public disabled = false; @property({ type: Boolean, reflect: true }) public required = true; @state() private _thumbnailUrl?: string | null; willUpdate(changedProps: PropertyValues) { if (changedProps.has("value")) { const thumbnail = this.value?.metadata?.thumbnail; const oldThumbnail = (changedProps.get("value") as this["value"]) ?.metadata?.thumbnail; if (thumbnail === oldThumbnail) { return; } if (thumbnail && thumbnail.startsWith("/")) { this._thumbnailUrl = undefined; // Thumbnails served by local API require authentication getSignedPath(this.hass, thumbnail).then((signedPath) => { this._thumbnailUrl = signedPath.path; }); } else if ( thumbnail && thumbnail.startsWith("https://brands.home-assistant.io") ) { // The backend is not aware of the theme used by the users, // so we rewrite the URL to show a proper icon this._thumbnailUrl = brandsUrl({ domain: extractDomainFromBrandUrl(thumbnail), type: "icon", useFallback: true, darkOptimized: this.hass.themes?.darkMode, }); } else { this._thumbnailUrl = thumbnail; } } } protected render() { const stateObj = this.value?.entity_id ? this.hass.states[this.value.entity_id] : undefined; const supportsBrowse = !this.value?.entity_id || (stateObj && supportsFeature(stateObj, MediaPlayerEntityFeature.BROWSE_MEDIA)); return html` ${!supportsBrowse ? html` ${this.hass.localize( "ui.components.selectors.media.browse_not_supported" )} ` : html`
${this.value?.metadata?.thumbnail ? html`
` : html`
`}
${!this.value?.media_content_id ? this.hass.localize("ui.components.selectors.media.pick_media") : this.value.metadata?.title || this.value.media_content_id}
`}`; } private _computeLabelCallback = ( schema: SchemaUnion ): string => this.hass.localize(`ui.components.selectors.media.${schema.name}`); private _entityChanged(ev: CustomEvent) { ev.stopPropagation(); fireEvent(this, "value-changed", { value: { entity_id: ev.detail.value, media_content_id: "", media_content_type: "", }, }); } private _pickMedia() { showMediaBrowserDialog(this, { action: "pick", entityId: this.value!.entity_id!, navigateIds: this.value!.metadata?.navigateIds, mediaPickedCallback: (pickedMedia: MediaPickedEvent) => { fireEvent(this, "value-changed", { value: { ...this.value, media_content_id: pickedMedia.item.media_content_id, media_content_type: pickedMedia.item.media_content_type, metadata: { title: pickedMedia.item.title, thumbnail: pickedMedia.item.thumbnail, media_class: pickedMedia.item.media_class, children_media_class: pickedMedia.item.children_media_class, navigateIds: pickedMedia.navigateIds?.map((id) => ({ media_content_type: id.media_content_type, media_content_id: id.media_content_id, })), }, }, }); }, }); } static get styles(): CSSResultGroup { return css` ha-entity-picker { display: block; margin-bottom: 16px; } mwc-button { margin-top: 8px; } ha-alert { display: block; margin-bottom: 16px; } ha-card { position: relative; width: 200px; box-sizing: border-box; cursor: pointer; } ha-card.disabled { pointer-events: none; color: var(--disabled-text-color); } ha-card .thumbnail { width: 100%; position: relative; box-sizing: border-box; transition: padding-bottom 0.1s ease-out; padding-bottom: 100%; } ha-card .thumbnail.portrait { padding-bottom: 150%; } ha-card .image { border-radius: 3px 3px 0 0; } .folder { --mdc-icon-size: calc(var(--media-browse-item-size, 175px) * 0.4); } .title { font-size: 16px; padding-top: 16px; overflow: hidden; text-overflow: ellipsis; margin-bottom: 16px; padding-left: 16px; padding-right: 4px; padding-inline-start: 16px; padding-inline-end: 4px; white-space: nowrap; } .image { position: absolute; top: 0; right: 0; left: 0; bottom: 0; background-size: cover; background-repeat: no-repeat; background-position: center; } .centered-image { margin: 0 8px; background-size: contain; } .icon-holder { display: flex; justify-content: center; align-items: center; } `; } } declare global { interface HTMLElementTagNameMap { "ha-selector-media": HaMediaSelector; } }