mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +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;
|
let parentProm: Promise<MediaPlayerItem> | undefined;
|
||||||
|
|
||||||
// See if we can take loading shortcuts if navigating to parent or child
|
// See if we can take loading shortcuts if navigating to parent or child
|
||||||
if (
|
if (!changedProps.has("entityId")) {
|
||||||
// Check if we navigated to a child
|
if (
|
||||||
oldNavigateIds &&
|
// Check if we navigated to a child
|
||||||
this.navigateIds.length > oldNavigateIds.length &&
|
oldNavigateIds &&
|
||||||
oldNavigateIds.every((oldVal, idx) => {
|
this.navigateIds.length > oldNavigateIds.length &&
|
||||||
const curVal = this.navigateIds[idx];
|
oldNavigateIds.every((oldVal, idx) => {
|
||||||
return (
|
const curVal = this.navigateIds[idx];
|
||||||
curVal.media_content_id === oldVal.media_content_id &&
|
return (
|
||||||
curVal.media_content_type === oldVal.media_content_type
|
curVal.media_content_id === oldVal.media_content_id &&
|
||||||
);
|
curVal.media_content_type === oldVal.media_content_type
|
||||||
})
|
);
|
||||||
) {
|
})
|
||||||
parentProm = Promise.resolve(oldCurrentItem!);
|
) {
|
||||||
} else if (
|
parentProm = Promise.resolve(oldCurrentItem!);
|
||||||
// Check if we navigated to a parent
|
} else if (
|
||||||
oldNavigateIds &&
|
// Check if we navigated to a parent
|
||||||
this.navigateIds.length < oldNavigateIds.length &&
|
oldNavigateIds &&
|
||||||
this.navigateIds.every((curVal, idx) => {
|
this.navigateIds.length < oldNavigateIds.length &&
|
||||||
const oldVal = oldNavigateIds[idx];
|
this.navigateIds.every((curVal, idx) => {
|
||||||
return (
|
const oldVal = oldNavigateIds[idx];
|
||||||
curVal.media_content_id === oldVal.media_content_id &&
|
return (
|
||||||
curVal.media_content_type === oldVal.media_content_type
|
curVal.media_content_id === oldVal.media_content_id &&
|
||||||
);
|
curVal.media_content_type === oldVal.media_content_type
|
||||||
})
|
);
|
||||||
) {
|
})
|
||||||
currentProm = Promise.resolve(oldParentItem!);
|
) {
|
||||||
|
currentProm = Promise.resolve(oldParentItem!);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Fetch current
|
// Fetch current
|
||||||
if (!currentProm) {
|
if (!currentProm) {
|
||||||
@ -710,7 +712,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
right: 0;
|
right: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
padding: 20px 24px 10px;
|
padding: 20px 24px 10px 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header_button {
|
.header_button {
|
||||||
@ -809,8 +811,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
minmax(var(--media-browse-item-size, 175px), 0.1fr)
|
minmax(var(--media-browse-item-size, 175px), 0.1fr)
|
||||||
);
|
);
|
||||||
grid-gap: 16px;
|
grid-gap: 16px;
|
||||||
padding: 0px 24px;
|
padding: 8px;
|
||||||
margin: 8px 0px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([dialog]) .children {
|
:host([dialog]) .children {
|
||||||
|
@ -320,3 +320,16 @@ export const computeMediaControls = (
|
|||||||
|
|
||||||
return buttons.length > 0 ? buttons : undefined;
|
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 { customElement, property } from "lit/decorators";
|
||||||
import { LocalStorage } from "../../common/decorators/local-storage";
|
import { LocalStorage } from "../../common/decorators/local-storage";
|
||||||
import { HASSDomEvent } from "../../common/dom/fire_event";
|
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 { navigate } from "../../common/navigate";
|
||||||
import "../../components/ha-menu-button";
|
import "../../components/ha-menu-button";
|
||||||
import "../../components/media-player/ha-media-player-browse";
|
import "../../components/media-player/ha-media-player-browse";
|
||||||
import type { MediaPlayerItemId } from "../../components/media-player/ha-media-player-browse";
|
import type { MediaPlayerItemId } from "../../components/media-player/ha-media-player-browse";
|
||||||
import {
|
import { BROWSER_PLAYER, MediaPickedEvent } from "../../data/media-player";
|
||||||
BROWSER_PLAYER,
|
|
||||||
MediaPickedEvent,
|
|
||||||
SUPPORT_BROWSE_MEDIA,
|
|
||||||
} from "../../data/media-player";
|
|
||||||
import "../../layouts/ha-app-layout";
|
import "../../layouts/ha-app-layout";
|
||||||
import { haStyle } from "../../resources/styles";
|
import { haStyle } from "../../resources/styles";
|
||||||
import type { HomeAssistant, Route } from "../../types";
|
import type { HomeAssistant, Route } from "../../types";
|
||||||
|
import "./ha-bar-media-player";
|
||||||
import { showWebBrowserPlayMediaDialog } from "./show-media-player-dialog";
|
import { showWebBrowserPlayMediaDialog } from "./show-media-player-dialog";
|
||||||
import { showSelectMediaPlayerDialog } from "./show-select-media-source-dialog";
|
|
||||||
|
|
||||||
@customElement("ha-panel-media-browser")
|
@customElement("ha-panel-media-browser")
|
||||||
class PanelMediaBrowser extends LitElement {
|
class PanelMediaBrowser extends LitElement {
|
||||||
@ -48,17 +42,6 @@ class PanelMediaBrowser extends LitElement {
|
|||||||
private _entityId = BROWSER_PLAYER;
|
private _entityId = BROWSER_PLAYER;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
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`
|
return html`
|
||||||
<ha-app-layout>
|
<ha-app-layout>
|
||||||
<app-header fixed slot="header">
|
<app-header fixed slot="header">
|
||||||
@ -73,23 +56,22 @@ class PanelMediaBrowser extends LitElement {
|
|||||||
"ui.components.media-browser.media-player-browser"
|
"ui.components.media-browser.media-player-browser"
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div class="secondary-text">${title || ""}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<mwc-button @click=${this._showSelectMediaPlayerDialog}>
|
|
||||||
${this.hass.localize("ui.components.media-browser.choose_player")}
|
|
||||||
</mwc-button>
|
|
||||||
</app-toolbar>
|
</app-toolbar>
|
||||||
</app-header>
|
</app-header>
|
||||||
<div class="content">
|
<ha-media-player-browse
|
||||||
<ha-media-player-browse
|
.hass=${this.hass}
|
||||||
.hass=${this.hass}
|
.entityId=${this._entityId}
|
||||||
.entityId=${this._entityId}
|
.navigateIds=${this._navigateIds}
|
||||||
.navigateIds=${this._navigateIds}
|
@media-picked=${this._mediaPicked}
|
||||||
@media-picked=${this._mediaPicked}
|
@media-browsed=${this._mediaBrowsed}
|
||||||
@media-browsed=${this._mediaBrowsed}
|
></ha-media-player-browse>
|
||||||
></ha-media-player-browse>
|
|
||||||
</div>
|
|
||||||
</ha-app-layout>
|
</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) {
|
private _mediaBrowsed(ev) {
|
||||||
if (ev.detail.back) {
|
if (ev.detail.back) {
|
||||||
history.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 {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
@ -199,21 +159,20 @@ class PanelMediaBrowser extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
--mdc-theme-primary: var(--app-header-text-color);
|
--mdc-theme-primary: var(--app-header-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-media-player-browse {
|
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;
|
ha-bar-media-player {
|
||||||
white-space: nowrap;
|
position: absolute;
|
||||||
margin-top: 4px;
|
bottom: 0;
|
||||||
}
|
left: 0;
|
||||||
.heading .secondary-text {
|
right: 0;
|
||||||
font-size: 14px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@ -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_down": "Volume down",
|
||||||
"media_volume_mute": "Volume mute",
|
"media_volume_mute": "Volume mute",
|
||||||
"media_volume_unmute": "Volume unmute",
|
"media_volume_unmute": "Volume unmute",
|
||||||
"text_to_speak": "Text to speak"
|
"text_to_speak": "Text to speak",
|
||||||
|
"nothing_playing": "Nothing Playing"
|
||||||
},
|
},
|
||||||
"persistent_notification": {
|
"persistent_notification": {
|
||||||
"dismiss": "Dismiss"
|
"dismiss": "Dismiss"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user