From 8b490c50475cca775cc7e8d6bcb4f35b41ef5f69 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Sat, 12 Sep 2020 11:59:19 -0500 Subject: [PATCH] Media Browser: Use Media Class (#6904) Co-authored-by: Bram Kragten --- .../media-player/ha-media-player-browse.ts | 334 ++++++++++-------- src/data/media-player.ts | 80 +++++ .../media-browser/ha-panel-media-browser.ts | 39 +- src/translations/en.json | 25 +- 4 files changed, 313 insertions(+), 165 deletions(-) diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index 741a85b238..7bf868254f 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -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` -

No local media found.

-

- It looks like you have not yet created a media directory. -
Create a directory with the name "media" in the - configuration directory of Home Assistant - (${this.hass.config.config_dir}).
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. -

- -

- Check the - documentation - for more info -

- `; - } - return err.message; - } - protected render(): TemplateResult { if (this._loading) { return html``; @@ -136,7 +108,7 @@ export class HaMediaPlayerBrowse extends LitElement { text: this._renderError(this._error), }); } else { - return html`
+ return html`
${this._renderError(this._error)}
`; } @@ -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`
-
- ${currentItem.thumbnail - ? html` -
- ${this._narrow && currentItem?.can_play - ? html` - - +
+ ${currentItem.thumbnail + ? html` +
+ ${this._narrow && currentItem?.can_play + ? html` + + + ${this.hass.localize( + `ui.components.media-browser.${this.action}` )} - .path=${this.action === "play" ? mdiPlay : mdiPlus} - > - ${this.hass.localize( - `ui.components.media-browser.${this.action}` - )} - - ` - : ""} -
- ` - : html``} -
- + ` + : html``} +
+ + ${currentItem.can_play && + (!currentItem.thumbnail || !this._narrow) ? html` -
- - ${previousItem.title} -
- ` - : ""} -

${currentItem.title}

- ${mediaType - ? html` -

- ${mediaType} -

+ + + ${this.hass.localize( + `ui.components.media-browser.${this.action}` + )} + ` : ""}
- ${currentItem.can_play && (!currentItem.thumbnail || !this._narrow) - ? html` - - - ${this.hass.localize( - `ui.components.media-browser.${this.action}` - )} - - ` - : ""}
+ ${this.dialog + ? html` + + + + ` + : ""}
- ${this.dialog - ? html` - - - - ` - : ""}
${this._error - ? html`
- ${this._renderError(this._error)} -
` + ? html` +
+ ${this._renderError(this._error)} +
+ ` : currentItem.children?.length - ? hasExpandableChildren + ? childrenMediaClass.layout === "grid" ? html` -
+
${currentItem.children.map( (child) => html`
- ${child.can_expand && !child.thumbnail + ${!child.thumbnail ? html` ` : ""} @@ -298,7 +281,9 @@ export class HaMediaPlayerBrowse extends LitElement { ${child.can_play ? html` html` ` - : html`
- ${this.hass.localize("ui.components.media-browser.no_items")} -
`} + : html` +
+ ${this.hass.localize("ui.components.media-browser.no_items")} +
+ `} `; } @@ -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` +

No local media found.

+

+ It looks like you have not yet created a media directory. +
Create a directory with the name "media" in the + configuration directory of Home Assistant + (${this.hass.config.config_dir}).
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. +

+ +

+ Check the + documentation + for more info +

+ `; + } + return html`err.message`; + } + 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 { diff --git a/src/data/media-player.ts b/src/data/media-player.ts index af11824211..70b29c2d63 100644 --- a/src/data/media-player.ts +++ b/src/data/media-player.ts @@ -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; diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index a58243857e..63c71d2e92 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -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,17 +58,17 @@ class PanelMediaBrowser extends LitElement { .hass=${this.hass} .narrow=${this.narrow} > -
- ${title || ""}${this.hass.localize( - "ui.components.media-browser.media-player-browser" - )} -
- - +
+
${this.hass.localize( - "ui.components.media-browser.choose_player" + "ui.components.media-browser.media-player-browser" )} - +
+
${title || ""}
+
+ + ${this.hass.localize("ui.components.media-browser.choose_player")} +
@@ -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; + } `, ]; } diff --git a/src/translations/en.json b/src/translations/en.json index e7d2ea366e..8eadb910a9 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -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" } } },