mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 17:26:42 +00:00
Media Browser Bar (#11369)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
7ad0b37a9e
commit
303e065433
@ -413,32 +413,34 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
let parentProm: Promise<MediaPlayerItem> | undefined;
|
||||
|
||||
// See if we can take loading shortcuts if navigating to parent or child
|
||||
if (
|
||||
// Check if we navigated to a child
|
||||
oldNavigateIds &&
|
||||
this.navigateIds.length > oldNavigateIds.length &&
|
||||
oldNavigateIds.every((oldVal, idx) => {
|
||||
const curVal = this.navigateIds[idx];
|
||||
return (
|
||||
curVal.media_content_id === oldVal.media_content_id &&
|
||||
curVal.media_content_type === oldVal.media_content_type
|
||||
);
|
||||
})
|
||||
) {
|
||||
parentProm = Promise.resolve(oldCurrentItem!);
|
||||
} else if (
|
||||
// Check if we navigated to a parent
|
||||
oldNavigateIds &&
|
||||
this.navigateIds.length < oldNavigateIds.length &&
|
||||
this.navigateIds.every((curVal, idx) => {
|
||||
const oldVal = oldNavigateIds[idx];
|
||||
return (
|
||||
curVal.media_content_id === oldVal.media_content_id &&
|
||||
curVal.media_content_type === oldVal.media_content_type
|
||||
);
|
||||
})
|
||||
) {
|
||||
currentProm = Promise.resolve(oldParentItem!);
|
||||
if (!changedProps.has("entityId")) {
|
||||
if (
|
||||
// Check if we navigated to a child
|
||||
oldNavigateIds &&
|
||||
this.navigateIds.length > oldNavigateIds.length &&
|
||||
oldNavigateIds.every((oldVal, idx) => {
|
||||
const curVal = this.navigateIds[idx];
|
||||
return (
|
||||
curVal.media_content_id === oldVal.media_content_id &&
|
||||
curVal.media_content_type === oldVal.media_content_type
|
||||
);
|
||||
})
|
||||
) {
|
||||
parentProm = Promise.resolve(oldCurrentItem!);
|
||||
} else if (
|
||||
// Check if we navigated to a parent
|
||||
oldNavigateIds &&
|
||||
this.navigateIds.length < oldNavigateIds.length &&
|
||||
this.navigateIds.every((curVal, idx) => {
|
||||
const oldVal = oldNavigateIds[idx];
|
||||
return (
|
||||
curVal.media_content_id === oldVal.media_content_id &&
|
||||
curVal.media_content_type === oldVal.media_content_type
|
||||
);
|
||||
})
|
||||
) {
|
||||
currentProm = Promise.resolve(oldParentItem!);
|
||||
}
|
||||
}
|
||||
// Fetch current
|
||||
if (!currentProm) {
|
||||
@ -710,7 +712,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
padding: 20px 24px 10px;
|
||||
padding: 20px 24px 10px 32px;
|
||||
}
|
||||
|
||||
.header_button {
|
||||
@ -809,8 +811,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
minmax(var(--media-browse-item-size, 175px), 0.1fr)
|
||||
);
|
||||
grid-gap: 16px;
|
||||
padding: 0px 24px;
|
||||
margin: 8px 0px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
:host([dialog]) .children {
|
||||
|
@ -320,3 +320,16 @@ export const computeMediaControls = (
|
||||
|
||||
return buttons.length > 0 ? buttons : undefined;
|
||||
};
|
||||
|
||||
export const formatMediaTime = (seconds: number): string => {
|
||||
if (!seconds) {
|
||||
return "";
|
||||
}
|
||||
|
||||
let secondsString = new Date(seconds * 1000).toISOString();
|
||||
secondsString =
|
||||
seconds > 3600
|
||||
? secondsString.substring(11, 16)
|
||||
: secondsString.substring(14, 19);
|
||||
return secondsString.replace(/^0+/, "").padStart(4, "0");
|
||||
};
|
||||
|
496
src/panels/media-browser/ha-bar-media-player.ts
Normal file
496
src/panels/media-browser/ha-bar-media-player.ts
Normal file
@ -0,0 +1,496 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-linear-progress/mwc-linear-progress";
|
||||
import type { LinearProgress } from "@material/mwc-linear-progress/mwc-linear-progress";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import {
|
||||
mdiChevronDown,
|
||||
mdiMonitor,
|
||||
mdiPause,
|
||||
mdiPlay,
|
||||
mdiPlayPause,
|
||||
mdiStop,
|
||||
} from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { domainIcon } from "../../common/entity/domain_icon";
|
||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import "../../components/ha-button-menu";
|
||||
import "../../components/ha-icon-button";
|
||||
import { UNAVAILABLE_STATES } from "../../data/entity";
|
||||
import {
|
||||
BROWSER_PLAYER,
|
||||
computeMediaControls,
|
||||
computeMediaDescription,
|
||||
formatMediaTime,
|
||||
getCurrentProgress,
|
||||
MediaPlayerEntity,
|
||||
SUPPORT_BROWSE_MEDIA,
|
||||
SUPPORT_PAUSE,
|
||||
SUPPORT_PLAY,
|
||||
SUPPORT_STOP,
|
||||
} from "../../data/media-player";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../lovelace/components/hui-marquee";
|
||||
|
||||
@customElement("ha-bar-media-player")
|
||||
class BarMediaPlayer extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public entityId!: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public narrow!: boolean;
|
||||
|
||||
@query("mwc-linear-progress") private _progressBar?: LinearProgress;
|
||||
|
||||
@query("#CurrentProgress") private _currentProgress?: HTMLElement;
|
||||
|
||||
@state() private _marqueeActive = false;
|
||||
|
||||
private _progressInterval?: number;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
const stateObj = this._stateObj;
|
||||
|
||||
if (!stateObj) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!this._progressInterval &&
|
||||
this._showProgressBar &&
|
||||
stateObj.state === "playing"
|
||||
) {
|
||||
this._progressInterval = window.setInterval(
|
||||
() => this._updateProgressBar(),
|
||||
1000
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
if (this._progressInterval) {
|
||||
clearInterval(this._progressInterval);
|
||||
this._progressInterval = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const choosePlayerElement = html`
|
||||
<div
|
||||
class="choose-player ${this.entityId === BROWSER_PLAYER
|
||||
? "browser"
|
||||
: ""}"
|
||||
>
|
||||
<ha-button-menu corner="BOTTOM_START">
|
||||
${this.narrow
|
||||
? html`
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.path=${this._stateObj
|
||||
? domainIcon(computeDomain(this.entityId), this._stateObj)
|
||||
: mdiMonitor}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: html`
|
||||
<mwc-button
|
||||
slot="trigger"
|
||||
.label=${this.narrow
|
||||
? ""
|
||||
: `${
|
||||
this._stateObj
|
||||
? computeStateName(this._stateObj)
|
||||
: BROWSER_PLAYER
|
||||
}
|
||||
`}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${this._stateObj
|
||||
? domainIcon(computeDomain(this.entityId), this._stateObj)
|
||||
: mdiMonitor}
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon
|
||||
slot="trailingIcon"
|
||||
.path=${mdiChevronDown}
|
||||
></ha-svg-icon>
|
||||
</mwc-button>
|
||||
`}
|
||||
<mwc-list-item .player=${BROWSER_PLAYER} @click=${this._selectPlayer}
|
||||
>${this.hass.localize(
|
||||
"ui.components.media-browser.web-browser"
|
||||
)}</mwc-list-item
|
||||
>
|
||||
${this._mediaPlayerEntities.map(
|
||||
(source) => html`
|
||||
<mwc-list-item
|
||||
?selected=${source.entity_id === this.entityId}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(source.state)}
|
||||
.player=${source.entity_id}
|
||||
@click=${this._selectPlayer}
|
||||
>${computeStateName(source)}</mwc-list-item
|
||||
>
|
||||
`
|
||||
)}
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (!this._stateObj) {
|
||||
return choosePlayerElement;
|
||||
}
|
||||
|
||||
const stateObj = this._stateObj;
|
||||
const controls = !this.narrow
|
||||
? computeMediaControls(stateObj)
|
||||
: (stateObj.state === "playing" &&
|
||||
(supportsFeature(stateObj, SUPPORT_PAUSE) ||
|
||||
supportsFeature(stateObj, SUPPORT_STOP))) ||
|
||||
((stateObj.state === "paused" || stateObj.state === "idle") &&
|
||||
supportsFeature(stateObj, SUPPORT_PLAY)) ||
|
||||
(stateObj.state === "on" &&
|
||||
(supportsFeature(stateObj, SUPPORT_PLAY) ||
|
||||
supportsFeature(stateObj, SUPPORT_PAUSE)))
|
||||
? [
|
||||
{
|
||||
icon:
|
||||
stateObj.state === "on"
|
||||
? mdiPlayPause
|
||||
: stateObj.state !== "playing"
|
||||
? mdiPlay
|
||||
: supportsFeature(stateObj, SUPPORT_PAUSE)
|
||||
? mdiPause
|
||||
: mdiStop,
|
||||
action:
|
||||
stateObj.state !== "playing"
|
||||
? "media_play"
|
||||
: supportsFeature(stateObj, SUPPORT_PAUSE)
|
||||
? "media_pause"
|
||||
: "media_stop",
|
||||
},
|
||||
]
|
||||
: [{}];
|
||||
const mediaDescription = computeMediaDescription(stateObj);
|
||||
const mediaDuration = formatMediaTime(stateObj!.attributes.media_duration!);
|
||||
|
||||
return html`
|
||||
<div class="info">
|
||||
${this._image
|
||||
? html`<img src=${this.hass.hassUrl(this._image)} />`
|
||||
: stateObj.state === "off" || stateObj.state !== "playing"
|
||||
? html`<div class="blank-image"></div>`
|
||||
: ""}
|
||||
<div class="media-info">
|
||||
<hui-marquee
|
||||
.text=${stateObj.attributes.media_title ||
|
||||
mediaDescription ||
|
||||
this.hass.localize(`ui.card.media_player.nothing_playing`)}
|
||||
.active=${this._marqueeActive}
|
||||
@mouseover=${this._marqueeMouseOver}
|
||||
@mouseleave=${this._marqueeMouseLeave}
|
||||
></hui-marquee>
|
||||
<span class="secondary">
|
||||
${stateObj.attributes.media_title ? mediaDescription : ""}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls-progress">
|
||||
<div class="controls">
|
||||
${controls!.map(
|
||||
(control) => html`
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize(
|
||||
`ui.card.media_player.${control.action}`
|
||||
)}
|
||||
.path=${control.icon}
|
||||
action=${control.action}
|
||||
@click=${this._handleClick}
|
||||
>
|
||||
</ha-icon-button>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
${this.narrow
|
||||
? html`<mwc-linear-progress></mwc-linear-progress>`
|
||||
: html`
|
||||
<div class="progress">
|
||||
<div id="CurrentProgress"></div>
|
||||
<mwc-linear-progress wide></mwc-linear-progress>
|
||||
<div>${mediaDuration}</div>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
${choosePlayerElement}
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (!this.hass || !this._stateObj || !changedProps.has("hass")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stateObj = this._stateObj;
|
||||
|
||||
this._updateProgressBar();
|
||||
|
||||
if (
|
||||
!this._progressInterval &&
|
||||
this._showProgressBar &&
|
||||
stateObj.state === "playing"
|
||||
) {
|
||||
this._progressInterval = window.setInterval(
|
||||
() => this._updateProgressBar(),
|
||||
1000
|
||||
);
|
||||
} else if (
|
||||
this._progressInterval &&
|
||||
(!this._showProgressBar || stateObj.state !== "playing")
|
||||
) {
|
||||
clearInterval(this._progressInterval);
|
||||
this._progressInterval = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private get _stateObj(): MediaPlayerEntity | undefined {
|
||||
return this.hass!.states[this.entityId] as MediaPlayerEntity;
|
||||
}
|
||||
|
||||
private get _showProgressBar() {
|
||||
if (!this.hass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const stateObj = this._stateObj;
|
||||
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
(stateObj.state === "playing" || stateObj.state === "paused") &&
|
||||
"media_duration" in stateObj.attributes &&
|
||||
"media_position" in stateObj.attributes
|
||||
);
|
||||
}
|
||||
|
||||
private get _image() {
|
||||
if (!this.hass) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const stateObj = this._stateObj;
|
||||
|
||||
if (!stateObj) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
stateObj.attributes.entity_picture_local ||
|
||||
stateObj.attributes.entity_picture
|
||||
);
|
||||
}
|
||||
|
||||
private get _mediaPlayerEntities() {
|
||||
return Object.values(this.hass!.states).filter((entity) => {
|
||||
if (
|
||||
computeStateDomain(entity) === "media_player" &&
|
||||
supportsFeature(entity, SUPPORT_BROWSE_MEDIA)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private _updateProgressBar(): void {
|
||||
if (this._progressBar && this._stateObj?.attributes.media_duration) {
|
||||
const currentProgress = getCurrentProgress(this._stateObj);
|
||||
this._progressBar.progress =
|
||||
currentProgress / this._stateObj!.attributes.media_duration;
|
||||
|
||||
if (this._currentProgress) {
|
||||
this._currentProgress.innerHTML = formatMediaTime(currentProgress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _handleClick(e: MouseEvent): void {
|
||||
const action = (e.currentTarget! as HTMLElement).getAttribute("action")!;
|
||||
this.hass!.callService("media_player", action, {
|
||||
entity_id: this.entityId,
|
||||
});
|
||||
}
|
||||
|
||||
private _marqueeMouseOver(): void {
|
||||
if (!this._marqueeActive) {
|
||||
this._marqueeActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
private _marqueeMouseLeave(): void {
|
||||
if (this._marqueeActive) {
|
||||
this._marqueeActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _selectPlayer(ev: CustomEvent): void {
|
||||
const entityId = (ev.currentTarget as any).player;
|
||||
navigate(`/media-browser/${entityId}`, { replace: true });
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
display: flex;
|
||||
min-height: 100px;
|
||||
background: var(
|
||||
--ha-card-background,
|
||||
var(--card-background-color, white)
|
||||
);
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
mwc-linear-progress {
|
||||
width: 100%;
|
||||
padding: 0 4px;
|
||||
--mdc-theme-primary: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
mwc-button[slot="trigger"] {
|
||||
--mdc-theme-primary: var(--primary-text-color);
|
||||
--mdc-icon-size: 36px;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-right: 16px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.secondary,
|
||||
.progress {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.choose-player {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.controls-progress {
|
||||
flex: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.progress {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
mwc-linear-progress[wide] {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.media-info {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
padding-left: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
hui-marquee {
|
||||
font-size: 1.2em;
|
||||
margin: 0px 0 4px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-height: 100px;
|
||||
}
|
||||
|
||||
.blank-image {
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
background-color: var(--divider-color);
|
||||
}
|
||||
|
||||
ha-button-menu mwc-button {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
:host([narrow]) {
|
||||
min-height: 80px;
|
||||
max-height: 80px;
|
||||
}
|
||||
|
||||
:host([narrow]) .controls-progress {
|
||||
flex: unset;
|
||||
min-width: 48px;
|
||||
}
|
||||
|
||||
:host([narrow]) .controls {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
:host([narrow]) .choose-player {
|
||||
padding-left: 0;
|
||||
min-width: 48px;
|
||||
flex: unset;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:host([narrow]) .choose-player.browser {
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:host([narrow]) img {
|
||||
max-height: 80px;
|
||||
}
|
||||
|
||||
:host([narrow]) .blank-image {
|
||||
height: 80px;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
:host([narrow]) mwc-linear-progress {
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
left: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-bar-media-player": BarMediaPlayer;
|
||||
}
|
||||
}
|
@ -11,22 +11,16 @@ import {
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { LocalStorage } from "../../common/decorators/local-storage";
|
||||
import { HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import "../../components/ha-menu-button";
|
||||
import "../../components/media-player/ha-media-player-browse";
|
||||
import type { MediaPlayerItemId } from "../../components/media-player/ha-media-player-browse";
|
||||
import {
|
||||
BROWSER_PLAYER,
|
||||
MediaPickedEvent,
|
||||
SUPPORT_BROWSE_MEDIA,
|
||||
} from "../../data/media-player";
|
||||
import { BROWSER_PLAYER, MediaPickedEvent } from "../../data/media-player";
|
||||
import "../../layouts/ha-app-layout";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../types";
|
||||
import "./ha-bar-media-player";
|
||||
import { showWebBrowserPlayMediaDialog } from "./show-media-player-dialog";
|
||||
import { showSelectMediaPlayerDialog } from "./show-select-media-source-dialog";
|
||||
|
||||
@customElement("ha-panel-media-browser")
|
||||
class PanelMediaBrowser extends LitElement {
|
||||
@ -48,17 +42,6 @@ class PanelMediaBrowser extends LitElement {
|
||||
private _entityId = BROWSER_PLAYER;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const stateObj = this._entityId
|
||||
? this.hass.states[this._entityId]
|
||||
: undefined;
|
||||
|
||||
const title =
|
||||
this._entityId === BROWSER_PLAYER
|
||||
? `${this.hass.localize("ui.components.media-browser.web-browser")}`
|
||||
: stateObj?.attributes.friendly_name
|
||||
? `${stateObj?.attributes.friendly_name}`
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
<ha-app-layout>
|
||||
<app-header fixed slot="header">
|
||||
@ -73,23 +56,22 @@ class PanelMediaBrowser extends LitElement {
|
||||
"ui.components.media-browser.media-player-browser"
|
||||
)}
|
||||
</div>
|
||||
<div class="secondary-text">${title || ""}</div>
|
||||
</div>
|
||||
<mwc-button @click=${this._showSelectMediaPlayerDialog}>
|
||||
${this.hass.localize("ui.components.media-browser.choose_player")}
|
||||
</mwc-button>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
<div class="content">
|
||||
<ha-media-player-browse
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
.navigateIds=${this._navigateIds}
|
||||
@media-picked=${this._mediaPicked}
|
||||
@media-browsed=${this._mediaBrowsed}
|
||||
></ha-media-player-browse>
|
||||
</div>
|
||||
<ha-media-player-browse
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
.navigateIds=${this._navigateIds}
|
||||
@media-picked=${this._mediaPicked}
|
||||
@media-browsed=${this._mediaBrowsed}
|
||||
></ha-media-player-browse>
|
||||
</ha-app-layout>
|
||||
<ha-bar-media-player
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
.narrow=${this.narrow}
|
||||
></ha-bar-media-player>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -129,15 +111,6 @@ class PanelMediaBrowser extends LitElement {
|
||||
];
|
||||
}
|
||||
|
||||
private _showSelectMediaPlayerDialog(): void {
|
||||
showSelectMediaPlayerDialog(this, {
|
||||
mediaSources: this._mediaPlayerEntities,
|
||||
sourceSelectedCallback: (entityId) => {
|
||||
navigate(`/media-browser/${entityId}`, { replace: true });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _mediaBrowsed(ev) {
|
||||
if (ev.detail.back) {
|
||||
history.back();
|
||||
@ -179,19 +152,6 @@ class PanelMediaBrowser extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private get _mediaPlayerEntities() {
|
||||
return Object.values(this.hass!.states).filter((entity) => {
|
||||
if (
|
||||
computeStateDomain(entity) === "media_player" &&
|
||||
supportsFeature(entity, SUPPORT_BROWSE_MEDIA)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
@ -199,21 +159,20 @@ class PanelMediaBrowser extends LitElement {
|
||||
:host {
|
||||
--mdc-theme-primary: var(--app-header-text-color);
|
||||
}
|
||||
|
||||
ha-media-player-browse {
|
||||
height: calc(100vh - var(--header-height));
|
||||
height: calc(100vh - (100px + var(--header-height)));
|
||||
}
|
||||
:host([narrow]) app-toolbar mwc-button {
|
||||
width: 65px;
|
||||
|
||||
:host([narrow]) ha-media-player-browse {
|
||||
height: calc(100vh - (80px + var(--header-height)));
|
||||
}
|
||||
.heading {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.heading .secondary-text {
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
ha-bar-media-player {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@ -1,98 +0,0 @@
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import { createCloseHeading } from "../../components/ha-dialog";
|
||||
import { UNAVAILABLE_STATES } from "../../data/entity";
|
||||
import { BROWSER_PLAYER } from "../../data/media-player";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { SelectMediaPlayerDialogParams } from "./show-select-media-source-dialog";
|
||||
|
||||
@customElement("hui-dialog-select-media-player")
|
||||
export class HuiDialogSelectMediaPlayer extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false })
|
||||
private _params?: SelectMediaPlayerDialogParams;
|
||||
|
||||
public showDialog(params: SelectMediaPlayerDialogParams): void {
|
||||
this._params = params;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._params) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
hideActions
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize(`ui.components.media-browser.choose_player`)
|
||||
)}
|
||||
@closed=${this.closeDialog}
|
||||
>
|
||||
<mwc-list>
|
||||
<mwc-list-item .player=${BROWSER_PLAYER} @click=${this._selectPlayer}
|
||||
>${this.hass.localize(
|
||||
"ui.components.media-browser.web-browser"
|
||||
)}</mwc-list-item
|
||||
>
|
||||
${this._params.mediaSources
|
||||
.sort((a, b) =>
|
||||
stringCompare(computeStateName(a), computeStateName(b))
|
||||
)
|
||||
.map(
|
||||
(source) => html`
|
||||
<mwc-list-item
|
||||
.disabled=${UNAVAILABLE_STATES.includes(source.state)}
|
||||
.player=${source.entity_id}
|
||||
@click=${this._selectPlayer}
|
||||
>${computeStateName(source)}</mwc-list-item
|
||||
>
|
||||
`
|
||||
)}
|
||||
</mwc-list>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _selectPlayer(ev: CustomEvent): void {
|
||||
const entityId = (ev.currentTarget as any).player;
|
||||
this._params!.sourceSelectedCallback(entityId);
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog {
|
||||
--dialog-content-padding: 0 24px 20px;
|
||||
}
|
||||
mwc-list-item[disabled] {
|
||||
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-dialog-select-media-player": HuiDialogSelectMediaPlayer;
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
export interface SelectMediaPlayerDialogParams {
|
||||
mediaSources: HassEntity[];
|
||||
sourceSelectedCallback: (entityId: string) => void;
|
||||
}
|
||||
|
||||
export const showSelectMediaPlayerDialog = (
|
||||
element: HTMLElement,
|
||||
selectMediaPlayereDialogParams: SelectMediaPlayerDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "hui-dialog-select-media-player",
|
||||
dialogImport: () => import("./hui-dialog-select-media-player"),
|
||||
dialogParams: selectMediaPlayereDialogParams,
|
||||
});
|
||||
};
|
@ -203,7 +203,8 @@
|
||||
"media_volume_down": "Volume down",
|
||||
"media_volume_mute": "Volume mute",
|
||||
"media_volume_unmute": "Volume unmute",
|
||||
"text_to_speak": "Text to speak"
|
||||
"text_to_speak": "Text to speak",
|
||||
"nothing_playing": "Nothing Playing"
|
||||
},
|
||||
"persistent_notification": {
|
||||
"dismiss": "Dismiss"
|
||||
|
Loading…
x
Reference in New Issue
Block a user