diff --git a/src/components/surround.ts b/src/components/surround.ts index 9fee9c0d..7daf8c4b 100644 --- a/src/components/surround.ts +++ b/src/components/surround.ts @@ -144,10 +144,7 @@ export class FrigateCardSurround extends LitElement { return null; } if (this.view?.is('live')) { - return getAllDependentCameras( - this.cameraManager, - this.view.camera, - ); + return getAllDependentCameras(this.cameraManager, this.view.camera); } if (this.view.isViewerView()) { return new Set( @@ -225,6 +222,10 @@ export class FrigateCardSurround extends LitElement { slot=${this.timelineConfig.mode} .hass=${this.hass} .view=${this.view} + .itemClickAction=${this.view.isViewerView() || + this.thumbnailConfig.mode === 'none' + ? 'play' + : 'select'} .cameraIDs=${this._cameraIDsForTimeline} .mini=${true} .timelineConfig=${this.timelineConfig} diff --git a/src/components/timeline-core.ts b/src/components/timeline-core.ts index e775c8e5..9c94b2f2 100644 --- a/src/components/timeline-core.ts +++ b/src/components/timeline-core.ts @@ -34,7 +34,7 @@ import { ExtendedHomeAssistant, frigateCardConfigDefaults, FrigateCardView, - ThumbnailsControlConfig, + ThumbnailsControlBaseConfig, TimelineCoreConfig, } from '../types'; import { stopEventFromActivatingCardWideActions } from '../utils/action'; @@ -78,6 +78,8 @@ interface TimelineViewContext { window?: TimelineWindow; } +type TimelineItemClickAction = 'play' | 'select'; + declare module 'view' { interface ViewContext { timeline?: TimelineViewContext; @@ -173,7 +175,7 @@ export class FrigateCardTimelineCore extends LitElement { public timelineConfig?: TimelineCoreConfig; @property({ attribute: true, type: Boolean }) - public thumbnailConfig?: ThumbnailsControlConfig; + public thumbnailConfig?: ThumbnailsControlBaseConfig; // Whether or not this is a mini-timeline (in mini-mode the component takes a // supportive role for other views). @@ -191,6 +193,9 @@ export class FrigateCardTimelineCore extends LitElement { @property({ attribute: false }) public cardWideConfig?: CardWideConfig; + @property({ attribute: false }) + public itemClickAction?: TimelineItemClickAction; + @state() protected _locked = false; @@ -507,6 +512,7 @@ export class FrigateCardTimelineCore extends LitElement { } let view: View | null = null; + let drawerAction: 'open' | 'close' = 'close'; if ( this.timelineConfig?.show_recordings && @@ -585,23 +591,23 @@ export class FrigateCardTimelineCore extends LitElement { } else { view = this.view.evolve({ queryResults: newResults, + view: this.itemClickAction === 'play' ? 'media' : this.view.view, }); } if (view?.queryResults?.hasResults()) { view.mergeInContext({ mediaViewer: { seek: properties.time } }); } + + if (this.itemClickAction === 'select' && view) { + drawerAction = 'open'; + } } if (view) { view.dispatchChangeEvent(this); - - if (this.view?.is('timeline')) { - dispatchFrigateCardEvent(this, 'thumbnails:open'); - } - } else if (this.view?.is('timeline')) { - dispatchFrigateCardEvent(this, 'thumbnails:close'); } + dispatchFrigateCardEvent(this, `thumbnails:${drawerAction}`); this._ignoreClick = false; } @@ -1114,13 +1120,11 @@ export class FrigateCardTimelineCore extends LitElement { this._destroy(); } - const options = this._getOptions(); let createdTimeline = false; if ( this._timelineSource && this._refTimeline.value && - options && this.timelineConfig && (changedProperties.has('timelineConfig') || changedProperties.has('cameraIDs')) ) { @@ -1139,45 +1143,48 @@ export class FrigateCardTimelineCore extends LitElement { return; } - createdTimeline = true; - if (this.mini && groups.length === 1) { - // In a mini timeline, if there's only one group don't bother grouping - // at all. - this._timeline = new Timeline( - this._refTimeline.value, - this._timelineSource.dataset, - options, - ) as Timeline; - this.removeAttribute('groups'); - } else { - this._timeline = new Timeline( - this._refTimeline.value, - this._timelineSource.dataset, - groups, - options, - ) as Timeline; - this.setAttribute('groups', ''); + const options = this._getOptions(); + if (options) { + createdTimeline = true; + if (this.mini && groups.length === 1) { + // In a mini timeline, if there's only one group don't bother grouping + // at all. + this._timeline = new Timeline( + this._refTimeline.value, + this._timelineSource.dataset, + options, + ) as Timeline; + this.removeAttribute('groups'); + } else { + this._timeline = new Timeline( + this._refTimeline.value, + this._timelineSource.dataset, + groups, + options, + ) as Timeline; + this.setAttribute('groups', ''); + } + + this._timeline.on('rangechanged', this._timelineRangeChangedHandler.bind(this)); + this._timeline.on('click', this._timelineClickHandler.bind(this)); + this._timeline.on('rangechange', this._timelineRangeChangeHandler.bind(this)); + + // This complexity exists to ensure we can tell between a click that + // causes the timeline zoom/range to change, and a 'static' click on the + // // timeline (which may need to trigger a card wide event). + this._timeline.on('mouseDown', (ev: TimelineEventPropertiesResult) => { + const window = this._timeline?.getWindow(); + this._pointerHeld = { + ...ev, + ...(window && { window: window }), + }; + this._ignoreClick = false; + }); + this._timeline.on('mouseUp', () => { + this._pointerHeld = null; + this._removeTargetBar(); + }); } - - this._timeline.on('rangechanged', this._timelineRangeChangedHandler.bind(this)); - this._timeline.on('click', this._timelineClickHandler.bind(this)); - this._timeline.on('rangechange', this._timelineRangeChangeHandler.bind(this)); - - // This complexity exists to ensure we can tell between a click that - // causes the timeline zoom/range to change, and a 'static' click on the - // // timeline (which may need to trigger a card wide event). - this._timeline.on('mouseDown', (ev: TimelineEventPropertiesResult) => { - const window = this._timeline?.getWindow(); - this._pointerHeld = { - ...ev, - ...(window && { window: window }), - }; - this._ignoreClick = false; - }); - this._timeline.on('mouseUp', () => { - this._pointerHeld = null; - this._removeTargetBar(); - }); } if (changedProperties.has('view')) { diff --git a/src/components/timeline.ts b/src/components/timeline.ts index 2030a762..2572eb70 100644 --- a/src/components/timeline.ts +++ b/src/components/timeline.ts @@ -51,6 +51,9 @@ export class FrigateCardTimeline extends LitElement { .thumbnailConfig=${this.timelineConfig.controls.thumbnails} .cameraManager=${this.cameraManager} .cardWideConfig=${this.cardWideConfig} + .itemClickAction=${this.timelineConfig.controls.thumbnails.mode === 'none' + ? 'play' + : 'select'} > `; diff --git a/src/const.ts b/src/const.ts index beb07729..b5bb42cf 100644 --- a/src/const.ts +++ b/src/const.ts @@ -165,7 +165,7 @@ export const CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL = export const CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL = `${CONF_LIVE}.controls.thumbnails.show_timeline_control` as const; export const CONF_LIVE_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD = - `${CONF_LIVE}.control s.timeline.clustering_threshold` as const; + `${CONF_LIVE}.controls.timeline.clustering_threshold` as const; export const CONF_LIVE_CONTROLS_TIMELINE_MEDIA = `${CONF_LIVE}.controls.timeline.media` as const; export const CONF_LIVE_CONTROLS_TIMELINE_MODE = diff --git a/src/types.ts b/src/types.ts index 7796fbc7..3dfeafe6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -779,8 +779,8 @@ export type ImageViewConfig = z.infer; /** * Thumbnail controls configuration section. */ -const thumbnailControlsDefaults = { - mode: 'right' as const, + +const thumbnailControlsBaseDefaults = { size: 100, show_details: true, show_favorite_control: true, @@ -788,26 +788,39 @@ const thumbnailControlsDefaults = { show_download_control: true, }; -const thumbnailsControlSchema = z.object({ - mode: z - .enum(['none', 'above', 'below', 'left', 'right']) - .default(thumbnailControlsDefaults.mode), +// Configuration for the actual rendered thumbnail. +const thumbnailsControlBaseSchema = z.object({ size: z .number() .min(THUMBNAIL_WIDTH_MIN) .max(THUMBNAIL_WIDTH_MAX) - .default(thumbnailControlsDefaults.size), - show_details: z.boolean().default(thumbnailControlsDefaults.show_details), + .default(thumbnailControlsBaseDefaults.size), + show_details: z.boolean().default(thumbnailControlsBaseDefaults.show_details), show_favorite_control: z .boolean() - .default(thumbnailControlsDefaults.show_favorite_control), + .default(thumbnailControlsBaseDefaults.show_favorite_control), show_timeline_control: z .boolean() - .default(thumbnailControlsDefaults.show_timeline_control), + .default(thumbnailControlsBaseDefaults.show_timeline_control), show_download_control: z .boolean() - .default(thumbnailControlsDefaults.show_download_control), + .default(thumbnailControlsBaseDefaults.show_download_control), }); + +export type ThumbnailsControlBaseConfig = z.infer; + +// Configuration that may control the placement of the thumbnail. +const thumbnailControlsDefaults = { + ...thumbnailControlsBaseDefaults, + mode: 'right' as const, +}; + +const thumbnailsControlSchema = thumbnailsControlBaseSchema.extend({ + mode: z + .enum(['none', 'above', 'below', 'left', 'right']) + .default(thumbnailControlsDefaults.mode), +}); + export type ThumbnailsControlConfig = z.infer; /**