Add media title popups.

This commit is contained in:
Dermot Duffy
2022-01-30 16:58:22 -08:00
parent 73c979e823
commit f871d9b533
12 changed files with 337 additions and 62 deletions
+15 -2
View File
@@ -21,6 +21,7 @@ import {
} from '../types.js';
import { EmblaOptionsType, EmblaPluginType } from 'embla-carousel';
import { HomeAssistant } from 'custom-card-helpers';
import JSMpeg from '@cycjimmy/jsmpeg-player';
import { Ref, createRef, ref } from 'lit/directives/ref.js';
import { Task } from '@lit-labs/task';
import { customElement, property, state } from 'lit/decorators.js';
@@ -47,7 +48,8 @@ import {
} from '../common.js';
import { renderProgressIndicator } from '../components/message.js';
import JSMpeg from '@cycjimmy/jsmpeg-player';
import './next-prev-control.js';
import './title-control.js';
import liveStyle from '../scss/live.scss';
import liveFrigateStyle from '../scss/live-frigate.scss';
@@ -471,7 +473,7 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
protected render(): TemplateResult | void {
const [slides, cameraToSlide] = this._getSlides();
this._cameraToSlide = cameraToSlide;
if (!slides || !this.liveConfig) {
if (!slides || !this.liveConfig || !this.cameras || !this.view) {
return;
}
@@ -482,6 +484,8 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
) as LiveConfig;
const [prev, next] = this._getCameraNeighbors();
const title = getCameraTitle(this.hass, this.cameras.get(this.view.camera));
return html`
<div class="embla">
<frigate-card-next-previous-control
@@ -512,6 +516,15 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
>
</frigate-card-next-previous-control>
</div>
<frigate-card-title-control
${ref(this._titleControlRef)}
.config=${config.controls.title}
.text="${title
? `${localize('common.live')}: ${title}`
: ''}"
.fitInto=${this as HTMLElement}
>
</frigate-card-title-control>
`;
}
}
+32 -2
View File
@@ -15,6 +15,7 @@ import './next-prev-control.js';
import mediaCarouselStyle from '../scss/media-carousel.scss';
import { FrigateCardNextPreviousControl } from './next-prev-control.js';
import { FrigateCardTitleControl } from './title-control.js';
import { MediaAutoPlayPauseType } from './embla-plugins/media-autoplay.js';
const getEmptyImageSrc = (width: number, height: number) =>
@@ -27,7 +28,8 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
protected _mediaShowInfo: Record<number, MediaShowInfo> = {};
protected _nextControlRef: Ref<FrigateCardNextPreviousControl> = createRef();
protected _previousControlRef: Ref<FrigateCardNextPreviousControl> = createRef();
protected _titleControlRef: Ref<FrigateCardTitleControl> = createRef();
protected _titleTimerID: number | null = null;
/**
* Play the media on the selected slide. May be overridden to control when
* autoplay should happen.
@@ -36,6 +38,32 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
(this._plugins['MediaAutoPlayPause'] as MediaAutoPlayPauseType | undefined)?.play();
}
/**
* Show the media title after the media loads.
*/
protected _titleHandler(): void {
const show = () => {
this._titleTimerID = null;
this._titleControlRef.value?.show();
};
if (this._titleTimerID) {
window.clearTimeout(this._titleTimerID);
}
if (this._titleControlRef.value?.isVisible()) {
// If it's already visible, update it immediately (but also update it
// after the timer expires to ensure it re-positions if necessary, see
// comment below).
show();
}
// Allow a brief pause after the media loads, but before the title is
// displayed. This allows for a pleasant appearance/disappear of the title,
// and allows for the browser to finish rendering the carousel (inc.
// adaptive height which has `0.5s ease`, see `media-carousel.scss`).
this._titleTimerID = window.setTimeout(show, 0.5 * 1000);
}
/**
* Component connected callback.
*/
@@ -43,6 +71,7 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
super.connectedCallback();
this.addEventListener('frigate-card:media-show', this._autoplayHandler);
this.addEventListener('frigate-card:media-show', this._adaptiveHeightHandler);
this.addEventListener('frigate-card:media-show', this._titleHandler);
}
/**
@@ -52,6 +81,7 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
super.disconnectedCallback();
this.removeEventListener('frigate-card:media-show', this._autoplayHandler);
this.removeEventListener('frigate-card:media-show', this._adaptiveHeightHandler);
this.removeEventListener('frigate-card:media-show', this._titleHandler);
}
protected _destroyCarousel(): void {
@@ -93,7 +123,7 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
* actually the media load/show that will change the dimensions, and that is
* async from carousel actions (e.g. lazy-loaded media).
*/
protected _adaptiveHeightHandler(): void {
protected _adaptiveHeightHandler(): void {
const adaptCarouselHeight = (): void => {
if (!this._carousel) {
return;
+85
View File
@@ -0,0 +1,85 @@
import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit';
import { createRef, ref, Ref } from 'lit/directives/ref';
import { customElement, property } from 'lit/decorators.js';
import { TitleControlConfig } from '../types.js';
import titleStyle from '../scss/title-control.scss';
type PaperToast = HTMLElement & {
opened: boolean;
};
@customElement('frigate-card-title-control')
export class FrigateCardTitleControl extends LitElement {
@property({ attribute: false })
public config?: TitleControlConfig;
@property({ attribute: false })
public text?: string;
@property({ attribute: false })
public fitInto?: HTMLElement;
protected _toastRef: Ref<PaperToast> = createRef();
/**
* Master render method.
* @returns A rendered template.
*/
protected render(): TemplateResult {
if (!this.text || !this.config || this.config.mode == 'none' || !this.fitInto) {
return html``;
}
const verticalAlign = this.config.mode.match(/-top-/) ? 'top' : 'bottom';
const horizontalAlign = this.config.mode.match(/-left$/) ? 'left' : 'right';
return html` <paper-toast
${ref(this._toastRef)}
class="capsule"
.duration=${this.config.duration_seconds * 1000}
.verticalAlign=${verticalAlign}
.horizontalAlign=${horizontalAlign}
.text="${this.text}"
.fitInto=${this.fitInto}
>
</paper-toast>`;
}
/**
* Determine if the toast is visible.
* @returns `true` if the toast is visible, `false` otherwise.
*/
public isVisible(): boolean {
return this._toastRef.value?.opened ?? false;
}
/**
* Show the toast.
*/
public hide(): void {
if (this._toastRef.value) {
// Set it to false first, to ensure the timer resets.
this._toastRef.value.opened = false;
}
}
/**
* Show the toast.
*/
public show(): void {
if (this._toastRef.value) {
// Set it to false first, to ensure the timer resets.
this._toastRef.value.opened = false;
this._toastRef.value.opened = true;
}
}
/**
* Get element styles.
*/
static get styles(): CSSResultGroup {
return unsafeCSS(titleStyle);
}
}
+35 -25
View File
@@ -41,6 +41,7 @@ import {
import { renderProgressIndicator } from '../components/message.js';
import './next-prev-control.js';
import './title-control.js';
import viewerStyle from '../scss/viewer.scss';
import viewerCoreStyle from '../scss/viewer-core.scss';
@@ -628,32 +629,41 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
const [prev, next] = [neighbors?.previous, neighbors?.next];
return html`<div class="embla">
<frigate-card-next-previous-control
${ref(this._previousControlRef)}
.direction=${'previous'}
.controlConfig=${this.viewerConfig?.controls.next_previous}
.thumbnail=${prev && prev.thumbnail ? prev.thumbnail : undefined}
.label=${prev ? prev.title : ''}
?disabled=${!prev}
@click=${() => {
this._nextPreviousHandler('previous');
}}
></frigate-card-next-previous-control>
<div class="embla__viewport">
<div class="embla__container">${slides}</div>
<frigate-card-next-previous-control
${ref(this._previousControlRef)}
.direction=${'previous'}
.controlConfig=${this.viewerConfig?.controls.next_previous}
.thumbnail=${prev && prev.thumbnail ? prev.thumbnail : undefined}
.label=${prev ? prev.title : ''}
?disabled=${!prev}
@click=${() => {
this._nextPreviousHandler('previous');
}}
></frigate-card-next-previous-control>
<div class="embla__viewport">
<div class="embla__container">${slides}</div>
</div>
<frigate-card-next-previous-control
${ref(this._nextControlRef)}
.direction=${'next'}
.controlConfig=${this.viewerConfig?.controls.next_previous}
.thumbnail=${next && next.thumbnail ? next.thumbnail : undefined}
.label=${next ? next.title : ''}
?disabled=${!next}
@click=${() => {
this._nextPreviousHandler('next');
}}
></frigate-card-next-previous-control>
</div>
<frigate-card-next-previous-control
${ref(this._nextControlRef)}
.direction=${'next'}
.controlConfig=${this.viewerConfig?.controls.next_previous}
.thumbnail=${next && next.thumbnail ? next.thumbnail : undefined}
.label=${next ? next.title : ''}
?disabled=${!next}
@click=${() => {
this._nextPreviousHandler('next');
}}
></frigate-card-next-previous-control>
</div>`;
${this.view?.media
? html` <frigate-card-title-control
${ref(this._titleControlRef)}
.config=${this.viewerConfig?.controls.title}
.text="${this.view.media.title}"
.fitInto=${this as HTMLElement}
>
</frigate-card-title-control>`
: ``} `;
}
protected _renderMediaItem(