diff --git a/src/components/surround.ts b/src/components/surround.ts index 21ee2038..30ff2170 100644 --- a/src/components/surround.ts +++ b/src/components/surround.ts @@ -175,19 +175,20 @@ export class FrigateCardSurround extends LitElement { ) => { const child: FrigateBrowseMediaSource | null = ev.detail.target?.children?.[ev.detail.childIndex] ?? null; - // Send the view change from the source of the tap event, so the - // view change will be caught by the handler above (to close the drawer). if (child) { this.view ?.evolve({ view: this.view.is('recording') ? 'recording' : 'media', target: ev.detail.target, childIndex: ev.detail.childIndex, - context: null, - ...(child?.frigate?.cameraID && { - camera: child?.frigate?.cameraID, + ...(child.frigate?.cameraID && { + camera: child.frigate?.cameraID, }), }) + .removeContext('timeline') + // Send the view change from the source of the tap event, so + // the view change will be caught by the handler above (to + // close the drawer). .dispatchChangeEvent(ev.composedPath()[0]); } }} diff --git a/src/components/thumbnail.ts b/src/components/thumbnail.ts index 6b988055..9f597684 100644 --- a/src/components/thumbnail.ts +++ b/src/components/thumbnail.ts @@ -303,6 +303,9 @@ export class FrigateCardThumbnail extends LitElement { .removeContext('timeline') .dispatchChangeEvent(this); } else if (recording) { + // Specifically reset the media target/childIndex, as we cannot + // 'select' an hour in the timeline rather we set the window to + // matching values. this.view ?.evolve({ view: 'timeline', diff --git a/src/components/timeline-core.ts b/src/components/timeline-core.ts index 114dc69c..ff05bc1d 100644 --- a/src/components/timeline-core.ts +++ b/src/components/timeline-core.ts @@ -40,6 +40,7 @@ import { FrigateBrowseMediaSource, frigateCardConfigDefaults, FrigateEvent, + FrigateRecording, TimelineCoreConfig, } from '../types'; import { stopEventFromActivatingCardWideActions } from '../utils/action'; @@ -65,7 +66,7 @@ import { } from '../utils/timeline-data-manager'; import { View } from '../view'; import { dispatchMessageEvent } from './message.js'; -import "./thumbnail.js"; +import './thumbnail.js'; interface FrigateCardGroupData { id: string; @@ -557,7 +558,7 @@ export class FrigateCardTimelineCore extends LitElement { childIndex: childIndex, }), }) - .mergeInContext({ ...this._generateTimelineContext(true), ...context }) + .mergeInContext({ ...this._generateTimelineContext(), ...context }) .dispatchChangeEvent(this); } } @@ -567,7 +568,7 @@ export class FrigateCardTimelineCore extends LitElement { * seek times into each media item). * @param children The media children. * @param targetTime The target time. - * @returns + * @returns The ViewContext. */ protected _generateMediaViewerContextForChildren( children: FrigateBrowseMediaSource[], @@ -696,9 +697,9 @@ export class FrigateCardTimelineCore extends LitElement { childIndex = thumbnails.childIndex; if (thumbnails.target?.children?.length) { context = this._generateMediaViewerContextForChildren( - thumbnails.target.children, - properties.time, - ); + thumbnails.target.children, + properties.time, + ); } } } else { @@ -771,7 +772,7 @@ export class FrigateCardTimelineCore extends LitElement { target: thumbnails?.target ?? null, childIndex: thumbnails?.childIndex ?? null, }) - .mergeInContext(this._generateTimelineContext(true)) + .mergeInContext(this._generateTimelineContext()) .dispatchChangeEvent(this); } }); @@ -874,6 +875,15 @@ export class FrigateCardTimelineCore extends LitElement { ]; } + /** + * Given a recording get the start/end window. + * @param recording The FrigateRecording to consider. + * @returns A tuple of start/end date. + */ + protected _getStartEndFromRecording(recording: FrigateRecording): [Date, Date] { + return [fromUnixTime(recording.start_time), fromUnixTime(recording.end_time)]; + } + /** * Get the configured window length in seconds. */ @@ -1004,8 +1014,12 @@ export class FrigateCardTimelineCore extends LitElement { } const event = this.view?.media?.frigate?.event; + const recording = this.view?.media?.frigate?.recording; + const [windowStart, windowEnd] = event ? this._getStartEndFromEvent(event) + : recording + ? this._getStartEndFromRecording(recording) : this._getStartEnd(); let fetched = false; @@ -1042,33 +1056,40 @@ export class FrigateCardTimelineCore extends LitElement { this.timelineDataManager?.rewriteItem(event.id); } + let contextWindow: TimelineWindow | null = null; if (!this._pointerHeld) { // Regenerate the thumbnails after the selection, to allow the new selection // to be in the generated view. const context = this.view.context?.timeline; const timelineWindow = this._timeline.getWindow(); - if (context?.window) { - if (!isEqual(context.window, timelineWindow)) { - this._timeline.setWindow(context.window.start, context.window.end); - } - } else if (event) { - const eventStart = new Date(event.start_time * 1000); - const eventEnd = event.end_time ? new Date(event.end_time * 1000) : 0; + // If there's a set context window, always move to it. + if (context?.window && !isEqual(context.window, timelineWindow)) { + contextWindow = {start: context.window.start, end: context.window.end}; + } else if (event || recording) { + const source = event ?? recording as FrigateEvent | FrigateRecording; + const start = fromUnixTime(source.start_time); + const end = source.end_time ? fromUnixTime(source.end_time) : 0; + // If there's an event or recording outside the current window, move to it. if ( - eventStart < timelineWindow.start || - eventStart > timelineWindow.end || - (eventEnd && - (eventEnd < timelineWindow.start || eventEnd > timelineWindow.end)) + start < timelineWindow.start || + start > timelineWindow.end || + (end && + (end < timelineWindow.start || end > timelineWindow.end)) ) { - this._timeline.setWindow(windowStart, windowEnd); + contextWindow = {start: windowStart, end: windowEnd}; } } else { - this._timeline.setWindow(windowStart, windowEnd); + // Otherwise just the default window. + contextWindow = {start: windowStart, end: windowEnd}; } } + if (contextWindow) { + this._timeline.setWindow(contextWindow.start, contextWindow.end); + } + // Only generate thumbnails if an actual fetch occurred, to avoid getting // stuck in a loop (the subsequent fetches will not actually fetch since the // data will have been cached). @@ -1078,15 +1099,17 @@ export class FrigateCardTimelineCore extends LitElement { // -> Thumbnails generated // -> New view dispatched (to load thumbnails into outer carousel). // -> New view received ... [loop] - - if ((fetched || !this.view.context?.timeline?.generatedThumbnails) && !this.mini) { + // + // Also don't generate thumbnails in mini-timelines (they will already have + // been generated), or if the media child is a recording. + if ((fetched || !this.view.context?.timeline?.generatedThumbnails) && !this.mini && !recording) { const thumbnails = this._generateThumbnails(); this.view ?.evolve({ target: thumbnails?.target ?? null, childIndex: thumbnails?.childIndex ?? null, }) - .mergeInContext(this._generateTimelineContext(false)) + .mergeInContext(this._generateTimelineContext(contextWindow)) .dispatchChangeEvent(this); } } @@ -1097,16 +1120,12 @@ export class FrigateCardTimelineCore extends LitElement { * the window is preserved if it is already in the context. * @returns The TimelineViewContext object. */ - protected _generateTimelineContext(addWindow: boolean): ViewContext { - const currentContext = this.view?.context?.timeline; + protected _generateTimelineContext(window?: TimelineWindow | null): ViewContext { const newContext: TimelineViewContext = { generatedThumbnails: true, }; - - if (addWindow && this._timeline) { - newContext.window = this._timeline.getWindow(); - } else if (currentContext?.window) { - newContext.window = currentContext.window; + if (this._timeline) { + newContext.window = window ? window : this._timeline.getWindow(); } if (this.timelineDataManager?.lastFetchDate) { newContext.dateFetch = this.timelineDataManager.lastFetchDate; diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index b30b9813..9e4655bc 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -161,11 +161,11 @@ "thumbnails": { "mode": "Media Viewer thumbnails mode", "modes": { - "above": "Thumbnails above the media", - "below": "Thumbnails below the media", - "left": "Thumbnails in a drawer left of the media", + "above": "Thumbnails above", + "below": "Thumbnails below", + "left": "Thumbnails in a drawer to the left", "none": "No thumbnails", - "right": "Thumbnails in a drawer right of the media" + "right": "Thumbnails in a drawer to the right" }, "show_details": "Show details with thumbnails", "show_favorite_control": "Show favorite control on thumbnails", @@ -372,7 +372,7 @@ "thumbnail": { "no_thumbnail": "No thumbnail available", "retain_indefinitely": "Event will be indefinitely retained", - "timeline": "See event in timeline" + "timeline": "See event/recording in timeline" }, "timeline": { "lock": "Lock timeline to a single event",