Refactor carousel into separate components.

This commit is contained in:
Dermot Duffy
2022-07-09 22:43:43 -07:00
parent 30fec98e18
commit dedecc24c6
18 changed files with 698 additions and 517 deletions
+104 -140
View File
@@ -11,18 +11,20 @@ import {
} from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { ref } from 'lit/directives/ref.js';
import { createRef, Ref, ref } from 'lit/directives/ref.js';
import {
dispatchFrigateCardErrorEvent,
renderProgressIndicator,
} from '../components/message.js';
import viewerStyle from '../scss/viewer.scss';
import type {
import viewerCarouselStyle from '../scss/viewer-carousel.scss';
import {
BrowseMediaNeighbors,
BrowseMediaQueryParameters,
CameraConfig,
ExtendedHomeAssistant,
FrigateBrowseMediaSource,
frigateCardConfigDefaults,
FrigateCardMediaPlayer,
MediaShowInfo,
TransitionEffect,
@@ -39,13 +41,16 @@ import {
overrideMultiBrowseMediaQueryParameters,
} from '../utils/ha/browse-media.js';
import { ResolvedMediaCache, resolveMedia } from '../utils/ha/resolved-media.js';
import { createMediaShowInfo } from '../utils/media-info.js';
import { View } from '../view.js';
import { AutoMediaPlugin } from './embla-plugins/automedia.js';
import { Lazyload } from './embla-plugins/lazyload.js';
import { FrigateCardMediaCarousel, IMG_EMPTY } from './media-carousel.js';
import {
FrigateCardMediaCarousel,
IMG_EMPTY,
wrapMediaLoadEventForCarousel,
wrapMediaShowEventForCarousel,
} from './media-carousel.js';
import './next-prev-control.js';
import { FrigateCardNextPreviousControl } from './next-prev-control.js';
import './title-control.js';
import '../patches/ha-hls-player';
import './surround-thumbnails';
@@ -133,7 +138,7 @@ export class FrigateCardViewer extends LitElement {
const FRIGATE_CARD_HLS_SELECTOR = 'frigate-card-ha-hls-player';
@customElement('frigate-card-viewer-carousel')
export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
export class FrigateCardViewerCarousel extends LitElement {
@property({ attribute: false })
public hass?: ExtendedHomeAssistant;
@@ -154,6 +159,8 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
@property({ attribute: false })
public resolvedMediaCache?: ResolvedMediaCache;
protected _refMediaCarousel: Ref<FrigateCardMediaCarousel> = createRef();
// Mapping of slide # to FrigateBrowseMediaSource child #.
// (Folders are not media items that can be rendered).
protected _slideToChild: Record<number, number> = {};
@@ -187,22 +194,20 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
* @param changedProperties The properties that were changed in this render.
*/
updated(changedProperties: PropertyValues): void {
if (this._carousel && changedProperties.has('viewerConfig')) {
this._destroyCarousel();
}
const frigateCardCarousel = this._refMediaCarousel.value?.frigateCardCarousel();
if (this._carousel && changedProperties.has('view')) {
if (frigateCardCarousel && changedProperties.has('view')) {
const oldView = changedProperties.get('view') as View | undefined;
if (oldView) {
if (oldView.target !== this.view?.target) {
// If the media target is different entirely, reset the carousel.
this._destroyCarousel();
} else if (this.view.childIndex != oldView.childIndex) {
if (
oldView.target === this.view?.target &&
this.view.childIndex != oldView.childIndex
) {
const slide = this._getSlideForChild(this.view.childIndex);
if (slide !== null && slide !== this.carouselSelected()) {
if (slide !== null && slide !== frigateCardCarousel.carouselSelected()) {
// If the media target is the same as already loaded, but isn't of
// the selected slide, scroll to that slide.
this.carouselScrollTo(slide);
frigateCardCarousel.carouselScrollTo(slide);
}
}
}
@@ -211,41 +216,6 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
super.updated(changedProperties);
}
/**
* Play the media on the loaded slide.
*/
protected _autoPlayHandler(): void {
if (
this.viewerConfig?.auto_play &&
['all', 'selected'].includes(this.viewerConfig.auto_play)
) {
super._autoPlayHandler();
}
}
/**
* Unmute the media on the loaded slide.
*/
protected _autoUnmuteHandler(): void {
if (
this.viewerConfig?.auto_unmute &&
['all', 'selected'].includes(this.viewerConfig.auto_unmute)
) {
super._autoUnmuteHandler();
}
}
/**
* Destroy the carousel.
*/
protected _destroyCarousel(): void {
super._destroyCarousel();
// Notes on instance variables:
// * this._slideToChild: This is set as part of each render and does not
// need to be destroyed here.
}
/**
* Get the slide number given a media child number.
* @param childIndex The child index (relative to `view.target`)
@@ -265,8 +235,11 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
* Get the transition effect to use.
* @returns An TransitionEffect object.
*/
protected _getTransitionEffect(): TransitionEffect | undefined {
return this.viewerConfig?.transition_effect;
protected _getTransitionEffect(): TransitionEffect {
return (
this.viewerConfig?.transition_effect ??
frigateCardConfigDefaults.media_viewer.transition_effect
);
}
/**
@@ -286,16 +259,16 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
* @param slide An optional slide.
* @returns The FrigateCardMediaPlayer or null if not found.
*/
protected _getPlayer(slide?: HTMLElement): FrigateCardMediaPlayer | null {
if (this._carousel) {
if (!slide) {
slide = this._carousel.slideNodes()[this._carousel.selectedScrollSnap()];
}
return slide?.querySelector(
FRIGATE_CARD_HLS_SELECTOR,
) as FrigateCardMediaPlayer | null;
protected _getPlayer(slide?: HTMLElement | null): FrigateCardMediaPlayer | null {
if (!slide) {
slide = this._refMediaCarousel.value
?.frigateCardCarousel()
?.carouselSelectedElement();
}
return null;
return (
(slide?.querySelector(FRIGATE_CARD_HLS_SELECTOR) as FrigateCardMediaPlayer) ?? null
);
}
/**
@@ -304,7 +277,6 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
*/
protected _getPlugins(): EmblaPluginType[] {
return [
...super._getPlugins(),
// Only enable wheel plugin if there is more than one media item.
...(this.view &&
this.view.target &&
@@ -480,15 +452,17 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
/**
* Handle the user selecting a new slide in the carousel.
*/
protected _selectSlideSetViewHandler(): void {
if (!this._carousel || !this.view) {
protected _setViewHandler(): void {
if (!this._refMediaCarousel.value || !this.view) {
return;
}
// Update the childIndex in the view.
const slidesInView = this._carousel.slidesInView(true);
if (slidesInView.length) {
const childIndex = this._slideToChild[slidesInView[0]];
const selected = this._refMediaCarousel.value
.frigateCardCarousel()
?.carouselSelected();
if (selected !== undefined) {
const childIndex = this._slideToChild[selected];
if (childIndex !== undefined) {
this.view
.evolve({
@@ -556,31 +530,6 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
});
}
/**
* Handle updating of the next/previous controls when the carousel is moved.
*/
protected _selectSlideNextPreviousHandler(): void {
const updateNextPreviousControl = (
control: FrigateCardNextPreviousControl,
direction: 'previous' | 'next',
): void => {
const neighbors = this._getMediaNeighbors();
const [prev, next] = [neighbors?.previous, neighbors?.next];
const target = direction == 'previous' ? prev : next;
control.disabled = target == null;
control.title = target && target.title ? target.title : '';
control.thumbnail = target && target.thumbnail ? target.thumbnail : undefined;
};
if (this._previousControlRef.value) {
updateNextPreviousControl(this._previousControlRef.value, 'previous');
}
if (this._nextControlRef.value) {
updateNextPreviousControl(this._nextControlRef.value, 'next');
}
}
/**
* Get slides to include in the render.
* @returns The slides to include in the render and an index keyed by slide
@@ -647,59 +596,59 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
protected _render(): TemplateResult | void {
const [slides, slideToChild] = this._getSlides();
this._slideToChild = slideToChild;
if (!slides.length) {
if (!slides.length || !this.view?.media) {
return;
}
const neighbors = this._getMediaNeighbors();
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=${(ev) => {
this._nextPreviousHandler('previous');
stopEventFromActivatingCardWideActions(ev);
}}
></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=${(ev) => {
this._nextPreviousHandler('next');
stopEventFromActivatingCardWideActions(ev);
}}
></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>`
: ``} `;
return html` <frigate-card-media-carousel
${ref(this._refMediaCarousel)}
.carouselOptions=${this._getOptions()}
.carouselPlugins=${this._getPlugins()}
.autoPlayCondition=${this.viewerConfig?.auto_play}
.autoPauseCondition=${this.viewerConfig?.auto_pause}
.autoMuteCondition=${this.viewerConfig?.auto_mute}
.autoUnmuteCondition=${this.viewerConfig?.auto_unmute}
.label="${this.view.media.title}"
.titlePopupConfig=${this.viewerConfig?.controls.title}
transitionEffect=${this._getTransitionEffect()}
@frigate-card:carousel:select=${this._setViewHandler.bind(this)}
@frigate-card:media-show=${this._recordingSeekHandler.bind(this)}
>
<frigate-card-next-previous-control
slot="previous"
.direction=${'previous'}
.controlConfig=${this.viewerConfig?.controls.next_previous}
.thumbnail=${prev && prev.thumbnail ? prev.thumbnail : undefined}
.label=${prev ? prev.title : ''}
?disabled=${!prev}
@click=${(ev) => {
this._refMediaCarousel.value?.frigateCardCarousel()?.carouselScrollPrevious();
stopEventFromActivatingCardWideActions(ev);
}}
></frigate-card-next-previous-control>
${slides}
<frigate-card-next-previous-control
slot="next"
.direction=${'next'}
.controlConfig=${this.viewerConfig?.controls.next_previous}
.thumbnail=${next && next.thumbnail ? next.thumbnail : undefined}
.label=${next ? next.title : ''}
?disabled=${!next}
@click=${(ev) => {
this._refMediaCarousel.value?.frigateCardCarousel()?.carouselScrollNext();
stopEventFromActivatingCardWideActions(ev);
}}
></frigate-card-next-previous-control>
</frigate-card-media-carousel>`;
}
/**
* Fire a media show event when a slide is selected.
*/
protected _selectSlideMediaShowHandler(): void {
super._selectSlideMediaShowHandler();
protected _recordingSeekHandler(): void {
// If this is a recording and play is desired to be started from a
// particular point, seek to that point. Use the media off the slide itself
// -- when the slide is changed, the media show event may be dispatched
@@ -751,8 +700,9 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
)}
.media=${mediaToRender}
.hass=${this.hass}
@frigate-card:media-show=${(e: CustomEvent<MediaShowInfo>) =>
this._mediaShowEventHandler(slideIndex, e)}
@frigate-card:media-show=${(e: CustomEvent<MediaShowInfo>) => {
wrapMediaShowEventForCarousel(slideIndex, e);
}}
>
</frigate-card-ha-hls-player>`
: html`<img
@@ -762,7 +712,11 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
)}
title="${mediaToRender.title}"
@click=${() => {
if (this._carousel?.clickAllowed()) {
if (
this._refMediaCarousel.value
?.frigateCardCarousel()
?.carouselClickAllowed()
) {
this._findRelatedClipView(mediaToRender).then((view) => {
if (view) {
view.dispatchChangeEvent(this);
@@ -771,6 +725,9 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
}
}}
@load="${(e: Event) => {
const lazyloadPlugin = this._refMediaCarousel.value
?.frigateCardCarousel()
?.getCarouselPlugins()?.lazyload;
if (
// This handler will be called on the empty image (including
// an updated empty image that is the same dimensions large as
@@ -778,15 +735,22 @@ export class FrigateCardViewerCarousel extends FrigateCardMediaCarousel {
// images in media-carousel.ts). Here we need to only call the
// media load handler on a 'real' load.
!lazyLoad ||
this._getLazyLoadPlugin()?.hasLazyloaded(slideIndex)
lazyloadPlugin?.hasLazyloaded(slideIndex)
) {
this._mediaLoadedHandler(slideIndex, createMediaShowInfo(e));
wrapMediaLoadEventForCarousel(slideIndex, e);
}
}}"
/>`}
</div>
`;
}
/**
* Get element styles.
*/
static get styles(): CSSResultGroup {
return unsafeCSS(viewerCarouselStyle);
}
}
declare global {