diff --git a/src/components/viewer.ts b/src/components/viewer.ts index d6c1c0f6..13177a25 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -153,6 +153,7 @@ export class FrigateCardViewer extends LitElement { this.view, { targetView: 'media', + mediaType: mediaType, }, ); } @@ -173,6 +174,7 @@ export class FrigateCardViewer extends LitElement { .cameras=${this.cameras} .viewerConfig=${this.viewerConfig} .resolvedMediaCache=${this.resolvedMediaCache} + .cameraManager=${this.cameraManager} .cardWideConfig=${this.cardWideConfig} > @@ -214,6 +216,9 @@ export class FrigateCardViewerCarousel extends LitElement { @property({ attribute: false }) public cameras?: Map; + @property({ attribute: false }) + public cameraManager?: CameraManager; + protected _refMediaCarousel: Ref = createRef(); protected _carouselOptions?: EmblaOptionsType; @@ -359,17 +364,16 @@ export class FrigateCardViewerCarousel extends LitElement { } /** - * Get a clip view that matches a given snapshot. Includes clips within the - * same range as the current view. - * @param snapshot The snapshot to find a matching clip for. - * @returns The view that would show the matching clip. + * Dispatch a clip view that matches the current (snapshot) query. + * @param index The index of the selected media. */ - protected async _createRelatedClipView(targetIndex: number): Promise { - const media = this.view?.queryResults?.getResult(targetIndex); + protected async _dispatchRelatedClipView(index: number): Promise { + const media = this.view?.queryResults?.getResult(index); if ( !this.hass || !this.view || + !this.cameraManager || !media || // If this specific media item has no clip, then do nothing (even if all // the other media items do). @@ -377,42 +381,28 @@ export class FrigateCardViewerCarousel extends LitElement { !media.hasClip() || !MediaQueriesClassifier.areEventQueries(this.view.query) ) { - return null; + return; } - const newResults: ViewMedia[] = []; - let newSelectedIndex: number | null = null; - // Convert the query to a clips equivalent. - const newQuery = this.view.query.clone(); - newQuery.convertToClipsQueries(); + const clipQuery = this.view.query.clone(); + clipQuery.convertToClipsQueries(); - // Regenerate the whole results stack. - for (let i = 0; i < (this.view.queryResults?.getResultsCount() ?? 0); ++i) { - const media = this.view.queryResults?.getResult(i); - if (!media || !ViewMediaClassifier.isFrigateEvent(media)) { - continue; - } - const clipMedia = media.getClipEquivalent(); - if (clipMedia) { - newResults.push(clipMedia); - if (i === targetIndex) { - newSelectedIndex = i; - } - } - } - if (newSelectedIndex === null) { - return null; + const results = await this.cameraManager.executeMediaQuery(this.hass, clipQuery); + if (!results) { + return; } - const newQueryResults = new MediaQueriesResults(newResults); - newQueryResults.selectResult(newSelectedIndex); + results.selectResultIfFound((clipMedia) => clipMedia.getID() === media.getID()); + if (!results.hasSelectedResult()) { + return; + } - return this.view.evolve({ - view: 'clip', - query: newQuery, - queryResults: newQueryResults, - }); + this.view.evolve({ + view: 'media', + query: clipQuery, + queryResults: results, + }).dispatchChangeEvent(this); } /** @@ -708,11 +698,7 @@ export class FrigateCardViewerCarousel extends LitElement { ?.carouselClickAllowed() && this.viewerConfig?.snapshot_click_plays_clip ) { - this._createRelatedClipView(index).then((view) => { - if (view) { - view.dispatchChangeEvent(this); - } - }); + this._dispatchRelatedClipView(index); } }} @load="${(e: Event) => { diff --git a/src/utils/basic.ts b/src/utils/basic.ts index cff8a7eb..4a1c5e81 100644 --- a/src/utils/basic.ts +++ b/src/utils/basic.ts @@ -5,6 +5,8 @@ import format from 'date-fns/format'; import isEqual from 'lodash-es/isEqual'; import { FrigateCardError } from '../types'; +export type ModifyInterface = Omit & R; + /** * Dispatch a Frigate Card event. * @param target The target from which send the event. diff --git a/src/utils/timeline-source.ts b/src/utils/timeline-source.ts index d31e513b..36b52734 100644 --- a/src/utils/timeline-source.ts +++ b/src/utils/timeline-source.ts @@ -10,7 +10,7 @@ import { capEndDate, convertRangeToCacheFriendlyTimes } from '../camera/util'; import { EventMediaQueries } from "../view/media-queries"; import { ViewMedia } from '../view/media'; import { compressRanges, ExpiringMemoryRangeSet, MemoryRangeSet } from '../camera/range'; -import { ModifyInterface } from './basic'; +import { ModifyInterface } from './basic.js'; // Allow timeline freshness to be at least this number of seconds out of date // (caching times in the data-engine may increase the effective delay). diff --git a/src/view/view.ts b/src/view/view.ts index 66fdbff1..5105b366 100644 --- a/src/view/view.ts +++ b/src/view/view.ts @@ -2,11 +2,11 @@ // - 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. // - TODO: Do I need to dedup recordings? (i.e. multiple zones on same camera may need to be dedup'd somewhere before returning the view). The media getID() call may be useful for this. -// - TODO: Do a fresh media query in the viewer on snapshot click, since the first query may (e.g.) only have requested events with snapshots (which would miss an event with just a clip). // - TODO: Move frigate specific view-media under the camera manager. // - TODO: ts-prune https://camchenry.com/blog/deleting-dead-code-in-typescript // Hard: +// - TODO: There are circumstances when lazy load doesn't work (e.g. snapshot -> clips re-query) // - TODO: Implement dragging the timeline seeking forward in both Frigate recordings & events. // - TODO: Implement gallery. // - TODO: Remove FrigateBrowseMediaSource if not necessary (post-gallery).