diff --git a/src/components/carousel.ts b/src/components/carousel.ts index 362fdd34..e1ab24bf 100644 --- a/src/components/carousel.ts +++ b/src/components/carousel.ts @@ -97,28 +97,6 @@ export class FrigateCardCarousel extends LitElement { } } - /** - * Scroll to a particular slide. - * @param index Slide number. - */ - public carouselScrollTo(index: number): void { - this.selected = index; - } - - /** - * Scroll to the previous slide. - */ - public carouselScrollPrevious(): void { - this.selected = Math.max(0, this.selected - 1); - } - - /** - * Scroll to the next slide. - */ - public carouselScrollNext(): void { - this.selected = this.selected + 1; - } - /** * Get the selected slide. * @returns A CarouselSelect object (index & element). diff --git a/src/components/live/live.ts b/src/components/live/live.ts index a121bdcc..91f0600b 100644 --- a/src/components/live/live.ts +++ b/src/components/live/live.ts @@ -48,7 +48,7 @@ import { import '../next-prev-control.js'; import '../title-control.js'; import '../surround.js'; -import { EmblaCarouselPlugins } from '../carousel.js'; +import { CarouselSelect, EmblaCarouselPlugins } from '../carousel.js'; import { classMap } from 'lit/directives/class-map.js'; import { updateElementStyleFromMediaLayoutConfig } from '../../utils/media-layout.js'; import { CameraManager } from '../../camera/manager.js'; @@ -316,28 +316,9 @@ export class FrigateCardLiveCarousel extends LitElement { super.updated(changedProperties); const frigateCardMediaCarousel = this._refMediaCarousel.value; - const frigateCardCarousel = frigateCardMediaCarousel?.frigateCardCarousel(); - - if (changedProperties.has('view')) { - const oldView = changedProperties.get('view') as View | undefined; - if ( - frigateCardCarousel && - this.view?.camera && - (!oldView || this.view?.camera !== oldView.camera) - ) { - const slide: number | undefined = this._cameraToSlide[this.view.camera]; - if ( - slide !== undefined && - slide !== frigateCardCarousel.getCarouselSelected()?.index - ) { - frigateCardCarousel.carouselScrollTo(slide); - } - } - } if ( frigateCardMediaCarousel && - frigateCardCarousel && changedProperties.has('inBackground') ) { // If this has changed to be in the background (i.e. preloaded but not @@ -463,25 +444,26 @@ export class FrigateCardLiveCarousel extends LitElement { /** * Handle the user selecting a new slide in the carousel. */ - protected _setViewHandler(): void { - const selectedCameraIndex = this._refMediaCarousel.value - ?.frigateCardCarousel() - ?.getCarouselSelected()?.index; - if (selectedCameraIndex === undefined || !this.view || !this.cameras) { - return; + protected _setViewHandler(ev: CustomEvent): void { + if (this.cameras && ev.detail.index !== this._getSelectedCameraIndex()) { + this._setViewCameraID(Array.from(this.cameras.keys())[ev.detail.index]); } + } - this.view - .evolve({ - camera: Array.from(this.cameras.keys())[selectedCameraIndex], - // Reset the query and query results. - query: null, - queryResults: null, - }) - // Don't yet fetch thumbnails (they will be fetched when the carousel - // settles). - .mergeInContext({ thumbnails: { fetch: false } }) - .dispatchChangeEvent(this); + protected _setViewCameraID(cameraID?: string | null): void { + if (cameraID) { + this.view + ?.evolve({ + camera: cameraID, + // Reset the query and query results. + query: null, + queryResults: null, + }) + // Don't yet fetch thumbnails (they will be fetched when the carousel + // settles). + .mergeInContext({ thumbnails: { fetch: false } }) + .dispatchChangeEvent(this); + } } /** @@ -549,7 +531,7 @@ export class FrigateCardLiveCarousel extends LitElement { `; } - protected _getCameraNeighbors(): [CameraConfig | null, CameraConfig | null] { + protected _getCameraIDsOfNeighbors(): [string | null, string | null] { if (!this.cameras || !this.view || !this.hass) { return [null, null]; } @@ -560,15 +542,10 @@ export class FrigateCardLiveCarousel extends LitElement { return [null, null]; } - const prev = - this.cameras.get( - keys[currentIndex > 0 ? currentIndex - 1 : this.cameras.size - 1], - ) ?? null; - const next = - this.cameras.get( - keys[currentIndex + 1 < this.cameras.size ? currentIndex + 1 : 0], - ) ?? null; - return [prev, next]; + return [ + keys[currentIndex > 0 ? currentIndex - 1 : this.cameras.size - 1], + keys[currentIndex + 1 < this.cameras.size ? currentIndex + 1 : 0], + ]; } /** @@ -588,7 +565,7 @@ export class FrigateCardLiveCarousel extends LitElement { this.conditionState, ) as LiveConfig; - const [prev, next] = this._getCameraNeighbors(); + const [prevID, nextID] = this._getCameraIDsOfNeighbors(); const title = getCameraTitle(this.hass, this.cameras.get(this.view.camera)); // Notes on the below: @@ -627,13 +604,11 @@ export class FrigateCardLiveCarousel extends LitElement { .hass=${this.hass} .direction=${'previous'} .controlConfig=${config.controls.next_previous} - .label=${getCameraTitle(this.hass, prev)} - .icon=${getCameraIcon(this.hass, prev)} - ?disabled=${prev == null} + .label=${getCameraTitle(this.hass, prevID ? this.cameras.get(prevID) : null)} + .icon=${getCameraIcon(this.hass, prevID ? this.cameras.get(prevID) : null)} + ?disabled=${prevID === null} @click=${(ev) => { - this._refMediaCarousel.value - ?.frigateCardCarousel() - ?.carouselScrollPrevious(); + this._setViewCameraID(prevID); stopEventFromActivatingCardWideActions(ev); }} > @@ -644,11 +619,11 @@ export class FrigateCardLiveCarousel extends LitElement { .hass=${this.hass} .direction=${'next'} .controlConfig=${config.controls.next_previous} - .label=${getCameraTitle(this.hass, next)} - .icon=${getCameraIcon(this.hass, next)} - ?disabled=${next == null} + .label=${getCameraTitle(this.hass, nextID ? this.cameras.get(nextID) : null)} + .icon=${getCameraIcon(this.hass, nextID ? this.cameras.get(nextID) : null)} + ?disabled=${nextID === null} @click=${(ev) => { - this._refMediaCarousel.value?.frigateCardCarousel()?.carouselScrollNext(); + this._setViewCameraID(nextID); stopEventFromActivatingCardWideActions(ev); }} > diff --git a/src/components/viewer.ts b/src/components/viewer.ts index 84ea3802..d6c1c0f6 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -31,8 +31,8 @@ import { contentsChanged } from '../utils/basic.js'; import { getFullDependentBrowseMediaQueryParametersOrDispatchError } from '../utils/ha/browse-media.js'; import { ResolvedMediaCache, resolveMedia } from '../utils/ha/resolved-media.js'; import { View } from '../view/view.js'; -import { MediaQueriesResults } from "../view/media-queries-results"; -import { MediaQueriesClassifier } from "../view/media-queries-classifier"; +import { MediaQueriesResults } from '../view/media-queries-results'; +import { MediaQueriesClassifier } from '../view/media-queries-classifier'; import { AutoMediaPlugin } from './embla-plugins/automedia.js'; import { Lazyload } from './embla-plugins/lazyload.js'; import { @@ -54,7 +54,7 @@ import { changeViewToRecentRecordingForCameraAndDependents, } from '../utils/media-to-view.js'; import { ViewMedia } from '../view/media.js'; -import { ViewMediaClassifier } from "../view/media-classifier"; +import { ViewMediaClassifier } from '../view/media-classifier'; import { guard } from 'lit/directives/guard.js'; import { localize } from '../localize/localize.js'; @@ -419,21 +419,21 @@ export class FrigateCardViewerCarousel extends LitElement { * Handle the user selecting a new slide in the carousel. */ protected _setViewHandler(ev: CustomEvent): void { - if (!this._refMediaCarousel.value || !this.view) { - return; + if (ev.detail.index !== this.view?.queryResults?.getSelectedIndex()) { + this._setViewSelectedIndex(ev.detail.index); } + } + protected _setViewSelectedIndex(index: number): void { // The slide may already be selected on load, so don't dispatch a new view // unless necessary. - if (ev.detail.index !== this.view.queryResults?.getSelectedIndex()) { - this.view - .evolve({ - queryResults: this.view.queryResults?.clone().selectResult(ev.detail.index), - }) - // Ensure the timeline is able to update its position. - .mergeInContext({ timeline: { noSetWindow: false } }) - .dispatchChangeEvent(this); - } + this.view + ?.evolve({ + queryResults: this.view.queryResults?.clone().selectResult(index), + }) + // Ensure the timeline is able to update its position. + .mergeInContext({ timeline: { noSetWindow: false } }) + .dispatchChangeEvent(this); } /** @@ -583,6 +583,17 @@ export class FrigateCardViewerCarousel extends LitElement { const [prev, next] = this._getMediaNeighbors(); + const scroll = (direction: 'previous' | 'next'): void => { + const currentIndex = this.view?.queryResults?.getSelectedIndex() ?? null; + if (!this.view || !this.view?.queryResults || currentIndex === null) { + return; + } + const newIndex = direction === 'previous' ? currentIndex - 1 : currentIndex + 1; + if (newIndex >= 0 && newIndex < this.view.queryResults.getResultsCount()) { + this._setViewSelectedIndex(newIndex); + } + }; + return html` { - this._refMediaCarousel.value?.frigateCardCarousel()?.carouselScrollPrevious(); + scroll('previous'); stopEventFromActivatingCardWideActions(ev); }} > @@ -619,7 +630,7 @@ export class FrigateCardViewerCarousel extends LitElement { .label=${next?.getTitle() ?? ''} ?disabled=${!next} @click=${(ev) => { - this._refMediaCarousel.value?.frigateCardCarousel()?.carouselScrollNext(); + scroll('next'); stopEventFromActivatingCardWideActions(ev); }} > diff --git a/src/view/media.ts b/src/view/media.ts index 15633795..17119fb2 100644 --- a/src/view/media.ts +++ b/src/view/media.ts @@ -9,7 +9,6 @@ import { getRecordingTitle, } from '../camera/frigate/util.js'; import { FrigateEvent, FrigateRecording } from '../camera/frigate/types.js'; -import { ViewMediaClassifier } from './media-classifier.js'; export type ViewMediaType = 'clip' | 'snapshot' | 'recording'; export type ViewMediaSourceType = FrigateEvent | FrigateRecording; @@ -129,7 +128,7 @@ export class FrigateEventViewMedia cameraConfig.frigate.client_id, cameraConfig.frigate.camera_name, this._source, - ViewMediaClassifier.isClip(this) ? 'clips' : 'snapshots', + this._mediaType === 'clip' ? 'clips' : 'snapshots', ); } diff --git a/src/view/view.ts b/src/view/view.ts index dd8df6bb..66fdbff1 100644 --- a/src/view/view.ts +++ b/src/view/view.ts @@ -1,7 +1,3 @@ -// Easy: -// - TODO: In MediaQueriesBase, do we need to generic? Just have T be a MediaQuery? -// - TODO: In the viewer @click handlers should I use this.selected instead of calling carouselScrollPrevious() - // Medium: // - TODO: Callers of all async methods of data-engine need to catch errors. // - TODO: Add garbage collecting of segments not present in the recording summaries anymore.