Fix safari issues and move scrollable to own container (#7029)

This commit is contained in:
Bram Kragten 2020-09-30 11:05:45 +02:00 committed by GitHub
parent 0456669aeb
commit 4f2bad034a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 321 additions and 259 deletions

View File

@ -90,14 +90,20 @@ class DialogMediaPlayerBrowse extends LitElement {
--dialog-content-padding: 0; --dialog-content-padding: 0;
} }
ha-media-player-browse {
--media-browser-max-height: 100vh;
}
@media (min-width: 800px) { @media (min-width: 800px) {
ha-dialog { ha-dialog {
--mdc-dialog-max-width: 800px; --mdc-dialog-max-width: 800px;
--dialog-surface-position: fixed; --dialog-surface-position: fixed;
--dialog-surface-top: 40px; --dialog-surface-top: 40px;
--mdc-dialog-max-height: calc(100% - 72px); --mdc-dialog-max-height: calc(100vh - 72px);
} }
ha-media-player-browse { ha-media-player-browse {
position: initial;
--media-browser-max-height: 100vh - 72px;
width: 700px; width: 700px;
} }
} }

View File

@ -10,11 +10,13 @@ import {
css, css,
CSSResultArray, CSSResultArray,
customElement, customElement,
eventOptions,
html, html,
internalProperty, internalProperty,
LitElement, LitElement,
property, property,
PropertyValues, PropertyValues,
query,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { classMap } from "lit-html/directives/class-map"; import { classMap } from "lit-html/directives/class-map";
@ -67,12 +69,21 @@ export class HaMediaPlayerBrowse extends LitElement {
@property({ type: Boolean, attribute: "narrow", reflect: true }) @property({ type: Boolean, attribute: "narrow", reflect: true })
private _narrow = false; private _narrow = false;
@property({ type: Boolean, attribute: "scroll", reflect: true })
private _scrolled = false;
@internalProperty() private _loading = false; @internalProperty() private _loading = false;
@internalProperty() private _error?: { message: string; code: string }; @internalProperty() private _error?: { message: string; code: string };
@internalProperty() private _mediaPlayerItems: MediaPlayerItem[] = []; @internalProperty() private _mediaPlayerItems: MediaPlayerItem[] = [];
@query(".header") private _header?: HTMLDivElement;
@query(".content") private _content?: HTMLDivElement;
private _headerOffsetHeight = 0;
private _resizeObserver?: ResizeObserver; private _resizeObserver?: ResizeObserver;
public connectedCallback(): void { public connectedCallback(): void {
@ -140,274 +151,283 @@ export class HaMediaPlayerBrowse extends LitElement {
return html` return html`
<div <div
class="header ${classMap({ class="header ${classMap({
"no-img": !currentItem.thumbnail, "no-img": !currentItem.thumbnail,
"no-dialog": !this.dialog, "no-dialog": !this.dialog,
})}" })}"
@transitionend=${this._setHeaderHeight}
> >
<div class="header-wrapper"> <div class="header-content">
<div class="header-content"> ${currentItem.thumbnail
${currentItem.thumbnail ? html`
<div
class="img"
style=${styleMap({
backgroundImage: currentItem.thumbnail
? `url(${currentItem.thumbnail})`
: "none",
})}
>
${this._narrow && currentItem?.can_play
? html`
<mwc-fab
mini
.item=${currentItem}
@click=${this._actionClicked}
>
<ha-svg-icon
slot="icon"
.label=${this.hass.localize(
`ui.components.media-browser.${this.action}-media`
)}
.path=${this.action === "play" ? mdiPlay : mdiPlus}
></ha-svg-icon>
${this.hass.localize(
`ui.components.media-browser.${this.action}`
)}
</mwc-fab>
`
: ""}
</div>
`
: html``}
<div class="header-info">
<div class="breadcrumb">
${previousItem
? html`
<div class="previous-title" @click=${this.navigateBack}>
<ha-svg-icon .path=${mdiArrowLeft}></ha-svg-icon>
${previousItem.title}
</div>
`
: ""}
<h1 class="title">${currentItem.title}</h1>
${subtitle
? html`
<h2 class="subtitle">
${subtitle}
</h2>
`
: ""}
</div>
${currentItem.can_play && (!currentItem.thumbnail || !this._narrow)
? html` ? html`
<div <mwc-button
class="img" raised
style=${styleMap({ .item=${currentItem}
backgroundImage: currentItem.thumbnail @click=${this._actionClicked}
? `url(${currentItem.thumbnail})`
: "none",
})}
> >
${this._narrow && currentItem?.can_play <ha-svg-icon
? html` .label=${this.hass.localize(
<mwc-fab `ui.components.media-browser.${this.action}-media`
mini )}
.item=${currentItem} .path=${this.action === "play" ? mdiPlay : mdiPlus}
></ha-svg-icon>
${this.hass.localize(
`ui.components.media-browser.${this.action}`
)}
</mwc-button>
`
: ""}
</div>
</div>
${this.dialog
? html`
<mwc-icon-button
aria-label=${this.hass.localize("ui.dialogs.generic.close")}
@click=${this._closeDialogAction}
class="header_button"
dir=${computeRTLDirection(this.hass)}
>
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
`
: ""}
</div>
<div class="content" @scroll=${this._scroll} @touchmove=${this._scroll}>
${this._error
? html`
<div class="container">
${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
class="child"
.item=${child}
@click=${this._childClicked}
>
<div class="ha-card-parent">
<ha-card
outlined
style=${styleMap({
backgroundImage: child.thumbnail
? `url(${child.thumbnail})`
: "none",
})}
>
${!child.thumbnail
? html`
<ha-svg-icon
class="folder"
.path=${MediaClassBrowserSettings[
child.media_class === "directory"
? child.children_media_class ||
child.media_class
: child.media_class
].icon}
></ha-svg-icon>
`
: ""}
</ha-card>
${child.can_play
? html`
<mwc-icon-button
class="play ${classMap({
can_expand: child.can_expand,
})}"
.item=${child}
.label=${this.hass.localize(
`ui.components.media-browser.${this.action}-media`
)}
@click=${this._actionClicked}
>
<ha-svg-icon
.path=${this.action === "play"
? mdiPlay
: mdiPlus}
></ha-svg-icon>
</mwc-icon-button>
`
: ""}
</div>
<div class="title">
${child.title}
<paper-tooltip
fitToVisibleBounds
position="top"
offset="4"
>${child.title}</paper-tooltip
>
</div>
<div class="type">
${this.hass.localize(
`ui.components.media-browser.content-type.${child.media_content_type}`
)}
</div>
</div>
`
)}
</div>
`
: html`
<mwc-list>
${currentItem.children.map(
(child) => html`
<mwc-list-item
@click=${this._childClicked}
.item=${child}
graphic="avatar"
hasMeta
dir=${computeRTLDirection(this.hass)}
>
<div
class="graphic"
style=${ifDefined(
mediaClass.show_list_images && child.thumbnail
? `background-image: url(${child.thumbnail})`
: undefined
)}
slot="graphic"
>
<mwc-icon-button
class="play ${classMap({
show:
!mediaClass.show_list_images ||
!child.thumbnail,
})}"
.item=${child}
.label=${this.hass.localize(
`ui.components.media-browser.${this.action}-media`
)}
@click=${this._actionClicked} @click=${this._actionClicked}
> >
<ha-svg-icon <ha-svg-icon
slot="icon"
.label=${this.hass.localize(
`ui.components.media-browser.${this.action}-media`
)}
.path=${this.action === "play" .path=${this.action === "play"
? mdiPlay ? mdiPlay
: mdiPlus} : mdiPlus}
></ha-svg-icon> ></ha-svg-icon>
${this.hass.localize( </mwc-icon-button>
`ui.components.media-browser.${this.action}` </div>
)} <span class="title">${child.title}</span>
</mwc-fab> </mwc-list-item>
` <li divider role="separator"></li>
: ""}
</div>
`
: html``}
<div class="header-info">
<div class="breadcrumb">
${previousItem
? html`
<div class="previous-title" @click=${this.navigateBack}>
<ha-svg-icon .path=${mdiArrowLeft}></ha-svg-icon>
${previousItem.title}
</div>
` `
: ""} )}
<h1 class="title">${currentItem.title}</h1> </mwc-list>
${subtitle
? html`
<h2 class="subtitle">
${subtitle}
</h2>
`
: ""}
</div>
${currentItem.can_play &&
(!currentItem.thumbnail || !this._narrow)
? html`
<mwc-button
raised
.item=${currentItem}
@click=${this._actionClicked}
>
<ha-svg-icon
.label=${this.hass.localize(
`ui.components.media-browser.${this.action}-media`
)}
.path=${this.action === "play" ? mdiPlay : mdiPlus}
></ha-svg-icon>
${this.hass.localize(
`ui.components.media-browser.${this.action}`
)}
</mwc-button>
`
: ""}
</div>
</div>
${this.dialog
? html`
<mwc-icon-button
aria-label=${this.hass.localize("ui.dialogs.generic.close")}
@click=${this._closeDialogAction}
class="header_button"
dir=${computeRTLDirection(this.hass)}
>
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
` `
: ""}
</div>
</div>
${this._error
? html`
<div class="container">
${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
class="child"
.item=${child}
@click=${this._childClicked}
>
<div class="ha-card-parent">
<ha-card
outlined
style=${styleMap({
backgroundImage: child.thumbnail
? `url(${child.thumbnail})`
: "none",
})}
>
${!child.thumbnail
? html`
<ha-svg-icon
class="folder"
.path=${MediaClassBrowserSettings[
child.media_class === "directory"
? child.children_media_class ||
child.media_class
: child.media_class
].icon}
></ha-svg-icon>
`
: ""}
</ha-card>
${child.can_play
? html`
<mwc-icon-button
class="play ${classMap({
can_expand: child.can_expand,
})}"
.item=${child}
.label=${this.hass.localize(
`ui.components.media-browser.${this.action}-media`
)}
@click=${this._actionClicked}
>
<ha-svg-icon
.path=${this.action === "play"
? mdiPlay
: mdiPlus}
></ha-svg-icon>
</mwc-icon-button>
`
: ""}
</div>
<div class="title">
${child.title}
<paper-tooltip
fitToVisibleBounds
position="top"
offset="4"
>${child.title}</paper-tooltip
>
</div>
<div class="type">
${this.hass.localize(
`ui.components.media-browser.content-type.${child.media_content_type}`
)}
</div>
</div>
`
)}
</div>
`
: html` : html`
<mwc-list> <div class="container">
${currentItem.children.map( ${this.hass.localize("ui.components.media-browser.no_items")}
(child) => html` ${currentItem.media_content_id ===
<mwc-list-item "media-source://media_source/local/."
@click=${this._childClicked} ? html`<br />${this.hass.localize(
.item=${child} "ui.components.media-browser.learn_adding_local_media",
graphic="avatar" "documentation",
hasMeta html`<a
dir=${computeRTLDirection(this.hass)} href="${documentationUrl(
> this.hass,
<div "/more-info/local-media/add-media"
class="graphic" )}"
style=${ifDefined( target="_blank"
mediaClass.show_list_images && child.thumbnail rel="noreferrer"
? `background-image: url(${child.thumbnail})` >${this.hass.localize(
: undefined "ui.components.media-browser.documentation"
)} )}</a
slot="graphic" >`
> )}
<mwc-icon-button <br />
class="play ${classMap({ ${this.hass.localize(
show: "ui.components.media-browser.local_media_files"
!mediaClass.show_list_images || !child.thumbnail, )}.`
})}" : ""}
.item=${child} </div>
.label=${this.hass.localize( `}
`ui.components.media-browser.${this.action}-media` </div>
)}
@click=${this._actionClicked}
>
<ha-svg-icon
.path=${this.action === "play" ? mdiPlay : mdiPlus}
></ha-svg-icon>
</mwc-icon-button>
</div>
<span class="title">${child.title}</span>
</mwc-list-item>
<li divider role="separator"></li>
`
)}
</mwc-list>
`
: html`
<div class="container">
${this.hass.localize("ui.components.media-browser.no_items")}
${currentItem.media_content_id ===
"media-source://media_source/local/."
? html`<br />${this.hass.localize(
"ui.components.media-browser.learn_adding_local_media",
"documentation",
html`<a
href="${documentationUrl(
this.hass,
"/more-info/local-media/add-media"
)}"
target="_blank"
rel="noreferrer"
>${this.hass.localize(
"ui.components.media-browser.documentation"
)}</a
>`
)}
<br />
${this.hass.localize(
"ui.components.media-browser.local_media_files"
)}.`
: ""}
</div>
`}
`; `;
} }
protected firstUpdated(): void { protected firstUpdated(): void {
this._measureCard(); this._measureCard();
this._attachObserver(); this._attachObserver();
this.addEventListener("scroll", this._scroll, { passive: true });
this.addEventListener("touchmove", this._scroll, {
passive: true,
});
} }
protected updated(changedProps: PropertyValues): void { protected updated(changedProps: PropertyValues): void {
super.updated(changedProps); super.updated(changedProps);
if (
changedProps.has("_mediaPlayerItems") &&
this._mediaPlayerItems.length
) {
this._setHeaderHeight();
}
if (
changedProps.get("_scrolled") !== undefined &&
this._mediaPlayerItems.length
) {
this._animateHeaderHeight();
}
if ( if (
!changedProps.has("entityId") && !changedProps.has("entityId") &&
!changedProps.has("mediaContentId") && !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 { private _actionClicked(ev: MouseEvent): void {
ev.stopPropagation(); ev.stopPropagation();
const item = (ev.currentTarget as any).item; const item = (ev.currentTarget as any).item;
@ -482,7 +529,8 @@ export class HaMediaPlayerBrowse extends LitElement {
return; return;
} }
this.scrollTo(0, 0); this._content?.scrollTo(0, 0);
this._scrolled = false;
this._mediaPlayerItems = [...this._mediaPlayerItems, itemData]; this._mediaPlayerItems = [...this._mediaPlayerItems, itemData];
} }
@ -509,11 +557,13 @@ export class HaMediaPlayerBrowse extends LitElement {
this._narrow = (this.dialog ? window.innerWidth : this.offsetWidth) < 450; this._narrow = (this.dialog ? window.innerWidth : this.offsetWidth) < 450;
} }
private _scroll(): void { @eventOptions({ passive: true })
if (this.scrollTop > (this._narrow ? 224 : 125)) { private _scroll(ev: Event): void {
this.setAttribute("scroll", ""); const content = ev.currentTarget as HTMLDivElement;
} else if (this.scrollTop === 0) { if (!this._scrolled && content.scrollTop > this._headerOffsetHeight) {
this.removeAttribute("scroll"); this._scrolled = true;
} else if (this._scrolled && content.scrollTop < this._headerOffsetHeight) {
this._scrolled = false;
} }
} }
@ -571,38 +621,44 @@ export class HaMediaPlayerBrowse extends LitElement {
haStyle, haStyle,
css` css`
:host { :host {
display: block;
overflow-y: auto;
display: flex; display: flex;
padding: 0px 0px 20px;
flex-direction: column; flex-direction: column;
position: relative;
} }
ha-circular-progress { ha-circular-progress {
--mdc-theme-primary: var(--primary-color); --mdc-theme-primary: var(--primary-color);
display: flex; display: flex;
justify-content: center; justify-content: center;
margin-top: 40px; margin: 40px;
} }
.container { .container {
padding: 16px; padding: 16px;
} }
.content {
overflow-y: auto;
padding-bottom: 20px;
box-sizing: border-box;
}
.header { .header {
display: block; display: flex;
justify-content: space-between; justify-content: space-between;
border-bottom: 1px solid var(--divider-color); border-bottom: 1px solid var(--divider-color);
background-color: var(--card-background-color); background-color: var(--card-background-color);
position: sticky; position: absolute;
position: -webkit-sticky;
top: 0; top: 0;
right: 0;
left: 0;
z-index: 5; z-index: 5;
padding: 20px 24px 10px; padding: 20px 24px 10px;
} }
.header-wrapper { .header_button {
display: flex; position: relative;
right: -8px;
} }
.header-content { .header-content {
@ -696,8 +752,8 @@ 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;
margin: 8px 0px;
padding: 0px 24px; padding: 0px 24px;
margin: 8px 0px;
} }
:host([dialog]) .children { :host([dialog]) .children {

View File

@ -137,7 +137,7 @@ class PanelMediaBrowser extends LitElement {
--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 - 84px); height: calc(100vh - 64px);
} }
:host([narrow]) app-toolbar mwc-button { :host([narrow]) app-toolbar mwc-button {
width: 65px; width: 65px;