diff --git a/README.md b/README.md index 8d1bc9e5..3400d854 100644 --- a/README.md +++ b/README.md @@ -564,7 +564,8 @@ live: | `show_download_control` | `true` | :white_check_mark: | Whether to show the download control on each thumbnail.| | `show_favorite_control` | `true` | :white_check_mark: | Whether to show the favorite ('star') control on each thumbnail.| | `show_timeline_control` | `true` | :white_check_mark: | Whether to show the timeline ('target') control on each thumbnail.| -| `media` | `all` | :white_check_mark: | Whether to show `clips`, `snapshots` or `all` in the thumbnail carousel in the `live` view.| +| `media_type` | `events` | :white_check_mark: | Whether to load `events` or `recordings` media.| +| `events_media_type` | `all` | :white_check_mark: | Whether to show `clips`, `snapshots` or `all` in the thumbnail carousel in the `live` view. This setting is only relevant when the `media_type` parameter (above) is set to `events`.| #### Live Controls: Next / Previous @@ -597,7 +598,7 @@ live: | `style` | `ribbon` | :white_check_mark: | Whether the timeline should show events as a single flat `ribbon` or a `stack` of events that are clustered using the `clustering_threshold` (below). | | `window_seconds` | `3600` | :white_check_mark: | The length of the default timeline in seconds. By default, 1 hour (`3600` seconds) is shown in the timeline. | | `clustering_threshold` | `3` | :white_check_mark: | The minimum number of overlapping events to allow prior to clustering/grouping them. Higher numbers cause clustering to happen less frequently. Depending on the timescale/zoom of the timeline, the underlying timeline library may still allow overlaps for low values of this parameter -- for a fully "flat" timeline use the `ribbon` style. `0` disables clustering entirely. Only used in the `stack` style of timeline. | -| `media` | `all` | :white_check_mark: | Whether to show only events with `clips`, events with `snapshots` or `all` events. When `all` is used, `clips` are favored for events that have both a clip and a snapshot.| +| `events_media_type` | `all` | :white_check_mark: | Whether to show only events with `clips`, events with `snapshots` or `all` events. When `all` is used, `clips` are favored for events that have both a clip and a snapshot.| | `show_recordings` | `true` | :white_check_mark: | Whether to show recordings on the timeline (specifically: which hours have any recorded content).| **Caution**: 🚩 For optimal UX, keep the settings for the mini-timeline in the `live` and `media_viewer` identical. Dragging the timeline may cause the card to change between the `live` view and `media_viewer` based views as the user pans between the past and present -- if the settings are different the timeline must "reset". @@ -781,7 +782,7 @@ media_viewer: | `style` | `ribbon` | :heavy_multiplication_x: | Whether the timeline should show events as a single flat `ribbon` or a `stack` of events that are clustered using the `clustering_threshold` (below). | | `window_seconds` | `3600` | :heavy_multiplication_x: | The length of the default timeline in seconds. By default, 1 hour (`3600` seconds) is shown in the timeline. | | `clustering_threshold` | `3` | :heavy_multiplication_x: | The minimum number of overlapping events to allow prior to clustering/grouping them. Higher numbers cause clustering to happen less frequently. Depending on the timescale/zoom of the timeline, the underlying timeline library may still allow overlaps for low values of this parameter -- for a fully "flat" timeline use the `ribbon` style. `0` disables clustering entirely. Only used in the `stack` style of timeline. | -| `media` | `all` | :heavy_multiplication_x: | Whether to show only events with `clips`, events with `snapshots` or `all` events. When `all` is used, `clips` are favored for events that have both a clip and a snapshot.| +| `events_media_type` | `all` | :heavy_multiplication_x: | Whether to show only events with `clips`, events with `snapshots` or `all` events. When `all` is used, `clips` are favored for events that have both a clip and a snapshot. | | `show_recordings` | `true` | :heavy_multiplication_x: | Whether to show recordings on the timeline (specifically: which hours have any recorded content).| **Caution**: 🚩 For optimal UX, keep the settings for the mini-timeline in the `live` and `media_viewer` identical. Dragging the timeline may cause the card to change between the `live` view and `media_viewer` based views as the user pans between the past and present -- if the settings are different the timeline must "reset". @@ -911,7 +912,7 @@ See the [fully expanded timeline configuration example](#config-expanded-timelin | `style` | `stack` | :heavy_multiplication_x: | Whether the timeline should show events as a single flat `ribbon` or a `stack` of events that are clustered using the `clustering_threshold` (below). | | `window_seconds` | `3600` | :heavy_multiplication_x: | The length of the default timeline in seconds. By default, 1 hour (`3600` seconds) is shown in the timeline. | | `clustering_threshold` | `3` | :heavy_multiplication_x: | The minimum number of overlapping events to allow prior to clustering/grouping them. Higher numbers cause clustering to happen less frequently. Depending on the timescale/zoom of the timeline, the underlying timeline library may still allow overlaps for low values of this parameter -- for a fully "flat" timeline use the `ribbon` style. `0` disables clustering entirely. Only used in the `stack` style of timeline. | -| `media` | `all` | :heavy_multiplication_x: | Whether to show only events with `clips`, events with `snapshots` or `all` events. When `all` is used, `clips` are favored for events that have both a clip and a snapshot.| +| `events_media_type` | `all` | :heavy_multiplication_x: | Whether to show only events with `clips`, events with `snapshots` or `all` events. When `all` is used, `clips` are favored for events that have both a clip and a snapshot.| | `show_recordings` | `true` | :heavy_multiplication_x: | Whether to show recordings on the timeline (specifically: which hours have any recorded content).| | `controls` | | :heavy_multiplication_x: | Configuration for the timeline controls. See below.| @@ -2092,7 +2093,8 @@ live: style: chevrons size: 48 thumbnails: - media: clips + media_type: events + events_media_type: all size: 100 show_details: false show_download_control: true @@ -2103,7 +2105,7 @@ live: style: ribbon mode: none clustering_threshold: 3 - media: all + events_media_type: all show_recordings: true window_seconds: 3600 title: @@ -2201,7 +2203,7 @@ media_viewer: style: ribbon mode: none clustering_threshold: 3 - media: all + events_media_type: all show_recordings: true window_seconds: 3600 title: @@ -2663,7 +2665,7 @@ Reference: [Timeline Options](#timeline-options). timeline: style: stack clustering_threshold: 3 - media: all + events_media_type: all show_recordings: true window_seconds: 3600 controls: @@ -2755,8 +2757,8 @@ overrides: maxAudioLag: 10 videoBufferSize: 524288 audioBufferSize: 131072 - image: - refresh_seconds: 1 + image: + refresh_seconds: 1 live: webrtc_card: ui: true @@ -2779,7 +2781,8 @@ overrides: style: chevrons size: 48 thumbnails: - media: clips + media_type: events + events_media_type: all size: 100 show_details: false show_download_control: true diff --git a/src/components-lib/timeline-source.ts b/src/components-lib/timeline-source.ts index 3aac95b4..ad9eb210 100644 --- a/src/components-lib/timeline-source.ts +++ b/src/components-lib/timeline-source.ts @@ -47,18 +47,18 @@ export class TimelineDataSource { protected _eventRanges = new ExpiringMemoryRangeSet(); protected _cameraIDs: Set; - protected _mediaType: ClipsOrSnapshotsOrAll; + protected _eventsMediaType: ClipsOrSnapshotsOrAll; protected _showRecordings: boolean; constructor( cameraManager: CameraManager, cameraIDs: Set, - media: ClipsOrSnapshotsOrAll, + eventsMediaType: ClipsOrSnapshotsOrAll, showRecordings: boolean, ) { this._cameraManager = cameraManager; this._cameraIDs = cameraIDs; - this._mediaType = media; + this._eventsMediaType = eventsMediaType; this._showRecordings = showRecordings; } @@ -101,8 +101,8 @@ export class TimelineDataSource { return this._cameraManager.generateDefaultEventQueries(this._cameraIDs, { start: window.start, end: window.end, - ...(this._mediaType === 'clips' && { hasClip: true }), - ...(this._mediaType === 'snapshots' && { hasSnapshot: true }), + ...(this._eventsMediaType === 'clips' && { hasClip: true }), + ...(this._eventsMediaType === 'snapshots' && { hasSnapshot: true }), }); } diff --git a/src/components/gallery.ts b/src/components/gallery.ts index 0cc3fa73..b4f0f315 100644 --- a/src/components/gallery.ts +++ b/src/components/gallery.ts @@ -87,7 +87,7 @@ export class FrigateCardGallery extends LitElement { this.view, ); } else { - const mediaType = this.view.is('snapshots') + const eventsMediaType = this.view.is('snapshots') ? 'snapshots' : this.view.is('clips') ? 'clips' @@ -98,7 +98,7 @@ export class FrigateCardGallery extends LitElement { this.cardWideConfig, this.view, { - ...(mediaType && { mediaType: mediaType }), + ...(eventsMediaType && { eventsMediaType: eventsMediaType }), }, ); } diff --git a/src/components/surround.ts b/src/components/surround.ts index 853f1a92..ad89f4ec 100644 --- a/src/components/surround.ts +++ b/src/components/surround.ts @@ -14,9 +14,16 @@ import { ThumbnailsControlConfig, } from '../config/types.js'; import basicBlockStyle from '../scss/basic-block.scss'; -import { ClipsOrSnapshotsOrAll, ExtendedHomeAssistant } from '../types.js'; +import { + ClipsOrSnapshotsOrAll, + EventsOrRecordings, + ExtendedHomeAssistant, +} from '../types.js'; import { contentsChanged, dispatchFrigateCardEvent } from '../utils/basic.js'; -import { changeViewToRecentEventsForCameraAndDependents } from '../utils/media-to-view'; +import { + changeViewToRecentEventsForCameraAndDependents, + changeViewToRecentRecordingForCameraAndDependents, +} from '../utils/media-to-view'; import { View } from '../view/view.js'; import './surround-basic.js'; import { ThumbnailCarouselTap } from './thumbnail-carousel.js'; @@ -48,7 +55,10 @@ export class FrigateCardSurround extends LitElement { // If fetchMedia is not specified, no fetching is done. @property({ attribute: false, hasChanged: contentsChanged }) - public fetchMedia?: ClipsOrSnapshotsOrAll; + public fetchMediaType?: EventsOrRecordings; + + @property({ attribute: false, hasChanged: contentsChanged }) + public fetchEventsMediaType?: ClipsOrSnapshotsOrAll; @property({ attribute: false }) public cameraManager?: CameraManager; @@ -68,7 +78,8 @@ export class FrigateCardSurround extends LitElement { if ( !this.cameraManager || !this.cardWideConfig || - !this.fetchMedia || + !this.fetchMediaType || + !this.fetchEventsMediaType || !this.hass || !this.view || this.view.query || @@ -78,18 +89,33 @@ export class FrigateCardSurround extends LitElement { ) { return; } - await changeViewToRecentEventsForCameraAndDependents( - this, - this.cameraManager, - this.cardWideConfig, - this.view, - { - allCameras: this.view.isGrid(), - targetView: this.view.view, - mediaType: this.fetchMedia, - select: 'latest', - }, - ); + + if (this.fetchMediaType === 'events') { + await changeViewToRecentEventsForCameraAndDependents( + this, + this.cameraManager, + this.cardWideConfig, + this.view, + { + allCameras: this.view.isGrid(), + targetView: this.view.view, + eventsMediaType: this.fetchEventsMediaType, + select: 'latest', + }, + ); + } else if (this.fetchMediaType === 'recordings') { + await changeViewToRecentRecordingForCameraAndDependents( + this, + this.cameraManager, + this.cardWideConfig, + this.view, + { + allCameras: this.view.isGrid(), + targetView: this.view.view, + select: 'latest', + }, + ); + } } /** diff --git a/src/components/timeline-core.ts b/src/components/timeline-core.ts index b0d62eb8..4aee4deb 100644 --- a/src/components/timeline-core.ts +++ b/src/components/timeline-core.ts @@ -1161,7 +1161,7 @@ export class FrigateCardTimelineCore extends LitElement { this._timelineSource = new TimelineDataSource( this.cameraManager, cameraIDs, - this.timelineConfig.media, + this.timelineConfig.events_media_type, this.timelineConfig.show_recordings, ); } else { diff --git a/src/components/viewer.ts b/src/components/viewer.ts index e078b1b9..7310853d 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -168,7 +168,7 @@ export class FrigateCardViewer extends LitElement { { allCameras: this.view.isGrid(), targetView: 'media', - mediaType: mediaType, + eventsMediaType: mediaType, }, ); } diff --git a/src/components/views.ts b/src/components/views.ts index d07e57d6..352547b9 100644 --- a/src/components/views.ts +++ b/src/components/views.ts @@ -64,7 +64,7 @@ export class FrigateCardViews extends LitElement { public microphoneManager?: ReadonlyMicrophoneManager; @property({ attribute: false }) - public triggeredCameraIDs?: Set; + public triggeredCameraIDs?: Set; protected willUpdate(changedProps: PropertyValues): void { if (changedProps.has('view') || changedProps.has('config')) { @@ -163,8 +163,11 @@ export class FrigateCardViews extends LitElement { class="${classMap(overallClasses)}" .hass=${this.hass} .view=${this.view} - .fetchMedia=${this.view?.is('live') - ? this.overriddenConfig.live.controls.thumbnails.media + .fetchMediaType=${this.view?.is('live') + ? this.overriddenConfig.live.controls.thumbnails.media_type + : undefined} + .fetchEventsMediaType=${this.view?.is('live') + ? this.overriddenConfig.live.controls.thumbnails.events_media_type : undefined} .thumbnailConfig=${!this.hide ? thumbnailConfig : undefined} .timelineConfig=${!this.hide ? miniTimelineConfig : undefined} diff --git a/src/config-mgmt.ts b/src/config-mgmt.ts index 9fe304ad..fd0e0e20 100644 --- a/src/config-mgmt.ts +++ b/src/config-mgmt.ts @@ -10,9 +10,13 @@ import { CONF_CAMERAS_GLOBAL_JSMPEG, CONF_CAMERAS_GLOBAL_WEBRTC_CARD, CONF_ELEMENTS, + CONF_LIVE_CONTROLS_THUMBNAILS_EVENTS_MEDIA_TYPE, + CONF_LIVE_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE, CONF_MEDIA_GALLERY, + CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE, CONF_MENU_BUTTONS_CAMERA_UI, CONF_OVERRIDES, + CONF_TIMELINE_EVENTS_MEDIA_TYPE, CONF_VIEW_INTERACTION_SECONDS, CONF_VIEW_SCAN_ACTIONS_UNTRIGGER, } from './const'; @@ -516,4 +520,20 @@ const UPGRADES = [ upgradeWithOverrides('media_viewer.auto_unmute', (data) => data === 'all' ? ['selected', 'visible'] : data === 'never' ? null : arrayify(data), ), + upgradeMoveToWithOverrides( + 'live.controls.thumbnails.media', + CONF_LIVE_CONTROLS_THUMBNAILS_EVENTS_MEDIA_TYPE, + ), + upgradeMoveToWithOverrides( + 'timeline.media', + CONF_TIMELINE_EVENTS_MEDIA_TYPE, + ), + upgradeMoveToWithOverrides( + 'live.controls.timeline.media', + CONF_LIVE_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE, + ), + upgradeMoveToWithOverrides( + 'media_viewer.controls.timeline.media', + CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE, + ), ]; diff --git a/src/config/types.ts b/src/config/types.ts index bbd36b2e..1ab1941f 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -626,26 +626,31 @@ const thumbnailsControlSchema = thumbnailsControlBaseSchema.extend({ }); export type ThumbnailsControlConfig = z.infer; +// ************************************************************************* +// Event Media Type Configuration +// ************************************************************************* +const eventsMediaTypeSchema = z.enum(['all', 'clips', 'snapshots']); + // ************************************************************************* // Timeline Configuration // ************************************************************************* const timelineCoreConfigDefault = { clustering_threshold: 3, - media: 'all' as const, + events_media_type: 'all' as const, window_seconds: 60 * 60, show_recordings: true, style: 'stack' as const, }; -const timelineMediaSchema = z.enum(['all', 'clips', 'snapshots']); - const timelineCoreConfigSchema = z.object({ clustering_threshold: z .number() .optional() .default(timelineCoreConfigDefault.clustering_threshold), - media: timelineMediaSchema.optional().default(timelineCoreConfigDefault.media), + events_media_type: eventsMediaTypeSchema + .optional() + .default(timelineCoreConfigDefault.events_media_type), window_seconds: z .number() .min(1 * 60) @@ -867,7 +872,8 @@ export type FrigateCardPTZConfig = z.infer; const liveThumbnailControlsDefaults = { ...thumbnailControlsDefaults, - media: 'all' as const, + media_type: 'events' as const, + events_media_type: 'all' as const, }; const liveConfigDefault = { @@ -899,9 +905,12 @@ const liveConfigDefault = { }; const livethumbnailsControlSchema = thumbnailsControlSchema.extend({ - media: z - .enum(['all', 'clips', 'snapshots']) - .default(liveConfigDefault.controls.thumbnails.media), + media_type: z + .enum(['events', 'recordings']) + .default(liveConfigDefault.controls.thumbnails.media_type), + events_media_type: eventsMediaTypeSchema.default( + liveConfigDefault.controls.thumbnails.events_media_type, + ), }); const liveOverridableConfigSchema = z diff --git a/src/const.ts b/src/const.ts index 92dc8064..f3f3298b 100644 --- a/src/const.ts +++ b/src/const.ts @@ -146,8 +146,8 @@ export const CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SIZE = `${CONF_MEDIA_VIEWER}.controls.thumbnails.size` as const; export const CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD = `${CONF_MEDIA_VIEWER}.controls.timeline.clustering_threshold` as const; -export const CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MEDIA = - `${CONF_MEDIA_VIEWER}.controls.timeline.media` as const; +export const CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE = + `${CONF_MEDIA_VIEWER}.controls.timeline.events_media_type` as const; export const CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MODE = `${CONF_MEDIA_VIEWER}.controls.timeline.mode` as const; export const CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_SHOW_RECORDINGS = @@ -189,8 +189,10 @@ export const CONF_LIVE_CONTROLS_PTZ_ORIENTATION = `${CONF_LIVE}.controls.ptz.orientation` as const; export const CONF_LIVE_CONTROLS_PTZ_POSITION = `${CONF_LIVE}.controls.ptz.position` as const; -export const CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA = - `${CONF_LIVE}.controls.thumbnails.media` as const; +export const CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA_TYPE = + `${CONF_LIVE}.controls.thumbnails.media_type` as const; +export const CONF_LIVE_CONTROLS_THUMBNAILS_EVENTS_MEDIA_TYPE = + `${CONF_LIVE}.controls.thumbnails.events_media_type` as const; export const CONF_LIVE_CONTROLS_THUMBNAILS_MODE = `${CONF_LIVE}.controls.thumbnails.mode` as const; export const CONF_LIVE_CONTROLS_THUMBNAILS_SIZE = @@ -205,8 +207,8 @@ 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}.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_EVENTS_MEDIA_TYPE = + `${CONF_LIVE}.controls.timeline.events_media_type` as const; export const CONF_LIVE_CONTROLS_TIMELINE_MODE = `${CONF_LIVE}.controls.timeline.mode` as const; export const CONF_LIVE_CONTROLS_TIMELINE_SHOW_RECORDINGS = @@ -256,7 +258,7 @@ const CONF_TIMELINE = 'timeline' as const; export const CONF_TIMELINE_WINDOW_SECONDS = `${CONF_TIMELINE}.window_seconds` as const; export const CONF_TIMELINE_CLUSTERING_THRESHOLD = `${CONF_TIMELINE}.clustering_threshold` as const; -export const CONF_TIMELINE_MEDIA = `${CONF_TIMELINE}.media` as const; +export const CONF_TIMELINE_EVENTS_MEDIA_TYPE = `${CONF_TIMELINE}.events_media_type` as const; export const CONF_TIMELINE_SHOW_RECORDINGS = `${CONF_TIMELINE}.show_recordings` as const; export const CONF_TIMELINE_STYLE = `${CONF_TIMELINE}.style` as const; export const CONF_TIMELINE_CONTROLS_THUMBNAILS_MODE = diff --git a/src/editor.ts b/src/editor.ts index d0db698f..fbd589a8 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -86,7 +86,8 @@ import { CONF_LIVE_CONTROLS_PTZ_MODE, CONF_LIVE_CONTROLS_PTZ_ORIENTATION, CONF_LIVE_CONTROLS_PTZ_POSITION, - CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA, + CONF_LIVE_CONTROLS_THUMBNAILS_EVENTS_MEDIA_TYPE, + CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA_TYPE, CONF_LIVE_CONTROLS_THUMBNAILS_MODE, CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_DETAILS, CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_DOWNLOAD_CONTROL, @@ -94,7 +95,7 @@ import { CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL, CONF_LIVE_CONTROLS_THUMBNAILS_SIZE, CONF_LIVE_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD, - CONF_LIVE_CONTROLS_TIMELINE_MEDIA, + CONF_LIVE_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE, CONF_LIVE_CONTROLS_TIMELINE_MODE, CONF_LIVE_CONTROLS_TIMELINE_SHOW_RECORDINGS, CONF_LIVE_CONTROLS_TIMELINE_STYLE, @@ -138,7 +139,7 @@ import { CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL, CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SIZE, CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD, - CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MEDIA, + CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE, CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MODE, CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_SHOW_RECORDINGS, CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_STYLE, @@ -174,7 +175,7 @@ import { CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL, CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL, CONF_TIMELINE_CONTROLS_THUMBNAILS_SIZE, - CONF_TIMELINE_MEDIA, + CONF_TIMELINE_EVENTS_MEDIA_TYPE, CONF_TIMELINE_SHOW_RECORDINGS, CONF_TIMELINE_STYLE, CONF_TIMELINE_WINDOW_SECONDS, @@ -452,15 +453,27 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor }, ]; - protected _thumbnailMedias: EditorSelectOption[] = [ + protected _thumbnailMediaTypes: EditorSelectOption[] = [ + { value: '', label: '' }, + { + value: 'events', + label: localize('config.common.controls.thumbnails.media_types.events'), + }, + { + value: 'recordings', + label: localize('config.common.controls.thumbnails.media_types.recordings'), + }, + ]; + + protected _thumbnailEventsMediaTypes: EditorSelectOption[] = [ { value: '', label: '' }, { value: 'clips', - label: localize('config.common.controls.thumbnails.medias.clips'), + label: localize('config.common.controls.thumbnails.events_media_types.clips'), }, { value: 'snapshots', - label: localize('config.common.controls.thumbnails.medias.snapshots'), + label: localize('config.common.controls.thumbnails.events_media_types.snapshots'), }, ]; @@ -498,11 +511,11 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor { value: 'url', label: localize('config.image.modes.url') }, ]; - protected _timelineMediaTypes: EditorSelectOption[] = [ + protected _timelineEventsMediaTypes: EditorSelectOption[] = [ { value: '', label: '' }, - { value: 'all', label: localize('config.common.timeline.medias.all') }, - { value: 'clips', label: localize('config.common.timeline.medias.clips') }, - { value: 'snapshots', label: localize('config.common.timeline.medias.snapshots') }, + { value: 'all', label: localize('config.common.timeline.events_media_types.all') }, + { value: 'clips', label: localize('config.common.timeline.events_media_types.clips') }, + { value: 'snapshots', label: localize('config.common.timeline.events_media_types.snapshots') }, ]; protected _timelineStyleTypes: EditorSelectOption[] = [ @@ -1149,7 +1162,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor * @param configPathStyle Timeline style config path. * @param configPathWindowSeconds Timeline window config path. * @param configPathClusteringThreshold Clustering threshold config path. - * @param configPathTimelineMedia Timeline media config path. + * @param configPathTimelineEventsMediaType Timeline media config path. * @param configPathShowRecordings Show recordings config path. * @param defaultShowRecordings Default value of show_recordings. * @returns A rendered template. @@ -1158,7 +1171,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor configPathStyle: string, configPathWindowSeconds: string, configPathClusteringThreshold: string, - configPathTimelineMedia: string, + configPathTimelineEventsMediaType: string, configPathShowRecordings: string, defaultShowRecordings: boolean, ): TemplateResult { @@ -1175,8 +1188,8 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor ${this._renderNumberInput(configPathClusteringThreshold, { label: localize(`config.common.${CONF_TIMELINE_CLUSTERING_THRESHOLD}`), })} - ${this._renderOptionSelector(configPathTimelineMedia, this._timelineMediaTypes, { - label: localize(`config.common.${CONF_TIMELINE_MEDIA}`), + ${this._renderOptionSelector(configPathTimelineEventsMediaType, this._timelineEventsMediaTypes, { + label: localize(`config.common.${CONF_TIMELINE_EVENTS_MEDIA_TYPE}`), })} ${this._renderSwitch(configPathShowRecordings, defaultShowRecordings, { label: localize(`config.common.${CONF_TIMELINE_SHOW_RECORDINGS}`), @@ -1188,7 +1201,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor * @param domain The submenu domain. * @param configPathWindowSeconds Timeline window config path. * @param configPathClusteringThreshold Clustering threshold config path. - * @param configPathTimelineMedia Timeline media config path. + * @param configPathTimelineEventsMediaType Timeline media config path. * @param configPathShowRecordings Show recordings config path. * @returns A rendered template. */ @@ -1198,7 +1211,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor configPathStyle: string, configPathWindowSeconds: string, configPathClusteringThreshold: string, - configPathTimelineMedia: string, + configPathTimelineEventsMediaType: string, configPathShowRecordings: string, showRecordingsDefault: boolean, ): TemplateResult | void { @@ -1214,7 +1227,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor configPathStyle, configPathWindowSeconds, configPathClusteringThreshold, - configPathTimelineMedia, + configPathTimelineEventsMediaType, configPathShowRecordings, showRecordingsDefault, )}`, @@ -1330,7 +1343,8 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor show_download_control: boolean; }, options?: { - configPathMedia?: string; + configPathMediaType?: string; + configPathEventsMediaType?: string; configPathMode?: string; }, ): TemplateResult | void { @@ -1349,12 +1363,21 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor }, )}` : html``} - ${options?.configPathMedia + ${options?.configPathMediaType ? html`${this._renderOptionSelector( - options.configPathMedia, - this._thumbnailMedias, + options.configPathMediaType, + this._thumbnailMediaTypes, { - label: localize('config.common.controls.thumbnails.media'), + label: localize('config.common.controls.thumbnails.media_type'), + }, + )}` + : html``} + ${options?.configPathEventsMediaType + ? html`${this._renderOptionSelector( + options.configPathEventsMediaType, + this._thumbnailEventsMediaTypes, + { + label: localize('config.common.controls.thumbnails.events_media_type'), }, )}` : html``} @@ -2101,7 +2124,8 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_DOWNLOAD_CONTROL, this._defaults.live.controls.thumbnails, { - configPathMedia: CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA, + configPathMediaType: CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA_TYPE, + configPathEventsMediaType: CONF_LIVE_CONTROLS_THUMBNAILS_EVENTS_MEDIA_TYPE, configPathMode: CONF_LIVE_CONTROLS_THUMBNAILS_MODE, }, )} @@ -2116,7 +2140,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_LIVE_CONTROLS_TIMELINE_STYLE, CONF_LIVE_CONTROLS_TIMELINE_WINDOW_SECONDS, CONF_LIVE_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD, - CONF_LIVE_CONTROLS_TIMELINE_MEDIA, + CONF_LIVE_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE, CONF_LIVE_CONTROLS_TIMELINE_SHOW_RECORDINGS, this._defaults.live.controls.timeline.show_recordings, )} @@ -2309,7 +2333,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_STYLE, CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_WINDOW_SECONDS, CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_CLUSTERING_THRESHOLD, - CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_MEDIA, + CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_EVENTS_MEDIA_TYPE, CONF_MEDIA_VIEWER_CONTROLS_TIMELINE_SHOW_RECORDINGS, this._defaults.media_viewer.controls.timeline.show_recordings, )} @@ -2347,7 +2371,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_TIMELINE_STYLE, CONF_TIMELINE_WINDOW_SECONDS, CONF_TIMELINE_CLUSTERING_THRESHOLD, - CONF_TIMELINE_MEDIA, + CONF_TIMELINE_EVENTS_MEDIA_TYPE, CONF_TIMELINE_SHOW_RECORDINGS, this._defaults.timeline.show_recordings, )} diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index bb246156..241f4e4c 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -18,8 +18,8 @@ "editor_label": "Cast Options", "method": "Cast method", "methods": { - "standard": "Standard", - "dashboard": "Dashboard" + "dashboard": "Dashboard", + "standard": "Standard" } }, "dependencies": { @@ -119,11 +119,16 @@ }, "thumbnails": { "editor_label": "Thumbnails", - "media": "Whether to show thumbnails of clips or snapshots", - "medias": { + "events_media_type": "Whether to show event thumbnails of clips or snapshots", + "events_media_types": { "clips": "Clip thumbnails", "snapshots": "Snapshot thumbnails" }, + "media_type": "Whether to show thumnails of events or recordings", + "media_types": { + "events": "Events", + "recordings": "Recordings" + }, "mode": "Thumbnails mode", "modes": { "above": "Thumbnails above", @@ -191,9 +196,9 @@ }, "timeline": { "clustering_threshold": "The count of events at which they are clustered (0=no clustering)", - "media": "The media the timeline displays", - "medias": { - "all": "All media types", + "events_media_type": "The events media the timeline displays", + "events_media_types": { + "all": "All event media types", "clips": "Clips", "snapshots": "Snapshots" }, @@ -238,27 +243,27 @@ "editor_label": "Live Controls", "ptz": { "editor_label": "PTZ", + "hide_home": "Hide home control", + "hide_pan_tilt": "Hide pan & tilt control", + "hide_zoom": "Hide zoom control", "mode": "Mode", "modes": { - "on": "On", + "auto": "Automatic", "off": "Off", - "auto": "Automatic" + "on": "On" }, "orientation": "Orientation", "orientations": { - "vertical": "Vertical", - "horizontal": "Horizontal" + "horizontal": "Horizontal", + "vertical": "Vertical" }, "position": "Position", "positions": { - "top-left": "Top left", - "top-right": "Top right", "bottom-left": "Bottom left", - "bottom-right": "Bottom right" - }, - "hide_zoom": "Hide zoom control", - "hide_pan_tilt": "Hide pan & tilt control", - "hide_home": "Hide home control" + "bottom-right": "Bottom right", + "top-left": "Top left", + "top-right": "Top right" + } } }, "draggable": "Live cameras view can be dragged/swiped", @@ -314,8 +319,8 @@ "camera_ui": "Camera user interface", "cameras": "Cameras", "clips": "Clips", - "download": "Download", "display_mode": "Display mode", + "download": "Download", "enabled": "Button enabled", "expand": "Expand", "frigate": "Frigate menu / Default view", @@ -387,6 +392,8 @@ "on": "On" }, "default": "Default view", + "interaction_seconds": "Seconds after user action to remain interacted with (0=never)", + "reset_after_interaction": "Reset to the default view after user interaction", "scan": { "actions": { "editor_label": "Scan mode actions", @@ -408,13 +415,11 @@ } }, "editor_label": "Scan mode", - "filter_selected_camera": "Only trigger on selected camera", "enabled": "Scan mode enabled", + "filter_selected_camera": "Only trigger on selected camera", "show_trigger_status": "Show pulsing border when triggered", "untrigger_seconds": "Seconds after inactive state change to untrigger" }, - "interaction_seconds": "Seconds after user action to remain interacted with (0=never)", - "reset_after_interaction": "Reset to the default view after user interaction", "update_cycle_camera": "Cycle through cameras when default view updates", "update_force": "Force card updates (ignore user interaction)", "update_seconds": "Refresh default view every X seconds (0=never)", @@ -433,8 +438,8 @@ } }, "display_modes": { - "single": "Show single media viewer", - "grid": "Show media viewer for each camera in a grid" + "grid": "Show media viewer for each camera in a grid", + "single": "Show single media viewer" }, "editor": { "add_new_camera": "Add new camera", @@ -528,9 +533,6 @@ "what": "What", "where": "Where" }, - "media_viewer": { - "unseekable": "Seek time not found in media" - }, "media_filter": { "all": "All", "camera": "Camera", @@ -560,6 +562,9 @@ }, "where": "Where" }, + "media_viewer": { + "unseekable": "Seek time not found in media" + }, "recording": { "camera": "Camera", "duration": "Duration", @@ -583,4 +588,4 @@ }, "select_date": "Choose date" } -} +} \ No newline at end of file diff --git a/src/localize/languages/it.json b/src/localize/languages/it.json index 570d1dc4..0a9b8e08 100644 --- a/src/localize/languages/it.json +++ b/src/localize/languages/it.json @@ -119,11 +119,16 @@ }, "thumbnails": { "editor_label": "Miniature", - "media": "Se mostrare miniature di clip o istantanee", - "medias": { + "events_media_type": "Se mostrare miniature di clip o istantanee", + "events_media_types": { "clips": "Miniature di clip", "snapshots": "Miniature istantanee" }, + "media_type": "", + "media_types": { + "events": "", + "recordings": "" + }, "mode": "Modalità miniatura", "modes": { "above": "Miniature sopra", @@ -191,8 +196,8 @@ }, "timeline": { "clustering_threshold": "Il conteggio degli eventi in cui sono raggruppati (0 = nessun clustering)", - "media": "I media vengono visualizzati la sequenza temporale", - "medias": { + "events_media_type": "I media vengono visualizzati la sequenza temporale", + "events_media_types": { "all": "Tutti i tipi di media", "clips": "Clip", "snapshots": "Istantanee" diff --git a/src/localize/languages/pt-BR.json b/src/localize/languages/pt-BR.json index 2909b41b..7b91adc2 100644 --- a/src/localize/languages/pt-BR.json +++ b/src/localize/languages/pt-BR.json @@ -119,11 +119,16 @@ }, "thumbnails": { "editor_label": "Miniaturas", - "media": "Se deve mostrar miniaturas de clipes ou snapshots", - "medias": { + "events_media_type": "Se deve mostrar miniaturas de clipes ou snapshots", + "events_media_types": { "clips": "Miniaturas de clipes", "snapshots": "Miniaturas de Snapshots" }, + "media_type": "", + "media_types": { + "events": "", + "recordings": "" + }, "mode": "Modo de miniaturas", "modes": { "above": "Miniaturas acima da mídia", @@ -191,8 +196,8 @@ }, "timeline": { "clustering_threshold": "A contagem de eventos nos quais eles são agrupados (0 = sem agrupamento)", - "media": "A mídia que a linha do tempo exibe", - "medias": { + "events_media_type": "A mídia que a linha do tempo exibe", + "events_media_types": { "all": "Todos os tipos de mídia", "clips": "Clipes", "snapshots": "Instantâneos" diff --git a/src/localize/languages/pt-PT.json b/src/localize/languages/pt-PT.json index e224ccf0..c342ed09 100644 --- a/src/localize/languages/pt-PT.json +++ b/src/localize/languages/pt-PT.json @@ -119,11 +119,16 @@ }, "thumbnails": { "editor_label": "Editor de titulos", - "media": "Mostrar miniaturas de clipes ou snapshots", - "medias": { + "events_media_type": "Mostrar miniaturas de clipes ou snapshots", + "events_media_types": { "clips": "Miniaturas de clipes", "snapshots": "Miniaturas de Snapshots" }, + "media_type": "", + "media_types": { + "events": "", + "recordings": "" + }, "mode": "Modos", "modes": { "above": "Miniaturas acima da mídia", @@ -191,8 +196,8 @@ }, "timeline": { "clustering_threshold": "A contagem de eventos nos quais eles são agrupados (0 = sem agrupamento)", - "media": "A mídia que a linha do tempo exibe", - "medias": { + "events_media_type": "A mídia que a linha do tempo exibe", + "events_media_types": { "all": "Todos os tipos de mídia", "clips": "Clipes", "snapshots": "Instantâneos" diff --git a/src/types.ts b/src/types.ts index 80527d0c..d95880ed 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,6 +4,7 @@ import { z } from 'zod'; export type ClipsOrSnapshots = 'clips' | 'snapshots'; export type ClipsOrSnapshotsOrAll = 'clips' | 'snapshots' | 'all'; +export type EventsOrRecordings = 'events' | 'recordings'; export class FrigateCardError extends Error { context?: unknown; diff --git a/src/utils/media-to-view.ts b/src/utils/media-to-view.ts index bfd58328..4383b29c 100644 --- a/src/utils/media-to-view.ts +++ b/src/utils/media-to-view.ts @@ -24,7 +24,7 @@ export const changeViewToRecentEventsForCameraAndDependents = async ( view: View, options?: { allCameras?: boolean; - mediaType?: ClipsOrSnapshotsOrAll; + eventsMediaType?: ClipsOrSnapshotsOrAll; targetView?: FrigateCardView; select?: ResultSelectType; }, @@ -37,7 +37,7 @@ export const changeViewToRecentEventsForCameraAndDependents = async ( } const queries = createQueriesForEventsView(cameraManager, cardWideConfig, cameraIDs, { - mediaType: options?.mediaType, + eventsMediaType: options?.eventsMediaType, }); if (!queries) { return; @@ -62,15 +62,15 @@ const createQueriesForEventsView = ( cardWideConfig: CardWideConfig, cameraIDs: Set, options?: { - mediaType?: ClipsOrSnapshotsOrAll; + eventsMediaType?: ClipsOrSnapshotsOrAll; }, ): EventMediaQueries | null => { const limit = cardWideConfig.performance?.features.media_chunk_size ?? MEDIA_CHUNK_SIZE_DEFAULT; const eventQueries = cameraManager.generateDefaultEventQueries(cameraIDs, { limit: limit, - ...(options?.mediaType === 'clips' && { hasClip: true }), - ...(options?.mediaType === 'snapshots' && { hasSnapshot: true }), + ...(options?.eventsMediaType === 'clips' && { hasClip: true }), + ...(options?.eventsMediaType === 'snapshots' && { hasSnapshot: true }), }); return eventQueries ? new EventMediaQueries(eventQueries) : null; }; @@ -91,7 +91,7 @@ export const changeViewToRecentRecordingForCameraAndDependents = async ( view: View, options?: { allCameras?: boolean; - targetView?: 'recording' | 'recordings'; + targetView?: FrigateCardView; select?: ResultSelectType; }, ): Promise => { diff --git a/tests/config-mgmt.test.ts b/tests/config-mgmt.test.ts index bba0efc3..702a2637 100644 --- a/tests/config-mgmt.test.ts +++ b/tests/config-mgmt.test.ts @@ -1590,5 +1590,105 @@ describe('should handle version specific upgrades', () => { }); }); }); + + describe('should rename thumbnails.media to thumbnails.events_media_type', () => { + it.each([['all' as const], ['clips' as const], ['snapshots' as const]])( + '%s', + (mediaEventType: string) => { + const config = { + live: { + controls: { + thumbnails: { + media: mediaEventType, + }, + }, + }, + }; + expect(upgradeConfig(config)).toBeTruthy(); + expect(config).toEqual({ + live: { + controls: { + thumbnails: { + events_media_type: mediaEventType, + }, + }, + }, + }); + }, + ); + }); + + describe('should rename timeline.media to timeline.events_media_type', () => { + it.each([['all' as const], ['clips' as const], ['snapshots' as const]])( + '%s', + (mediaEventType: string) => { + const config = { + timeline: { + media: mediaEventType, + }, + }; + expect(upgradeConfig(config)).toBeTruthy(); + expect(config).toEqual({ + timeline: { + events_media_type: mediaEventType, + }, + }); + }, + ); + }); + + describe('should rename live.controls.timeline.media to live.controls.timeline.events_media_type', () => { + it.each([['all' as const], ['clips' as const], ['snapshots' as const]])( + '%s', + (mediaEventType: string) => { + const config = { + live: { + controls: { + timeline: { + media: mediaEventType, + }, + }, + }, + }; + expect(upgradeConfig(config)).toBeTruthy(); + expect(config).toEqual({ + live: { + controls: { + timeline: { + events_media_type: mediaEventType, + }, + }, + }, + }); + }, + ); + }); + + describe('should rename media_viewer.controls.timeline.media to media_viewer.controls.timeline.events_media_type', () => { + it.each([['all' as const], ['clips' as const], ['snapshots' as const]])( + '%s', + (mediaEventType: string) => { + const config = { + media_viewer: { + controls: { + timeline: { + media: mediaEventType, + }, + }, + }, + }; + expect(upgradeConfig(config)).toBeTruthy(); + expect(config).toEqual({ + media_viewer: { + controls: { + timeline: { + events_media_type: mediaEventType, + }, + }, + }, + }); + }, + ); + }); }); }); diff --git a/tests/config/types.test.ts b/tests/config/types.test.ts index ef597865..d34b057f 100644 --- a/tests/config/types.test.ts +++ b/tests/config/types.test.ts @@ -77,7 +77,8 @@ describe('config defaults', () => { position: 'bottom-right', }, thumbnails: { - media: 'all', + media_type: 'events', + events_media_type: 'all', mode: 'right', show_details: true, show_download_control: true, @@ -87,7 +88,7 @@ describe('config defaults', () => { }, timeline: { clustering_threshold: 3, - media: 'all', + events_media_type: 'all', mode: 'none', show_recordings: true, style: 'ribbon', @@ -143,7 +144,7 @@ describe('config defaults', () => { }, timeline: { clustering_threshold: 3, - media: 'all', + events_media_type: 'all', mode: 'none', show_recordings: true, style: 'ribbon', @@ -268,7 +269,7 @@ describe('config defaults', () => { size: 100, }, }, - media: 'all', + events_media_type: 'all', show_recordings: true, style: 'stack', window_seconds: 3600, diff --git a/tests/utils/media-to-view.test.ts b/tests/utils/media-to-view.test.ts index 9cb614be..24ac2bf9 100644 --- a/tests/utils/media-to-view.test.ts +++ b/tests/utils/media-to-view.test.ts @@ -101,7 +101,7 @@ describe('changeViewToRecentEventsForCameraAndDependents', () => { {}, createView(), { - mediaType: 'clips', + eventsMediaType: 'clips', }, ); expect(elementHandler.viewHandler).not.toBeCalled(); @@ -222,7 +222,7 @@ describe('changeViewToRecentEventsForCameraAndDependents', () => { {}, createView(), { - mediaType: mediaType, + eventsMediaType: mediaType, }, ); diff --git a/vite.config.ts b/vite.config.ts index c068a35d..67ec51a5 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -10,10 +10,10 @@ export default defineConfig({ // Thresholds will automatically be updated as coverage improves to avoid // back-sliding. thresholdAutoUpdate: true, - statements: 72.72, - branches: 61.97, + statements: 72.73, + branches: 61.93, functions: 74.01, - lines: 72.62, + lines: 72.63, }, }, });