Merge pull request #875 from dermotduffy/carousel-issues

Fix carousel previous/next controls not showing
This commit is contained in:
Dermot Duffy
2022-10-16 12:24:33 -07:00
committed by GitHub
3 changed files with 70 additions and 72 deletions
+15 -17
View File
@@ -163,24 +163,21 @@ export class FrigateCardCarousel extends LitElement {
return this._carousel ?? null; return this._carousel ?? null;
} }
/**
* ReInit the carousel.
*/
protected _carouselReInit(options?: EmblaOptionsType): void {
// Allow the browser a moment to paint components that are inflight, to
// ensure accurate measurements are taken during the carousel
// reinitialization.
window.requestAnimationFrame(() => {
this._carousel?.reInit({ ...options });
});
}
/** /**
* ReInit the carousel but stay on the current slide. * ReInit the carousel but stay on the current slide.
*/ */
protected _carouselReInitInPlaceInternal(): void { protected _carouselReInitInPlaceInternal(): void {
const carouselReInit = (options?: EmblaOptionsType): void => {
// Allow the browser a moment to paint components that are inflight, to
// ensure accurate measurements are taken during the carousel
// reinitialization.
window.requestAnimationFrame(() => {
this._carousel?.reInit({ ...options });
});
}
const selected = this.getCarouselSelected(); const selected = this.getCarouselSelected();
this._carouselReInit({ carouselReInit({
...(selected && { startIndex: selected.index }), ...(selected && { startIndex: selected.index }),
}); });
} }
@@ -252,12 +249,11 @@ export class FrigateCardCarousel extends LitElement {
axis: this.direction == 'horizontal' ? 'x' : 'y', axis: this.direction == 'horizontal' ? 'x' : 'y',
speed: 20, speed: 20,
...this.carouselOptions, ...this.carouselOptions,
...(this._savedStartIndex && { startIndex: this._savedStartIndex }), ...(this._savedStartIndex !== null && { startIndex: this._savedStartIndex }),
}, },
this.carouselPlugins, this.carouselPlugins,
); );
this._carousel.on('init', () => dispatchFrigateCardEvent(this, 'carousel:init')); const selectSlide = (): void => {
this._carousel.on('select', () => {
const selected = this.getCarouselSelected(); const selected = this.getCarouselSelected();
if (selected) { if (selected) {
dispatchFrigateCardEvent<CarouselSelect>(this, 'carousel:select', selected); dispatchFrigateCardEvent<CarouselSelect>(this, 'carousel:select', selected);
@@ -266,8 +262,10 @@ export class FrigateCardCarousel extends LitElement {
// Make sure every select causes a refresh to allow for re-paint of the // Make sure every select causes a refresh to allow for re-paint of the
// next/previous controls. // next/previous controls.
this.requestUpdate(); this.requestUpdate();
}); }
this._carousel.on('init', selectSlide);
this._carousel.on('select', selectSlide);
this._carousel.on('scroll', () => { this._carousel.on('scroll', () => {
this._scrolling = true; this._scrolling = true;
}); });
@@ -305,7 +303,7 @@ export class FrigateCardCarousel extends LitElement {
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
const slides = this._refSlot.value?.assignedElements({ flatten: true }) || []; const slides = this._refSlot.value?.assignedElements({ flatten: true }) || [];
const currentSlide = this._carousel?.selectedScrollSnap() ?? 0; const currentSlide = (this._carousel?.selectedScrollSnap() ?? this.carouselOptions?.startIndex) ?? 0;
const showPrevious = this.carouselOptions?.loop || currentSlide > 0; const showPrevious = this.carouselOptions?.loop || currentSlide > 0;
const showNext = this.carouselOptions?.loop || currentSlide + 1 < slides.length; const showNext = this.carouselOptions?.loop || currentSlide + 1 < slides.length;
+12 -20
View File
@@ -155,7 +155,6 @@ export class FrigateCardMediaCarousel extends LitElement {
// This carousel may be resized by Lovelace resizes, window resizes, // This carousel may be resized by Lovelace resizes, window resizes,
// fullscreen, etc. Always call the adaptive height handler when the size // fullscreen, etc. Always call the adaptive height handler when the size
// changes. // changes.
protected _resizeObserver: ResizeObserver;
protected _slideResizeObserver: ResizeObserver; protected _slideResizeObserver: ResizeObserver;
protected _intersectionObserver: IntersectionObserver; protected _intersectionObserver: IntersectionObserver;
@@ -166,7 +165,6 @@ export class FrigateCardMediaCarousel extends LitElement {
// Need to watch both changes in this element (e.g. caused by a window // Need to watch both changes in this element (e.g. caused by a window
// resize or fullscreen change) and changes in the selected slide itself // resize or fullscreen change) and changes in the selected slide itself
// (e.g. changing from a progress indicator to a loaded media). // (e.g. changing from a progress indicator to a loaded media).
this._resizeObserver = new ResizeObserver(this._reInitAndAdjustHeight.bind(this));
this._slideResizeObserver = new ResizeObserver( this._slideResizeObserver = new ResizeObserver(
this._reInitAndAdjustHeight.bind(this), this._reInitAndAdjustHeight.bind(this),
); );
@@ -294,7 +292,6 @@ export class FrigateCardMediaCarousel extends LitElement {
this._debouncedAdaptContainerHeightToSlide, this._debouncedAdaptContainerHeightToSlide,
); );
this.removeEventListener('frigate-card:media:loaded', this._boundTitleHandler); this.removeEventListener('frigate-card:media:loaded', this._boundTitleHandler);
this._resizeObserver.disconnect();
this._intersectionObserver.disconnect(); this._intersectionObserver.disconnect();
this._mediaLoadedInfo = {}; this._mediaLoadedInfo = {};
@@ -397,28 +394,26 @@ export class FrigateCardMediaCarousel extends LitElement {
} }
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
const selectSlide = (ev?: CustomEvent<CarouselSelect>): void => { const selectSlide = (ev: CustomEvent<CarouselSelect>): void => {
this._slideResizeObserver.disconnect(); this._slideResizeObserver.disconnect();
const parent = this.getRootNode(); const parent = this.getRootNode();
if (parent && parent instanceof ShadowRoot) { if (parent && parent instanceof ShadowRoot) {
this._slideResizeObserver.observe(parent.host); this._slideResizeObserver.observe(parent.host);
} }
const selected = ev ? ev.detail : this.frigateCardCarousel()?.getCarouselSelected(); const selected = ev.detail;
if (selected) { this._slideResizeObserver.observe(selected.element);
this._slideResizeObserver.observe(selected.element);
// Pass up the media-carousel select event first to allow parents to // Pass up the media-carousel select event first to allow parents to
// initialize/reset before the media info is dispatched. // initialize/reset before the media info is dispatched.
dispatchFrigateCardEvent<CarouselSelect>( dispatchFrigateCardEvent<CarouselSelect>(
this, this,
'media-carousel:select', 'media-carousel:select',
selected, selected,
); );
// Dispatch media info. // Dispatch media info.
this._dispatchMediaLoadedInfo(selected); this._dispatchMediaLoadedInfo(selected);
}
} }
return html` <frigate-card-carousel return html` <frigate-card-carousel
@@ -426,9 +421,6 @@ export class FrigateCardMediaCarousel extends LitElement {
.carouselOptions=${this.carouselOptions} .carouselOptions=${this.carouselOptions}
.carouselPlugins=${this.carouselPlugins} .carouselPlugins=${this.carouselPlugins}
transitionEffect=${ifDefined(this.transitionEffect)} transitionEffect=${ifDefined(this.transitionEffect)}
@frigate-card:carousel:init=${() => {
selectSlide();
}}
@frigate-card:carousel:select=${(ev: CustomEvent<CarouselSelect>) => { @frigate-card:carousel:select=${(ev: CustomEvent<CarouselSelect>) => {
selectSlide(ev); selectSlide(ev);
}} }}
+43 -35
View File
@@ -9,7 +9,6 @@ import {
TemplateResult, TemplateResult,
unsafeCSS, unsafeCSS,
} from 'lit'; } from 'lit';
import { guard } from 'lit/directives/guard.js';
import { customElement, property } from 'lit/decorators.js'; import { customElement, property } from 'lit/decorators.js';
import { ifDefined } from 'lit/directives/if-defined.js'; import { ifDefined } from 'lit/directives/if-defined.js';
import { createRef, Ref, ref } from 'lit/directives/ref.js'; import { createRef, Ref, ref } from 'lit/directives/ref.js';
@@ -49,11 +48,11 @@ import {
wrapRawMediaLoadedEventForCarousel, wrapRawMediaLoadedEventForCarousel,
wrapMediaLoadedEventForCarousel, wrapMediaLoadedEventForCarousel,
} from './media-carousel.js'; } from './media-carousel.js';
import type { CarouselSelect } from './carousel.js';
import './next-prev-control.js'; import './next-prev-control.js';
import './title-control.js'; import './title-control.js';
import '../patches/ha-hls-player'; import '../patches/ha-hls-player';
import './surround.js'; import './surround.js';
import { EmblaCarouselPlugins } from './carousel.js';
import { renderTask } from '../utils/task.js'; import { renderTask } from '../utils/task.js';
import { updateElementStyleFromMediaLayoutConfig } from '../utils/media-layout.js'; import { updateElementStyleFromMediaLayoutConfig } from '../utils/media-layout.js';
import { DataManager } from '../utils/data-manager.js'; import { DataManager } from '../utils/data-manager.js';
@@ -220,6 +219,9 @@ export class FrigateCardViewerCarousel extends LitElement {
// (Folders are not media items that can be rendered). // (Folders are not media items that can be rendered).
protected _slideToChild: Record<number, number> = {}; protected _slideToChild: Record<number, number> = {};
protected _carouselOptions?: EmblaOptionsType;
protected _carouselPlugins?: EmblaPluginType[];
// A task to resolve target media if lazy loading is disabled. // A task to resolve target media if lazy loading is disabled.
protected _mediaResolutionTask = new Task< protected _mediaResolutionTask = new Task<
[FrigateBrowseMediaSource | null | undefined], [FrigateBrowseMediaSource | null | undefined],
@@ -516,24 +518,19 @@ export class FrigateCardViewerCarousel extends LitElement {
/** /**
* Handle the user selecting a new slide in the carousel. * Handle the user selecting a new slide in the carousel.
*/ */
protected _setViewHandler(): void { protected _setViewHandler(ev: CustomEvent<CarouselSelect>): void {
if (!this._refMediaCarousel.value || !this.view) { if (!this._refMediaCarousel.value || !this.view) {
return; return;
} }
// Update the childIndex in the view. // Update the childIndex in the view.
const selected = this._refMediaCarousel.value const childIndex = this._slideToChild[ev.detail.index];
.frigateCardCarousel() if (childIndex !== undefined) {
?.getCarouselSelected()?.index; this.view
if (selected !== undefined) { .evolve({
const childIndex = this._slideToChild[selected]; childIndex: childIndex,
if (childIndex !== undefined) { })
this.view .dispatchChangeEvent(this);
.evolve({
childIndex: childIndex,
})
.dispatchChangeEvent(this);
}
} }
} }
@@ -596,30 +593,27 @@ export class FrigateCardViewerCarousel extends LitElement {
/** /**
* Get slides to include in the render. * Get slides to include in the render.
* @returns The slides to include in the render and an index keyed by slide * @returns The slides to include in the render.
* number that maps to child number.
*/ */
protected _getSlides(): [TemplateResult[], Record<number, number>] { protected _getSlides(): TemplateResult[] {
if ( if (
!this.view || !this.view ||
!this.view.target || !this.view.target ||
!this.view.target.children || !this.view.target.children ||
!this.view.target.children.length !this.view.target.children.length
) { ) {
return [[], {}]; return [];
} }
const slideToChild: Record<number, number> = {};
const slides: TemplateResult[] = []; const slides: TemplateResult[] = [];
for (let i = 0; i < this.view.target.children?.length; ++i) { for (let i = 0; i < this.view.target.children?.length; ++i) {
const slide = this._renderMediaItem(this.view.target.children[i], slides.length); const slide = this._renderMediaItem(this.view.target.children[i], slides.length);
if (slide) { if (slide) {
slideToChild[slides.length] = i;
slides.push(slide); slides.push(slide);
} }
} }
return [slides, slideToChild]; return slides;
} }
/** /**
@@ -639,17 +633,38 @@ export class FrigateCardViewerCarousel extends LitElement {
* @param changedProps The changed properties * @param changedProps The changed properties
*/ */
protected willUpdate(changedProps: PropertyValues): void { protected willUpdate(changedProps: PropertyValues): void {
// Pre-populate a map between real media slides and view child indicies.
if (changedProps.has('view')) {
this._slideToChild = {};
let i = 0;
(this.view?.target?.children ?? []).forEach((child, index) => {
if (isTrueMedia(child) && ['video', 'image'].includes(child.media_content_type)) {
this._slideToChild[i++] = index;
}
})
}
if (changedProps.has('viewerConfig')) { if (changedProps.has('viewerConfig')) {
updateElementStyleFromMediaLayoutConfig(this, this.viewerConfig?.layout); updateElementStyleFromMediaLayoutConfig(this, this.viewerConfig?.layout);
} }
if (!this._carouselOptions || changedProps.has('viewerConfig')) {
this._carouselOptions = this._getOptions();
}
if (
!this._carouselPlugins ||
changedProps.has('viewerConfig') ||
(changedProps.has('view') &&
this.view?.target?.children?.length !==
changedProps.get('view')?.target?.children?.length)
) {
this._carouselPlugins = this._getPlugins();
}
} }
/** /**
* Render the element, resolving the media first if necessary. * Render the element, resolving the media first if necessary.
*/ */
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
this._slideToChild = {};
// If lazy loading is not enabled, wait for the media resolver task to // If lazy loading is not enabled, wait for the media resolver task to
// complete and show a progress indictator until this. // complete and show a progress indictator until this.
if (!this.viewerConfig?.lazy_load && !this._isMediaFullyResolved()) { if (!this.viewerConfig?.lazy_load && !this._isMediaFullyResolved()) {
@@ -665,8 +680,8 @@ export class FrigateCardViewerCarousel extends LitElement {
* @returns A template to display to the user. * @returns A template to display to the user.
*/ */
protected _render(): TemplateResult | void { protected _render(): TemplateResult | void {
const [slides, slideToChild] = this._getSlides(); const slides = this._getSlides();
this._slideToChild = slideToChild;
if (!slides.length || !this.view?.media) { if (!slides.length || !this.view?.media) {
return; return;
} }
@@ -674,17 +689,10 @@ export class FrigateCardViewerCarousel extends LitElement {
const neighbors = this._getMediaNeighbors(); const neighbors = this._getMediaNeighbors();
const [prev, next] = [neighbors?.previous, neighbors?.next]; const [prev, next] = [neighbors?.previous, neighbors?.next];
// Notes on the below:
// - guard() is used to avoid reseting the carousel unless the
// options/plugins actually change.
return html` <frigate-card-media-carousel return html` <frigate-card-media-carousel
${ref(this._refMediaCarousel)} ${ref(this._refMediaCarousel)}
.carouselOptions=${guard([this.viewerConfig], this._getOptions.bind(this))} .carouselOptions=${this._carouselOptions}
.carouselPlugins=${guard( .carouselPlugins=${this._carouselPlugins}
[this.viewerConfig, this.view?.target?.children?.length],
this._getPlugins.bind(this),
) as EmblaCarouselPlugins}
.label="${this.view.media.title}" .label="${this.view.media.title}"
.titlePopupConfig=${this.viewerConfig?.controls.title} .titlePopupConfig=${this.viewerConfig?.controls.title}
transitionEffect=${this._getTransitionEffect()} transitionEffect=${this._getTransitionEffect()}