From 0a43d6aad59e2eef9a76f223d79d735e66dc9823 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sat, 15 Oct 2022 15:45:43 -0700 Subject: [PATCH] Fix carousel previous/next controls not showing. --- src/components/carousel.ts | 32 ++++++------- src/components/media-carousel.ts | 32 +++++-------- src/components/viewer.ts | 78 ++++++++++++++++++-------------- 3 files changed, 70 insertions(+), 72 deletions(-) diff --git a/src/components/carousel.ts b/src/components/carousel.ts index 2269610c..8199eb7c 100644 --- a/src/components/carousel.ts +++ b/src/components/carousel.ts @@ -163,24 +163,21 @@ export class FrigateCardCarousel extends LitElement { 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. */ 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(); - this._carouselReInit({ + carouselReInit({ ...(selected && { startIndex: selected.index }), }); } @@ -252,12 +249,11 @@ export class FrigateCardCarousel extends LitElement { axis: this.direction == 'horizontal' ? 'x' : 'y', speed: 20, ...this.carouselOptions, - ...(this._savedStartIndex && { startIndex: this._savedStartIndex }), + ...(this._savedStartIndex !== null && { startIndex: this._savedStartIndex }), }, this.carouselPlugins, ); - this._carousel.on('init', () => dispatchFrigateCardEvent(this, 'carousel:init')); - this._carousel.on('select', () => { + const selectSlide = (): void => { const selected = this.getCarouselSelected(); if (selected) { dispatchFrigateCardEvent(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 // next/previous controls. this.requestUpdate(); - }); + } + this._carousel.on('init', selectSlide); + this._carousel.on('select', selectSlide); this._carousel.on('scroll', () => { this._scrolling = true; }); @@ -305,7 +303,7 @@ export class FrigateCardCarousel extends LitElement { protected render(): TemplateResult | void { 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 showNext = this.carouselOptions?.loop || currentSlide + 1 < slides.length; diff --git a/src/components/media-carousel.ts b/src/components/media-carousel.ts index f4d97538..5679ab52 100644 --- a/src/components/media-carousel.ts +++ b/src/components/media-carousel.ts @@ -155,7 +155,6 @@ export class FrigateCardMediaCarousel extends LitElement { // This carousel may be resized by Lovelace resizes, window resizes, // fullscreen, etc. Always call the adaptive height handler when the size // changes. - protected _resizeObserver: ResizeObserver; protected _slideResizeObserver: ResizeObserver; 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 // resize or fullscreen change) and changes in the selected slide itself // (e.g. changing from a progress indicator to a loaded media). - this._resizeObserver = new ResizeObserver(this._reInitAndAdjustHeight.bind(this)); this._slideResizeObserver = new ResizeObserver( this._reInitAndAdjustHeight.bind(this), ); @@ -294,7 +292,6 @@ export class FrigateCardMediaCarousel extends LitElement { this._debouncedAdaptContainerHeightToSlide, ); this.removeEventListener('frigate-card:media:loaded', this._boundTitleHandler); - this._resizeObserver.disconnect(); this._intersectionObserver.disconnect(); this._mediaLoadedInfo = {}; @@ -397,28 +394,26 @@ export class FrigateCardMediaCarousel extends LitElement { } protected render(): TemplateResult | void { - const selectSlide = (ev?: CustomEvent): void => { + const selectSlide = (ev: CustomEvent): void => { this._slideResizeObserver.disconnect(); const parent = this.getRootNode(); if (parent && parent instanceof ShadowRoot) { this._slideResizeObserver.observe(parent.host); } - const selected = ev ? ev.detail : this.frigateCardCarousel()?.getCarouselSelected(); - if (selected) { - this._slideResizeObserver.observe(selected.element); + const selected = ev.detail; + this._slideResizeObserver.observe(selected.element); - // Pass up the media-carousel select event first to allow parents to - // initialize/reset before the media info is dispatched. - dispatchFrigateCardEvent( - this, - 'media-carousel:select', - selected, - ); + // Pass up the media-carousel select event first to allow parents to + // initialize/reset before the media info is dispatched. + dispatchFrigateCardEvent( + this, + 'media-carousel:select', + selected, + ); - // Dispatch media info. - this._dispatchMediaLoadedInfo(selected); - } + // Dispatch media info. + this._dispatchMediaLoadedInfo(selected); } return html` { - selectSlide(); - }} @frigate-card:carousel:select=${(ev: CustomEvent) => { selectSlide(ev); }} diff --git a/src/components/viewer.ts b/src/components/viewer.ts index ec3e0c1e..156a21ac 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -9,7 +9,6 @@ import { TemplateResult, unsafeCSS, } from 'lit'; -import { guard } from 'lit/directives/guard.js'; import { customElement, property } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { createRef, Ref, ref } from 'lit/directives/ref.js'; @@ -49,11 +48,11 @@ import { wrapRawMediaLoadedEventForCarousel, wrapMediaLoadedEventForCarousel, } from './media-carousel.js'; +import type { CarouselSelect } from './carousel.js'; import './next-prev-control.js'; import './title-control.js'; import '../patches/ha-hls-player'; import './surround.js'; -import { EmblaCarouselPlugins } from './carousel.js'; import { renderTask } from '../utils/task.js'; import { updateElementStyleFromMediaLayoutConfig } from '../utils/media-layout.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). protected _slideToChild: Record = {}; + protected _carouselOptions?: EmblaOptionsType; + protected _carouselPlugins?: EmblaPluginType[]; + // A task to resolve target media if lazy loading is disabled. protected _mediaResolutionTask = new Task< [FrigateBrowseMediaSource | null | undefined], @@ -516,24 +518,19 @@ export class FrigateCardViewerCarousel extends LitElement { /** * Handle the user selecting a new slide in the carousel. */ - protected _setViewHandler(): void { + protected _setViewHandler(ev: CustomEvent): void { if (!this._refMediaCarousel.value || !this.view) { return; } // Update the childIndex in the view. - const selected = this._refMediaCarousel.value - .frigateCardCarousel() - ?.getCarouselSelected()?.index; - if (selected !== undefined) { - const childIndex = this._slideToChild[selected]; - if (childIndex !== undefined) { - this.view - .evolve({ - childIndex: childIndex, - }) - .dispatchChangeEvent(this); - } + const childIndex = this._slideToChild[ev.detail.index]; + if (childIndex !== undefined) { + this.view + .evolve({ + childIndex: childIndex, + }) + .dispatchChangeEvent(this); } } @@ -596,30 +593,27 @@ export class FrigateCardViewerCarousel extends LitElement { /** * Get slides to include in the render. - * @returns The slides to include in the render and an index keyed by slide - * number that maps to child number. + * @returns The slides to include in the render. */ - protected _getSlides(): [TemplateResult[], Record] { + protected _getSlides(): TemplateResult[] { if ( !this.view || !this.view.target || !this.view.target.children || !this.view.target.children.length ) { - return [[], {}]; + return []; } - const slideToChild: Record = {}; const slides: TemplateResult[] = []; for (let i = 0; i < this.view.target.children?.length; ++i) { const slide = this._renderMediaItem(this.view.target.children[i], slides.length); if (slide) { - slideToChild[slides.length] = i; slides.push(slide); } } - return [slides, slideToChild]; + return slides; } /** @@ -639,17 +633,38 @@ export class FrigateCardViewerCarousel extends LitElement { * @param changedProps The changed properties */ 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')) { 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. */ protected render(): TemplateResult | void { - this._slideToChild = {}; - // If lazy loading is not enabled, wait for the media resolver task to // complete and show a progress indictator until this. if (!this.viewerConfig?.lazy_load && !this._isMediaFullyResolved()) { @@ -665,8 +680,8 @@ export class FrigateCardViewerCarousel extends LitElement { * @returns A template to display to the user. */ protected _render(): TemplateResult | void { - const [slides, slideToChild] = this._getSlides(); - this._slideToChild = slideToChild; + const slides = this._getSlides(); + if (!slides.length || !this.view?.media) { return; } @@ -674,17 +689,10 @@ export class FrigateCardViewerCarousel extends LitElement { const neighbors = this._getMediaNeighbors(); 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`