mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 03:36:44 +00:00
Media Browser: Use Media Class (#6904)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
7e70ba6ab2
commit
8b490c5047
@ -2,7 +2,7 @@ import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-fab/mwc-fab";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiArrowLeft, mdiClose, mdiFolder, mdiPlay, mdiPlus } from "@mdi/js";
|
||||
import { mdiArrowLeft, mdiClose, mdiPlay, mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
@ -19,7 +19,6 @@ import {
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import { styleMap } from "lit-html/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
@ -27,6 +26,7 @@ import {
|
||||
browseLocalMediaPlayer,
|
||||
browseMediaPlayer,
|
||||
BROWSER_SOURCE,
|
||||
MediaClassBrowserSettings,
|
||||
MediaPickedEvent,
|
||||
MediaPlayerBrowseAction,
|
||||
} from "../../data/media-player";
|
||||
@ -93,34 +93,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
this._navigate(item);
|
||||
}
|
||||
|
||||
private _renderError(err: { message: string; code: string }) {
|
||||
if (err.message === "Media directory does not exist.") {
|
||||
return html`
|
||||
<h2>No local media found.</h2>
|
||||
<p>
|
||||
It looks like you have not yet created a media directory.
|
||||
<br />Create a directory with the name <b>"media"</b> in the
|
||||
configuration directory of Home Assistant
|
||||
(${this.hass.config.config_dir}). <br />Place your video, audio and
|
||||
image files in this directory to be able to browse and play them in
|
||||
the browser or on supported media players.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Check the
|
||||
<a
|
||||
href="https://www.home-assistant.io/integrations/media_source/#local-media"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>documentation</a
|
||||
>
|
||||
for more info
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
return err.message;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this._loading) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
@ -136,7 +108,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
text: this._renderError(this._error),
|
||||
});
|
||||
} else {
|
||||
return html`<div class="container error">
|
||||
return html`<div class="container">
|
||||
${this._renderError(this._error)}
|
||||
</div>`;
|
||||
}
|
||||
@ -155,17 +127,12 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
? this._mediaPlayerItems[this._mediaPlayerItems.length - 2]
|
||||
: undefined;
|
||||
|
||||
const hasExpandableChildren:
|
||||
| MediaPlayerItem
|
||||
| undefined = this._hasExpandableChildren(currentItem.children);
|
||||
|
||||
const showImages: boolean | undefined = currentItem.children?.some(
|
||||
(child) => child.thumbnail && child.thumbnail !== currentItem.thumbnail
|
||||
);
|
||||
|
||||
const mediaType = this.hass.localize(
|
||||
`ui.components.media-browser.content-type.${currentItem.media_content_type}`
|
||||
const subtitle = this.hass.localize(
|
||||
`ui.components.media-browser.class.${currentItem.media_class}`
|
||||
);
|
||||
const mediaClass = MediaClassBrowserSettings[currentItem.media_class];
|
||||
const childrenMediaClass =
|
||||
MediaClassBrowserSettings[currentItem.children_media_class];
|
||||
|
||||
return html`
|
||||
<div
|
||||
@ -174,6 +141,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
"no-dialog": !this.dialog,
|
||||
})}"
|
||||
>
|
||||
<div class="header-wrapper">
|
||||
<div class="header-content">
|
||||
${currentItem.thumbnail
|
||||
? html`
|
||||
@ -197,7 +165,9 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
.label=${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}-media`
|
||||
)}
|
||||
.path=${this.action === "play" ? mdiPlay : mdiPlus}
|
||||
.path=${this.action === "play"
|
||||
? mdiPlay
|
||||
: mdiPlus}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}`
|
||||
@ -219,15 +189,16 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
<h1 class="title">${currentItem.title}</h1>
|
||||
${mediaType
|
||||
${subtitle
|
||||
? html`
|
||||
<h2 class="subtitle">
|
||||
${mediaType}
|
||||
${subtitle}
|
||||
</h2>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
${currentItem.can_play && (!currentItem.thumbnail || !this._narrow)
|
||||
${currentItem.can_play &&
|
||||
(!currentItem.thumbnail || !this._narrow)
|
||||
? html`
|
||||
<mwc-button
|
||||
raised
|
||||
@ -262,14 +233,21 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
${this._error
|
||||
? html`<div class="container error">
|
||||
${this._renderError(this._error)}
|
||||
</div>`
|
||||
: currentItem.children?.length
|
||||
? hasExpandableChildren
|
||||
? html`
|
||||
<div class="children">
|
||||
<div class="container error">
|
||||
${this._renderError(this._error)}
|
||||
</div>
|
||||
`
|
||||
: currentItem.children?.length
|
||||
? childrenMediaClass.layout === "grid"
|
||||
? html`
|
||||
<div
|
||||
class="children ${classMap({
|
||||
portrait: childrenMediaClass.thumbnail_ratio === "portrait",
|
||||
})}"
|
||||
>
|
||||
${currentItem.children.map(
|
||||
(child) => html`
|
||||
<div
|
||||
@ -286,11 +264,16 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
: "none",
|
||||
})}
|
||||
>
|
||||
${child.can_expand && !child.thumbnail
|
||||
${!child.thumbnail
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
class="folder"
|
||||
.path=${mdiFolder}
|
||||
.path=${MediaClassBrowserSettings[
|
||||
child.media_class === "directory"
|
||||
? child.children_media_class ||
|
||||
child.media_class
|
||||
: child.media_class
|
||||
].icon}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: ""}
|
||||
@ -298,7 +281,9 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
${child.can_play
|
||||
? html`
|
||||
<mwc-icon-button
|
||||
class="play"
|
||||
class="play ${classMap({
|
||||
can_expand: child.can_expand,
|
||||
})}"
|
||||
.item=${child}
|
||||
.label=${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}-media`
|
||||
@ -330,7 +315,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
${currentItem.children.map(
|
||||
(child) => html`
|
||||
<mwc-list-item
|
||||
@click=${this._actionClicked}
|
||||
@click=${this._childClicked}
|
||||
.item=${child}
|
||||
graphic="avatar"
|
||||
hasMeta
|
||||
@ -339,7 +324,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
<div
|
||||
class="graphic"
|
||||
style=${ifDefined(
|
||||
showImages && child.thumbnail
|
||||
mediaClass.show_list_images && child.thumbnail
|
||||
? `background-image: url(${child.thumbnail})`
|
||||
: undefined
|
||||
)}
|
||||
@ -347,7 +332,8 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
>
|
||||
<mwc-icon-button
|
||||
class="play ${classMap({
|
||||
show: !showImages || !child.thumbnail,
|
||||
show:
|
||||
!mediaClass.show_list_images || !child.thumbnail,
|
||||
})}"
|
||||
.item=${child}
|
||||
.label=${this.hass.localize(
|
||||
@ -367,9 +353,11 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
)}
|
||||
</mwc-list>
|
||||
`
|
||||
: html`<div class="container">
|
||||
: html`
|
||||
<div class="container">
|
||||
${this.hass.localize("ui.components.media-browser.no_items")}
|
||||
</div>`}
|
||||
</div>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
|
||||
@ -504,14 +492,38 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
this._resizeObserver.observe(this);
|
||||
}
|
||||
|
||||
private _hasExpandableChildren = memoizeOne((children?: MediaPlayerItem[]) =>
|
||||
children?.find((item: MediaPlayerItem) => item.can_expand)
|
||||
);
|
||||
|
||||
private _closeDialogAction(): void {
|
||||
fireEvent(this, "close-dialog");
|
||||
}
|
||||
|
||||
private _renderError(err: { message: string; code: string }) {
|
||||
if (err.message === "Media directory does not exist.") {
|
||||
return html`
|
||||
<h2>No local media found.</h2>
|
||||
<p>
|
||||
It looks like you have not yet created a media directory.
|
||||
<br />Create a directory with the name <b>"media"</b> in the
|
||||
configuration directory of Home Assistant
|
||||
(${this.hass.config.config_dir}). <br />Place your video, audio and
|
||||
image files in this directory to be able to browse and play them in
|
||||
the browser or on supported media players.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Check the
|
||||
<a
|
||||
href="https://www.home-assistant.io/integrations/media_source/#local-media"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>documentation</a
|
||||
>
|
||||
for more info
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
return html`<span class="error">err.message</span>`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
haStyle,
|
||||
@ -529,12 +541,9 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
display: block;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: var(--card-background-color);
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
@ -543,6 +552,10 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
padding: 20px 24px 10px;
|
||||
}
|
||||
|
||||
.header-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@ -570,6 +583,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
|
||||
.header-info mwc-button {
|
||||
display: block;
|
||||
--mdc-theme-primary: var(--primary-color);
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
@ -655,7 +669,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
.children ha-card {
|
||||
width: 100%;
|
||||
padding-bottom: 100%;
|
||||
position: relative;
|
||||
@ -663,6 +677,11 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
transition: padding-bottom 0.1s ease-out;
|
||||
}
|
||||
|
||||
.portrait.children ha-card {
|
||||
padding-bottom: 150%;
|
||||
}
|
||||
|
||||
.child .folder,
|
||||
@ -678,18 +697,36 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
}
|
||||
|
||||
.child .play {
|
||||
transition: color 0.5s;
|
||||
border-radius: 50%;
|
||||
bottom: calc(50% - 35px);
|
||||
right: calc(50% - 35px);
|
||||
opacity: 0;
|
||||
transition: opacity 0.1s ease-out;
|
||||
}
|
||||
|
||||
.child .play:not(.can_expand) {
|
||||
--mdc-icon-button-size: 70px;
|
||||
--mdc-icon-size: 48px;
|
||||
}
|
||||
|
||||
.ha-card-parent:hover .play:not(.can_expand) {
|
||||
opacity: 1;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.child .play.can_expand {
|
||||
opacity: 1;
|
||||
background-color: rgba(var(--rgb-card-background-color), 0.5);
|
||||
bottom: 4px;
|
||||
right: 4px;
|
||||
transition: all 0.5s;
|
||||
background-color: rgba(var(--rgb-card-background-color), 0.5);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.child .play:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
ha-card:hover {
|
||||
.ha-card-parent:hover ha-card {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@ -706,6 +743,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
.child .type {
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
mwc-list-item .graphic {
|
||||
|
@ -1,5 +1,23 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import {
|
||||
mdiFolder,
|
||||
mdiPlaylistMusic,
|
||||
mdiFileMusic,
|
||||
mdiAlbum,
|
||||
mdiMusic,
|
||||
mdiTelevisionClassic,
|
||||
mdiMovie,
|
||||
mdiVideo,
|
||||
mdiImage,
|
||||
mdiWeb,
|
||||
mdiGamepadVariant,
|
||||
mdiAccountMusic,
|
||||
mdiPodcast,
|
||||
mdiApplication,
|
||||
mdiAccountMusicOutline,
|
||||
mdiDramaMasks,
|
||||
} from "@mdi/js";
|
||||
|
||||
export const SUPPORT_PAUSE = 1;
|
||||
export const SUPPORT_SEEK = 2;
|
||||
@ -22,6 +40,66 @@ export type MediaPlayerBrowseAction = "pick" | "play";
|
||||
|
||||
export const BROWSER_SOURCE = "browser";
|
||||
|
||||
export type MediaClassBrowserSetting = {
|
||||
icon: string;
|
||||
thumbnail_ratio?: string;
|
||||
layout?: string;
|
||||
show_list_images?: boolean;
|
||||
};
|
||||
|
||||
export const MediaClassBrowserSettings: {
|
||||
[type: string]: MediaClassBrowserSetting;
|
||||
} = {
|
||||
album: { icon: mdiAlbum, layout: "grid" },
|
||||
app: { icon: mdiApplication, layout: "grid" },
|
||||
artist: { icon: mdiAccountMusic, layout: "grid", show_list_images: true },
|
||||
channel: {
|
||||
icon: mdiTelevisionClassic,
|
||||
thumbnail_ratio: "portrait",
|
||||
layout: "grid",
|
||||
},
|
||||
composer: {
|
||||
icon: mdiAccountMusicOutline,
|
||||
layout: "grid",
|
||||
show_list_images: true,
|
||||
},
|
||||
contributing_artist: {
|
||||
icon: mdiAccountMusic,
|
||||
layout: "grid",
|
||||
show_list_images: true,
|
||||
},
|
||||
directory: { icon: mdiFolder, layout: "grid", show_list_images: true },
|
||||
episode: {
|
||||
icon: mdiTelevisionClassic,
|
||||
layout: "grid",
|
||||
thumbnail_ratio: "portrait",
|
||||
},
|
||||
game: {
|
||||
icon: mdiGamepadVariant,
|
||||
layout: "grid",
|
||||
thumbnail_ratio: "portrait",
|
||||
},
|
||||
genre: { icon: mdiDramaMasks, layout: "grid", show_list_images: true },
|
||||
image: { icon: mdiImage, layout: "grid" },
|
||||
movie: { icon: mdiMovie, thumbnail_ratio: "portrait", layout: "grid" },
|
||||
music: { icon: mdiMusic },
|
||||
playlist: { icon: mdiPlaylistMusic, layout: "grid", show_list_images: true },
|
||||
podcast: { icon: mdiPodcast, layout: "grid" },
|
||||
season: {
|
||||
icon: mdiTelevisionClassic,
|
||||
layout: "grid",
|
||||
thumbnail_ratio: "portrait",
|
||||
},
|
||||
track: { icon: mdiFileMusic },
|
||||
tv_show: {
|
||||
icon: mdiTelevisionClassic,
|
||||
layout: "grid",
|
||||
thumbnail_ratio: "portrait",
|
||||
},
|
||||
url: { icon: mdiWeb },
|
||||
video: { icon: mdiVideo, layout: "grid" },
|
||||
};
|
||||
|
||||
export interface MediaPickedEvent {
|
||||
item: MediaPlayerItem;
|
||||
}
|
||||
@ -40,6 +118,8 @@ export interface MediaPlayerItem {
|
||||
title: string;
|
||||
media_content_type: string;
|
||||
media_content_id: string;
|
||||
media_class: string;
|
||||
children_media_class: string;
|
||||
can_play: boolean;
|
||||
can_expand: boolean;
|
||||
thumbnail?: string;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import "@material/mwc-icon-button";
|
||||
import { mdiPlayNetwork } from "@mdi/js";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import {
|
||||
@ -46,9 +45,9 @@ class PanelMediaBrowser extends LitElement {
|
||||
|
||||
const title =
|
||||
this._entityId === BROWSER_SOURCE
|
||||
? `${this.hass.localize("ui.components.media-browser.web-browser")} - `
|
||||
? `${this.hass.localize("ui.components.media-browser.web-browser")}`
|
||||
: stateObj?.attributes.friendly_name
|
||||
? `${stateObj?.attributes.friendly_name} - `
|
||||
? `${stateObj?.attributes.friendly_name}`
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
@ -59,16 +58,16 @@ class PanelMediaBrowser extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
<div main-title>
|
||||
${title || ""}${this.hass.localize(
|
||||
<div main-title class="heading">
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.components.media-browser.media-player-browser"
|
||||
)}
|
||||
</div>
|
||||
<div class="secondary">${title || ""}</div>
|
||||
</div>
|
||||
<mwc-button @click=${this._showSelectMediaPlayerDialog}>
|
||||
<ha-svg-icon .path=${mdiPlayNetwork}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.components.media-browser.choose_player"
|
||||
)}
|
||||
${this.hass.localize("ui.components.media-browser.choose_player")}
|
||||
</mwc-button>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
@ -134,9 +133,25 @@ class PanelMediaBrowser extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
--mdc-theme-primary: var(--app-header-text-color);
|
||||
}
|
||||
ha-media-player-browse {
|
||||
height: calc(100vh - 84px);
|
||||
}
|
||||
:host([narrow]) app-toolbar mwc-button {
|
||||
width: 65px;
|
||||
}
|
||||
.heading {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.heading .secondary {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -373,12 +373,27 @@
|
||||
"video_not_supported": "Your browser does not support the video element.",
|
||||
"media_not_supported": "The Browser Media Player does not support this type of media",
|
||||
"media_browsing_error": "Media Browsing Error",
|
||||
"content-type": {
|
||||
"server": "Server",
|
||||
"library": "Library",
|
||||
"artist": "Artist",
|
||||
"class": {
|
||||
"album": "Album",
|
||||
"playlist": "Playlist"
|
||||
"app": "App",
|
||||
"artist": "Artist",
|
||||
"channel": "Channel",
|
||||
"composer": "Composer",
|
||||
"contributing_artist": "Contributing Artist",
|
||||
"directory": "Library",
|
||||
"episode": "Episode",
|
||||
"game": "Game",
|
||||
"genre": "Genre",
|
||||
"image": "Image",
|
||||
"movie": "Movie",
|
||||
"music": "Music",
|
||||
"playlist": "Playlist",
|
||||
"podcast": "Podcast",
|
||||
"season": "Season",
|
||||
"track": "Track",
|
||||
"tv_show": "TV Show",
|
||||
"url": "Url",
|
||||
"video": "Video"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user