From 1a4d38e609ad134404e8f08f1463f04776710bff Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Tue, 5 Sep 2023 20:39:54 -0700 Subject: [PATCH] Fix init/select out of sync issues. --- src/components/carousel.ts | 4 +--- src/components/live/live.ts | 4 +++- src/components/viewer.ts | 31 +++++++++++++++----------- src/utils/embla/carousel-controller.ts | 10 ++++----- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/components/carousel.ts b/src/components/carousel.ts index 1b83b78c..b5a2dc68 100644 --- a/src/components/carousel.ts +++ b/src/components/carousel.ts @@ -102,9 +102,7 @@ export class FrigateCardCarousel extends LitElement { plugins: this.plugins, }, ); - } - - if (changedProps.has('selected')) { + } else if (changedProps.has('selected')) { this._carousel?.selectSlide(this.selected); } } diff --git a/src/components/live/live.ts b/src/components/live/live.ts index 25bfd7a0..f68bdbdc 100644 --- a/src/components/live/live.ts +++ b/src/components/live/live.ts @@ -434,7 +434,9 @@ export class FrigateCardLiveCarousel extends LitElement { protected _getSelectedCameraIndex(): number { const cameraIDs = this.cameraManager?.getStore().getVisibleCameraIDs(); - if (!cameraIDs || !this.view) { + if (!cameraIDs || !this.view || this.viewFilterCameraID) { + // If the carousel is limited to a single cameraID, the first (only) + // element is always the selected one. return 0; } return Math.max(0, Array.from(cameraIDs).indexOf(this.view.camera)); diff --git a/src/components/viewer.ts b/src/components/viewer.ts index 1b01f5bd..a676a3ae 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -6,7 +6,7 @@ import { TemplateResult, unsafeCSS, } from 'lit'; -import { customElement, property } from 'lit/decorators.js'; +import { customElement, property, state } from 'lit/decorators.js'; import { guard } from 'lit/directives/guard.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { createRef, Ref, ref } from 'lit/directives/ref.js'; @@ -223,8 +223,8 @@ export class FrigateCardViewerCarousel extends LitElement { @property({ attribute: false }) public cameraManager?: CameraManager; - @property({ attribute: false }) - public selected = 0; + @state() + protected _selected = 0; protected _media: ViewMedia[] | null = null; protected _titleTimer = new Timer(); @@ -298,12 +298,12 @@ export class FrigateCardViewerCarousel extends LitElement { */ protected _getMediaNeighbors(): MediaNeighbors | null { const mediaCount = this._media?.length ?? 0; - if (!this._media || this.selected === null) { + if (!this._media) { return null; } - const prevIndex = this.selected > 0 ? this.selected - 1 : null; - const nextIndex = this.selected + 1 < mediaCount ? this.selected + 1 : null; + const prevIndex = this._selected > 0 ? this._selected - 1 : null; + const nextIndex = this._selected + 1 < mediaCount ? this._selected + 1 : null; return { ...(prevIndex !== null && { previous: { @@ -325,7 +325,7 @@ export class FrigateCardViewerCarousel extends LitElement { return; } - if (this.selected === null || this.selected === index) { + if (this._selected === index) { // The slide may already be selected on load, so don't dispatch a new view // unless necessary (i.e. the new index is different from the current // index). @@ -408,10 +408,10 @@ export class FrigateCardViewerCarousel extends LitElement { this.view?.queryResults?.getSelectedIndex(this.viewFilterCameraID) ?? 0; const newSeek = this.view?.context?.mediaViewer?.seek; - if (newMedia !== this._media || newSelected !== this.selected || !newSeek) { + if (newMedia !== this._media || newSelected !== this._selected || !newSeek) { setOrRemoveAttribute(this, false, 'unseekable'); this._media = newMedia; - this.selected = newSelected; + this._selected = newSelected; } } } @@ -427,7 +427,7 @@ export class FrigateCardViewerCarousel extends LitElement { // If there's no selected media, just choose the last (most recent one) to // avoid rendering a blank. This situation should not occur in practice, as // this view should not be called without a selected media. - const selectedMedia = this._media[this.selected] ?? this._media[mediaCount - 1]; + const selectedMedia = this._media[this._selected] ?? this._media[mediaCount - 1]; if (!this.hass || !this.cameraManager || !selectedMedia) { return; @@ -460,7 +460,7 @@ export class FrigateCardViewerCarousel extends LitElement { ) => { this._setViewSelectedIndex(ev.detail.index); @@ -526,10 +526,15 @@ export class FrigateCardViewerCarousel extends LitElement { */ protected async _seekHandler(): Promise { const seek = this.view?.context?.mediaViewer?.seek; - if (!this.hass || !seek || !this._media || this.selected === null || !this._player) { + if ( + !this.hass || + !seek || + !this._media || + !this._player + ) { return; } - const selectedMedia = this._media[this.selected]; + const selectedMedia = this._media[this._selected]; if (!selectedMedia) { return; } diff --git a/src/utils/embla/carousel-controller.ts b/src/utils/embla/carousel-controller.ts index acfb4849..b80f13c8 100644 --- a/src/utils/embla/carousel-controller.ts +++ b/src/utils/embla/carousel-controller.ts @@ -137,10 +137,11 @@ export class CarouselController { ); const getCarouselSelectedObject = (): CarouselSelected | null => { - const selectedIndex = this.getSelectedIndex(); - const slide = this.getSlide(selectedIndex); - - if (selectedIndex !== null && slide) { + // Caution: Must use methods/accessors of the new carousel, not the public + // API of this controller which may use a different carousel. + const selectedIndex = carousel.selectedScrollSnap(); + const slide = carousel.slideNodes()[selectedIndex] ?? null; + if (slide) { return { index: selectedIndex, element: slide, @@ -160,7 +161,6 @@ export class CarouselController { } }; - carousel.on('init', () => selectSlide()); carousel.on('select', () => selectSlide()); carousel.on('settle', () => { const carouselSelected = getCarouselSelectedObject();