diff --git a/src/components/media-player/dialog-media-player-browse.ts b/src/components/media-player/dialog-media-player-browse.ts index 311536844f..3ef4f00c04 100644 --- a/src/components/media-player/dialog-media-player-browse.ts +++ b/src/components/media-player/dialog-media-player-browse.ts @@ -90,14 +90,20 @@ class DialogMediaPlayerBrowse extends LitElement { --dialog-content-padding: 0; } + ha-media-player-browse { + --media-browser-max-height: 100vh; + } + @media (min-width: 800px) { ha-dialog { --mdc-dialog-max-width: 800px; --dialog-surface-position: fixed; --dialog-surface-top: 40px; - --mdc-dialog-max-height: calc(100% - 72px); + --mdc-dialog-max-height: calc(100vh - 72px); } ha-media-player-browse { + position: initial; + --media-browser-max-height: 100vh - 72px; width: 700px; } } diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index 53dfc5c230..32146ec822 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -10,11 +10,13 @@ import { css, CSSResultArray, customElement, + eventOptions, html, internalProperty, LitElement, property, PropertyValues, + query, TemplateResult, } from "lit-element"; import { classMap } from "lit-html/directives/class-map"; @@ -67,12 +69,21 @@ export class HaMediaPlayerBrowse extends LitElement { @property({ type: Boolean, attribute: "narrow", reflect: true }) private _narrow = false; + @property({ type: Boolean, attribute: "scroll", reflect: true }) + private _scrolled = false; + @internalProperty() private _loading = false; @internalProperty() private _error?: { message: string; code: string }; @internalProperty() private _mediaPlayerItems: MediaPlayerItem[] = []; + @query(".header") private _header?: HTMLDivElement; + + @query(".content") private _content?: HTMLDivElement; + + private _headerOffsetHeight = 0; + private _resizeObserver?: ResizeObserver; public connectedCallback(): void { @@ -140,274 +151,283 @@ export class HaMediaPlayerBrowse extends LitElement { return html`
-
-
- ${currentItem.thumbnail +
+ ${currentItem.thumbnail + ? html` +
+ ${this._narrow && currentItem?.can_play + ? html` + + + ${this.hass.localize( + `ui.components.media-browser.${this.action}` + )} + + ` + : ""} +
+ ` + : html``} +
+ + ${currentItem.can_play && (!currentItem.thumbnail || !this._narrow) ? html` -
- ${this._narrow && currentItem?.can_play - ? html` - + ${this.hass.localize( + `ui.components.media-browser.${this.action}` + )} + + ` + : ""} +
+
+ ${this.dialog + ? html` + + + + ` + : ""} +
+
+ ${this._error + ? html` +
+ ${this._renderError(this._error)} +
+ ` + : currentItem.children?.length + ? childrenMediaClass.layout === "grid" + ? html` +
+ ${currentItem.children.map( + (child) => html` +
+
+ + ${!child.thumbnail + ? html` + + ` + : ""} + + ${child.can_play + ? html` + + + + ` + : ""} +
+
+ ${child.title} + ${child.title} +
+
+ ${this.hass.localize( + `ui.components.media-browser.content-type.${child.media_content_type}` + )} +
+
+ ` + )} +
+ ` + : html` + + ${currentItem.children.map( + (child) => html` + +
+ - ${this.hass.localize( - `ui.components.media-browser.${this.action}` - )} - - ` - : ""} -
- ` - : html``} -
- + ${child.title} + +
  • ` - : ""} -

    ${currentItem.title}

    - ${subtitle - ? html` -

    - ${subtitle} -

    - ` - : ""} -
    - ${currentItem.can_play && - (!currentItem.thumbnail || !this._narrow) - ? html` - - - ${this.hass.localize( - `ui.components.media-browser.${this.action}` - )} - - ` - : ""} -
    -
    - ${this.dialog - ? html` - - - + )} + ` - : ""} -
    -
    - ${this._error - ? html` -
    - ${this._renderError(this._error)} -
    - ` - : currentItem.children?.length - ? childrenMediaClass.layout === "grid" - ? html` -
    - ${currentItem.children.map( - (child) => html` -
    -
    - - ${!child.thumbnail - ? html` - - ` - : ""} - - ${child.can_play - ? html` - - - - ` - : ""} -
    -
    - ${child.title} - ${child.title} -
    - -
    - ${this.hass.localize( - `ui.components.media-browser.content-type.${child.media_content_type}` - )} -
    -
    - ` - )} -
    - ` : html` - - ${currentItem.children.map( - (child) => html` - -
    - - - -
    - ${child.title} -
    -
  • - ` - )} -
    - ` - : html` -
    - ${this.hass.localize("ui.components.media-browser.no_items")} - ${currentItem.media_content_id === - "media-source://media_source/local/." - ? html`
    ${this.hass.localize( - "ui.components.media-browser.learn_adding_local_media", - "documentation", - html`${this.hass.localize( - "ui.components.media-browser.documentation" - )}` - )} -
    - ${this.hass.localize( - "ui.components.media-browser.local_media_files" - )}.` - : ""} -
    - `} +
    + ${this.hass.localize("ui.components.media-browser.no_items")} + ${currentItem.media_content_id === + "media-source://media_source/local/." + ? html`
    ${this.hass.localize( + "ui.components.media-browser.learn_adding_local_media", + "documentation", + html`${this.hass.localize( + "ui.components.media-browser.documentation" + )}` + )} +
    + ${this.hass.localize( + "ui.components.media-browser.local_media_files" + )}.` + : ""} +
    + `} + `; } protected firstUpdated(): void { this._measureCard(); this._attachObserver(); - - this.addEventListener("scroll", this._scroll, { passive: true }); - this.addEventListener("touchmove", this._scroll, { - passive: true, - }); } protected updated(changedProps: PropertyValues): void { super.updated(changedProps); + if ( + changedProps.has("_mediaPlayerItems") && + this._mediaPlayerItems.length + ) { + this._setHeaderHeight(); + } + + if ( + changedProps.get("_scrolled") !== undefined && + this._mediaPlayerItems.length + ) { + this._animateHeaderHeight(); + } + if ( !changedProps.has("entityId") && !changedProps.has("mediaContentId") && @@ -435,6 +455,33 @@ export class HaMediaPlayerBrowse extends LitElement { }); } + private async _setHeaderHeight() { + await this.updateComplete; + const header = this._header; + const content = this._content; + if (!header || !content) { + return; + } + this._headerOffsetHeight = header.offsetHeight; + content.style.marginTop = `${this._headerOffsetHeight}px`; + content.style.maxHeight = `calc(var(--media-browser-max-height, 100%) - ${this._headerOffsetHeight}px)`; + } + + private _animateHeaderHeight() { + let start; + const animate = (time) => { + if (start === undefined) { + start = time; + } + const elapsed = time - start; + this._setHeaderHeight(); + if (elapsed < 400) { + requestAnimationFrame(animate); + } + }; + requestAnimationFrame(animate); + } + private _actionClicked(ev: MouseEvent): void { ev.stopPropagation(); const item = (ev.currentTarget as any).item; @@ -482,7 +529,8 @@ export class HaMediaPlayerBrowse extends LitElement { return; } - this.scrollTo(0, 0); + this._content?.scrollTo(0, 0); + this._scrolled = false; this._mediaPlayerItems = [...this._mediaPlayerItems, itemData]; } @@ -509,11 +557,13 @@ export class HaMediaPlayerBrowse extends LitElement { this._narrow = (this.dialog ? window.innerWidth : this.offsetWidth) < 450; } - private _scroll(): void { - if (this.scrollTop > (this._narrow ? 224 : 125)) { - this.setAttribute("scroll", ""); - } else if (this.scrollTop === 0) { - this.removeAttribute("scroll"); + @eventOptions({ passive: true }) + private _scroll(ev: Event): void { + const content = ev.currentTarget as HTMLDivElement; + if (!this._scrolled && content.scrollTop > this._headerOffsetHeight) { + this._scrolled = true; + } else if (this._scrolled && content.scrollTop < this._headerOffsetHeight) { + this._scrolled = false; } } @@ -571,38 +621,44 @@ export class HaMediaPlayerBrowse extends LitElement { haStyle, css` :host { - display: block; - overflow-y: auto; display: flex; - padding: 0px 0px 20px; flex-direction: column; + position: relative; } ha-circular-progress { --mdc-theme-primary: var(--primary-color); display: flex; justify-content: center; - margin-top: 40px; + margin: 40px; } .container { padding: 16px; } + .content { + overflow-y: auto; + padding-bottom: 20px; + box-sizing: border-box; + } + .header { - display: block; + display: flex; justify-content: space-between; border-bottom: 1px solid var(--divider-color); background-color: var(--card-background-color); - position: sticky; - position: -webkit-sticky; + position: absolute; top: 0; + right: 0; + left: 0; z-index: 5; padding: 20px 24px 10px; } - .header-wrapper { - display: flex; + .header_button { + position: relative; + right: -8px; } .header-content { @@ -696,8 +752,8 @@ export class HaMediaPlayerBrowse extends LitElement { minmax(var(--media-browse-item-size, 175px), 0.1fr) ); grid-gap: 16px; - margin: 8px 0px; padding: 0px 24px; + margin: 8px 0px; } :host([dialog]) .children { diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index 32e8661d52..7409a1e7d0 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -137,7 +137,7 @@ class PanelMediaBrowser extends LitElement { --mdc-theme-primary: var(--app-header-text-color); } ha-media-player-browse { - height: calc(100vh - 84px); + height: calc(100vh - 64px); } :host([narrow]) app-toolbar mwc-button { width: 65px;