Merge pull request #932 from dermotduffy/camera-capabilities

Don't show the timeline if no cameras support it
This commit is contained in:
Dermot Duffy
2023-02-11 07:36:23 -08:00
committed by GitHub
12 changed files with 148 additions and 129 deletions
+5 -2
View File
@@ -14,7 +14,7 @@ import {
RecordingQueryResultsMap, RecordingQueryResultsMap,
RecordingSegmentsQuery, RecordingSegmentsQuery,
RecordingSegmentsQueryResultsMap, RecordingSegmentsQueryResultsMap,
CameraManagerEngineCapabilities, CameraManagerCameraCapabilities,
CameraManagerMediaCapabilities, CameraManagerMediaCapabilities,
CameraManagerCameraMetadata, CameraManagerCameraMetadata,
} from './types'; } from './types';
@@ -100,6 +100,9 @@ export interface CameraManagerEngine {
cameraConfig: CameraConfig, cameraConfig: CameraConfig,
): CameraManagerCameraMetadata; ): CameraManagerCameraMetadata;
getCapabilities(): CameraManagerEngineCapabilities | null; getCameraCapabilities(
cameraConfig: CameraConfig,
): CameraManagerCameraCapabilities | null;
getMediaCapabilities(media: ViewMedia): CameraManagerMediaCapabilities | null; getMediaCapabilities(media: ViewMedia): CameraManagerMediaCapabilities | null;
} }
+8 -4
View File
@@ -13,7 +13,7 @@ import {
import { DateRange } from '../range'; import { DateRange } from '../range';
import { import {
CameraManagerCameraMetadata, CameraManagerCameraMetadata,
CameraManagerEngineCapabilities, CameraManagerCameraCapabilities,
CameraManagerMediaCapabilities, CameraManagerMediaCapabilities,
DataQuery, DataQuery,
Engine, Engine,
@@ -805,10 +805,14 @@ export class FrigateCameraManagerEngine implements CameraManagerEngine {
return seekMilliseconds / 1000; return seekMilliseconds / 1000;
} }
public getCapabilities(): CameraManagerEngineCapabilities { public getCameraCapabilities(
cameraConfig: CameraConfig,
): CameraManagerCameraCapabilities {
const isBirdseye = cameraConfig.frigate.camera_name === CAMERA_BIRDSEYE;
return { return {
canFavoriteEvents: true, canFavoriteEvents: !isBirdseye,
canFavoriteRecordings: false, canFavoriteRecordings: !isBirdseye,
supportsTimeline: !isBirdseye,
}; };
} }
+35 -17
View File
@@ -2,6 +2,7 @@ import { HomeAssistant } from 'custom-card-helpers';
import { CameraConfig, CardWideConfig } from '../types.js'; import { CameraConfig, CardWideConfig } from '../types.js';
import { allPromises, arrayify, setify } from '../utils/basic.js'; import { allPromises, arrayify, setify } from '../utils/basic.js';
import { import {
CameraManagerCameraCapabilities,
CameraManagerCameraMetadata, CameraManagerCameraMetadata,
CameraManagerCapabilities, CameraManagerCapabilities,
CameraManagerMediaCapabilities, CameraManagerMediaCapabilities,
@@ -312,22 +313,6 @@ export class CameraManager {
return engine.getMediaDownloadPath(cameraConfig, media); return engine.getMediaDownloadPath(cameraConfig, media);
} }
public getCapabilities(): CameraManagerCapabilities | null {
const engines = this._engineFactory.getAllEngines(this._cameras);
if (!engines) {
return null;
}
return {
canFavoriteEvents: engines.some(
(engine) => engine.getCapabilities()?.canFavoriteEvents,
),
canFavoriteRecordings: engines.some(
(engine) => engine.getCapabilities()?.canFavoriteRecordings,
),
};
}
public getMediaCapabilities(media: ViewMedia): CameraManagerMediaCapabilities | null { public getMediaCapabilities(media: ViewMedia): CameraManagerMediaCapabilities | null {
const engine = this._engineFactory.getEngineForMedia(this._cameras, media); const engine = this._engineFactory.getEngineForMedia(this._cameras, media);
if (!engine) { if (!engine) {
@@ -534,12 +519,45 @@ export class CameraManager {
public getCameraMetadata( public getCameraMetadata(
hass: HomeAssistant, hass: HomeAssistant,
cameraConfig?: CameraConfig, cameraID: string,
): CameraManagerCameraMetadata | null { ): CameraManagerCameraMetadata | null {
const cameraConfig = this._cameras.get(cameraID);
const engine = this._engineFactory.getEngineForCamera(cameraConfig); const engine = this._engineFactory.getEngineForCamera(cameraConfig);
if (!engine || !cameraConfig) { if (!engine || !cameraConfig) {
return null; return null;
} }
return engine.getCameraMetadata(hass, cameraConfig); return engine.getCameraMetadata(hass, cameraConfig);
} }
public getCameraCapabilities(
cameraID: string,
): CameraManagerCameraCapabilities | null {
const cameraConfig = this._cameras.get(cameraID);
if (!cameraConfig) {
return null;
}
const engine = this._engineFactory.getEngineForCamera(cameraConfig);
if (!engine) {
return null;
}
return engine.getCameraCapabilities(cameraConfig);
}
public getAggregateCameraCapabilities(
cameraIDs?: Set<string>,
): CameraManagerCapabilities {
const perCameraCapabilities = [...(cameraIDs ?? this._cameras.keys())].map(
(cameraID) => this.getCameraCapabilities(cameraID),
);
return {
canFavoriteEvents: perCameraCapabilities.some((cap) => cap?.canFavoriteEvents),
canFavoriteRecordings: perCameraCapabilities.some(
(cap) => cap?.canFavoriteRecordings,
),
supportsTimeline: perCameraCapabilities.some((cap) => cap?.supportsTimeline),
};
}
} }
+2 -1
View File
@@ -85,10 +85,11 @@ export interface MediaMetadata {
interface BaseCapabilities { interface BaseCapabilities {
canFavoriteEvents: boolean; canFavoriteEvents: boolean;
canFavoriteRecordings: boolean; canFavoriteRecordings: boolean;
supportsTimeline: boolean;
} }
export type CameraManagerCapabilities = BaseCapabilities; export type CameraManagerCapabilities = BaseCapabilities;
export type CameraManagerEngineCapabilities = BaseCapabilities; export type CameraManagerCameraCapabilities = BaseCapabilities;
export interface CameraManagerMediaCapabilities { export interface CameraManagerMediaCapabilities {
canFavorite: boolean; canFavorite: boolean;
} }
+7 -5
View File
@@ -399,12 +399,12 @@ class FrigateCard extends LitElement {
}); });
if (this._cameras && this._cameras.size > 1) { if (this._cameras && this._cameras.size > 1) {
const menuItems = Array.from(this._cameras, ([camera, config]) => { const menuItems = Array.from(this._cameras, ([cameraID, config]) => {
const action = createFrigateCardCustomAction('camera_select', { const action = createFrigateCardCustomAction('camera_select', {
camera: camera, camera: cameraID,
}); });
const metadata = this._hass const metadata = this._hass
? this._cameraManager?.getCameraMetadata(this._hass, config) ?? undefined ? this._cameraManager?.getCameraMetadata(this._hass, cameraID) ?? undefined
: undefined; : undefined;
return { return {
@@ -413,7 +413,7 @@ class FrigateCard extends LitElement {
entity: config.camera_entity, entity: config.camera_entity,
state_color: true, state_color: true,
title: metadata?.title, title: metadata?.title,
selected: this._view?.camera === camera, selected: this._view?.camera === cameraID,
...(action && { tap_action: action }), ...(action && { tap_action: action }),
}; };
}); });
@@ -1427,7 +1427,9 @@ class FrigateCard extends LitElement {
} else if (this._view?.is('live') && cameraEntity) { } else if (this._view?.is('live') && cameraEntity) {
media_content_id = `media-source://camera/${cameraEntity}`; media_content_id = `media-source://camera/${cameraEntity}`;
media_content_type = 'application/vnd.apple.mpegurl'; media_content_type = 'application/vnd.apple.mpegurl';
title = this._cameraManager.getCameraMetadata(this._hass, cameraConfig)?.title ?? null; title =
this._cameraManager.getCameraMetadata(this._hass, this._view.camera)?.title ??
null;
thumbnail = this._hass?.states[cameraEntity]?.attributes?.entity_picture ?? null; thumbnail = this._hass?.states[cameraEntity]?.attributes?.entity_picture ?? null;
} }
-1
View File
@@ -343,7 +343,6 @@ export class FrigateCardGalleryCore extends LitElement {
.hass=${this.hass} .hass=${this.hass}
.cameraManager=${this.cameraManager} .cameraManager=${this.cameraManager}
.media=${media} .media=${media}
.cameraConfig=${this.cameras?.get(media.getCameraID())}
.view=${this.view} .view=${this.view}
?details=${!!this.galleryConfig?.controls.thumbnails.show_details} ?details=${!!this.galleryConfig?.controls.thumbnails.show_details}
?show_favorite_control=${!!this.galleryConfig?.controls.thumbnails ?show_favorite_control=${!!this.galleryConfig?.controls.thumbnails
+6 -7
View File
@@ -489,7 +489,7 @@ export class FrigateCardLiveCarousel extends LitElement {
} }
protected _renderLive( protected _renderLive(
camera: string, cameraID: string,
cameraConfig: CameraConfig, cameraConfig: CameraConfig,
slideIndex: number, slideIndex: number,
): TemplateResult | void { ): TemplateResult | void {
@@ -501,7 +501,7 @@ export class FrigateCardLiveCarousel extends LitElement {
// <frigate-card-live-provider> is rendering right now. // <frigate-card-live-provider> is rendering right now.
const conditionState = { const conditionState = {
...this.conditionState, ...this.conditionState,
camera: camera, camera: cameraID,
}; };
const config = getOverriddenConfig( const config = getOverriddenConfig(
@@ -510,7 +510,7 @@ export class FrigateCardLiveCarousel extends LitElement {
conditionState, conditionState,
) as LiveConfig; ) as LiveConfig;
const cameraMetadata = this.cameraManager.getCameraMetadata(this.hass, cameraConfig); const cameraMetadata = this.cameraManager.getCameraMetadata(this.hass, cameraID);
return html` return html`
<div class="embla__slide"> <div class="embla__slide">
@@ -560,7 +560,6 @@ export class FrigateCardLiveCarousel extends LitElement {
if ( if (
!slides.length || !slides.length ||
!this.liveConfig || !this.liveConfig ||
!this.cameras ||
!this.view || !this.view ||
!this.hass || !this.hass ||
!this.cameraManager !this.cameraManager
@@ -578,15 +577,15 @@ export class FrigateCardLiveCarousel extends LitElement {
const cameraMetadataPrevious = prevID ? this.cameraManager.getCameraMetadata( const cameraMetadataPrevious = prevID ? this.cameraManager.getCameraMetadata(
this.hass, this.hass,
this.cameras.get(prevID), prevID,
) : null; ) : null;
const cameraMetadataCurrent = this.cameraManager.getCameraMetadata( const cameraMetadataCurrent = this.cameraManager.getCameraMetadata(
this.hass, this.hass,
this.cameras.get(this.view.camera), this.view.camera,
); );
const cameraMetadataNext = nextID ? this.cameraManager.getCameraMetadata( const cameraMetadataNext = nextID ? this.cameraManager.getCameraMetadata(
this.hass, this.hass,
this.cameras.get(nextID), nextID,
) : null; ) : null;
// Notes on the below: // Notes on the below:
+7 -9
View File
@@ -272,14 +272,12 @@ class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) {
protected willUpdate(changedProps: PropertyValues): void { protected willUpdate(changedProps: PropertyValues): void {
if (changedProps.has('cameras') && this.cameras) { if (changedProps.has('cameras') && this.cameras) {
this._cameraOptions = Array.from(this.cameras.entries()).map( this._cameraOptions = Array.from(this.cameras.keys()).map((cameraID) => ({
([cameraID, cameraConfig]) => ({ value: cameraID,
value: cameraID, label: this.hass
label: this.hass ? this.cameraManager?.getCameraMetadata(this.hass, cameraID)?.title ?? ''
? this.cameraManager?.getCameraMetadata(this.hass, cameraConfig)?.title ?? '' : '',
: '', }));
}),
);
} }
if (changedProps.has('cameraManager') && this.hass && this.cameraManager) { if (changedProps.has('cameraManager') && this.hass && this.cameraManager) {
@@ -413,7 +411,7 @@ class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) {
const areRecordings = !!( const areRecordings = !!(
this.view?.query && MediaQueriesClassifier.areRecordingQueries(this.view.query) this.view?.query && MediaQueriesClassifier.areRecordingQueries(this.view.query)
); );
const managerCapabilities = this.cameraManager?.getCapabilities(); const managerCapabilities = this.cameraManager?.getAggregateCameraCapabilities();
// Which media controls are shown depends on the view. // Which media controls are shown depends on the view.
const showFavoriteControl = areEvents const showFavoriteControl = areEvents
-1
View File
@@ -190,7 +190,6 @@ export class FrigateCardSurround extends LitElement {
.config=${this.thumbnailConfig} .config=${this.thumbnailConfig}
.cameraManager=${this.cameraManager} .cameraManager=${this.cameraManager}
.view=${this.view} .view=${this.view}
.cameras=${this.cameras}
.selected=${this.view.queryResults?.getSelectedIndex() ?? undefined} .selected=${this.view.queryResults?.getSelectedIndex() ?? undefined}
@frigate-card:view:change=${(ev: CustomEvent) => changeDrawer(ev, 'close')} @frigate-card:view:change=${(ev: CustomEvent) => changeDrawer(ev, 'close')}
@frigate-card:thumbnail-carousel:tap=${( @frigate-card:thumbnail-carousel:tap=${(
+1 -6
View File
@@ -39,9 +39,6 @@ export class FrigateCardThumbnailCarousel extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
public view?: Readonly<View>; public view?: Readonly<View>;
@property({ attribute: false })
public cameras?: Map<string, CameraConfig>;
@property({ attribute: false }) @property({ attribute: false })
public cameraManager?: CameraManager; public cameraManager?: CameraManager;
@@ -150,8 +147,7 @@ export class FrigateCardThumbnailCarousel extends LitElement {
*/ */
protected _renderThumbnail(index: number): TemplateResult | void { protected _renderThumbnail(index: number): TemplateResult | void {
const media = this.view?.queryResults?.getResult(index) ?? null; const media = this.view?.queryResults?.getResult(index) ?? null;
const cameraConfig = media ? this.cameras?.get(media.getCameraID()) : null; if (!media || !this.view) {
if (!media || !cameraConfig || !this.view) {
return; return;
} }
@@ -166,7 +162,6 @@ export class FrigateCardThumbnailCarousel extends LitElement {
.cameraManager=${this.cameraManager} .cameraManager=${this.cameraManager}
.hass=${this.hass} .hass=${this.hass}
.media=${media} .media=${media}
.cameraConfig=${cameraConfig}
.view=${this.view} .view=${this.view}
.seek=${seekTarget && media.includesTime(seekTarget) ? seekTarget : undefined} .seek=${seekTarget && media.includesTime(seekTarget) ? seekTarget : undefined}
?details=${!!this.config?.show_details} ?details=${!!this.config?.show_details}
+45 -46
View File
@@ -21,7 +21,7 @@ import { createFetchThumbnailTask, FetchThumbnailTaskArgs } from '../utils/thumb
import { View } from '../view/view.js'; import { View } from '../view/view.js';
import { Task, TaskStatus } from '@lit-labs/task'; import { Task, TaskStatus } from '@lit-labs/task';
import type { CameraConfig, ExtendedHomeAssistant } from '../types.js'; import type { ExtendedHomeAssistant } from '../types.js';
import { EventViewMedia, RecordingViewMedia, ViewMedia } from '../view/media.js'; import { EventViewMedia, RecordingViewMedia, ViewMedia } from '../view/media.js';
import { CameraManager } from '../camera-manager/manager.js'; import { CameraManager } from '../camera-manager/manager.js';
import { ViewMediaClassifier } from '../view/media-classifier.js'; import { ViewMediaClassifier } from '../view/media-classifier.js';
@@ -104,12 +104,12 @@ export class FrigateCardThumbnailFeatureEvent extends LitElement {
return html`${this.thumbnail return html`${this.thumbnail
? renderTask( ? renderTask(
this, this,
this._embedThumbnailTask, this._embedThumbnailTask,
(embeddedThumbnail: string | null) => (embeddedThumbnail: string | null) =>
embeddedThumbnail ? html`<img src="${embeddedThumbnail}" />` : html``, embeddedThumbnail ? html`<img src="${embeddedThumbnail}" />` : html``,
{ inProgressFunc: () => imageOff }, { inProgressFunc: () => imageOff },
) )
: imageOff} `; : imageOff} `;
} }
@@ -162,7 +162,7 @@ export class FrigateCardThumbnailDetailsEvent extends LitElement {
return html` <div class="left"> return html` <div class="left">
${what ? html`<div class="larger">${prettifyTitle(what.join(', '))}</div>` : ``} ${what ? html`<div class="larger">${prettifyTitle(what.join(', '))}</div>` : ``}
${startTime ${startTime
? html` <div> ? html` <div>
<span class="heading">${localize('event.start')}:</span> <span class="heading">${localize('event.start')}:</span>
<span>${format(startTime, 'HH:mm:ss')}</span> <span>${format(startTime, 'HH:mm:ss')}</span>
</div> </div>
@@ -170,17 +170,17 @@ export class FrigateCardThumbnailDetailsEvent extends LitElement {
<span class="heading">${localize('event.duration')}:</span> <span class="heading">${localize('event.duration')}:</span>
<span <span
>${endTime >${endTime
? getDurationString(startTime, endTime) ? getDurationString(startTime, endTime)
: localize('event.in_progress')}</span : localize('event.in_progress')}</span
> >
</div>` </div>`
: ``} : ``}
${this.seek ${this.seek
? html` <div> ? html` <div>
<span class="heading">${localize('event.seek')}</span> <span class="heading">${localize('event.seek')}</span>
<span>${format(this.seek, 'HH:mm:ss')}</span> <span>${format(this.seek, 'HH:mm:ss')}</span>
</div>` </div>`
: html``} : html``}
</div> </div>
${score ${score
? html`<div class="right"> ? html`<div class="right">
@@ -213,11 +213,11 @@ export class FrigateCardThumbnailDetailsRecording extends LitElement {
return html`<div class="left"> return html`<div class="left">
<div class="larger">${this.cameraTitle ?? ''}</div> <div class="larger">${this.cameraTitle ?? ''}</div>
${this.seek ${this.seek
? html` <div> ? html` <div>
<span class="heading">${localize('recording.seek')}</span> <span class="heading">${localize('recording.seek')}</span>
<span>${format(this.seek, 'HH:mm:ss')}</span> <span>${format(this.seek, 'HH:mm:ss')}</span>
</div>` </div>`
: html``} : html``}
</div> </div>
${eventCount !== null ${eventCount !== null
? html`<div class="right"> ? html`<div class="right">
@@ -245,9 +245,6 @@ export class FrigateCardThumbnail extends LitElement {
@property({ attribute: true }) @property({ attribute: true })
public media?: ViewMedia; public media?: ViewMedia;
@property({ attribute: false })
public cameraConfig?: CameraConfig;
@property({ attribute: true, type: Boolean }) @property({ attribute: true, type: Boolean })
public details = false; public details = false;
@@ -268,7 +265,7 @@ export class FrigateCardThumbnail extends LitElement {
* @returns A template to display to the user. * @returns A template to display to the user.
*/ */
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
if (!this.media || !this.cameraConfig || !this.cameraManager || !this.hass) { if (!this.media || !this.cameraManager || !this.hass) {
return; return;
} }
@@ -293,7 +290,10 @@ export class FrigateCardThumbnail extends LitElement {
this.hass && this.hass &&
this.cameraManager?.getMediaCapabilities(this.media)?.canFavorite; this.cameraManager?.getMediaCapabilities(this.media)?.canFavorite;
const cameraTitle = this.cameraManager.getCameraMetadata(this.hass, this.cameraConfig)?.title; const cameraTitle = this.cameraManager.getCameraMetadata(
this.hass,
this.media.getCameraID(),
)?.title;
return html` ${ViewMediaClassifier.isEvent(this.media) return html` ${ViewMediaClassifier.isEvent(this.media)
? html`<frigate-card-thumbnail-feature-event ? html`<frigate-card-thumbnail-feature-event
@@ -303,50 +303,50 @@ export class FrigateCardThumbnail extends LitElement {
.thumbnail=${thumbnail ?? undefined} .thumbnail=${thumbnail ?? undefined}
></frigate-card-thumbnail-feature-event>` ></frigate-card-thumbnail-feature-event>`
: ViewMediaClassifier.isRecording(this.media) : ViewMediaClassifier.isRecording(this.media)
? html`<frigate-card-thumbnail-feature-recording ? html`<frigate-card-thumbnail-feature-recording
aria-label="${title ?? ''}" aria-label="${title ?? ''}"
title="${title ?? ''}" title="${title ?? ''}"
.cameraTitle=${this.details ? undefined : cameraTitle} .cameraTitle=${this.details ? undefined : cameraTitle}
.date=${this.media.getStartTime() ?? undefined} .date=${this.media.getStartTime() ?? undefined}
></frigate-card-thumbnail-feature-recording>` ></frigate-card-thumbnail-feature-recording>`
: html``} : html``}
${shouldShowFavoriteControl ${shouldShowFavoriteControl
? html` <ha-icon ? html` <ha-icon
class="${classMap(starClasses)}" class="${classMap(starClasses)}"
icon=${this.media.isFavorite() ? 'mdi:star' : 'mdi:star-outline'} icon=${this.media.isFavorite() ? 'mdi:star' : 'mdi:star-outline'}
title=${localize('thumbnail.retain_indefinitely')} title=${localize('thumbnail.retain_indefinitely')}
@click=${async (ev: Event) => { @click=${async (ev: Event) => {
stopEventFromActivatingCardWideActions(ev); stopEventFromActivatingCardWideActions(ev);
if (this.hass && this.media) { if (this.hass && this.media) {
try { try {
await this.cameraManager?.favoriteMedia( await this.cameraManager?.favoriteMedia(
this.hass, this.hass,
this.media, this.media,
!this.media?.isFavorite(), !this.media?.isFavorite(),
); );
} catch (e) { } catch (e) {
errorToConsole(e as Error); errorToConsole(e as Error);
return; return;
}
this.requestUpdate();
} }
this.requestUpdate(); }}
}
}}
/></ha-icon>` /></ha-icon>`
: ``} : ``}
${this.details && ViewMediaClassifier.isEvent(this.media) ${this.details && ViewMediaClassifier.isEvent(this.media)
? html`<frigate-card-thumbnail-details-event ? html`<frigate-card-thumbnail-details-event
.media=${this.media ?? undefined} .media=${this.media ?? undefined}
.seek=${this.seek} .seek=${this.seek}
></frigate-card-thumbnail-details-event>` ></frigate-card-thumbnail-details-event>`
: this.details && ViewMediaClassifier.isRecording(this.media) : this.details && ViewMediaClassifier.isRecording(this.media)
? html`<frigate-card-thumbnail-details-recording ? html`<frigate-card-thumbnail-details-recording
.media=${this.media ?? undefined} .media=${this.media ?? undefined}
.cameraTitle=${cameraTitle} .cameraTitle=${cameraTitle}
.seek=${this.seek} .seek=${this.seek}
></frigate-card-thumbnail-details-recording>` ></frigate-card-thumbnail-details-recording>`
: html``} : html``}
${shouldShowTimelineControl ${shouldShowTimelineControl
? html`<ha-icon ? html`<ha-icon
class="timeline" class="timeline"
icon="mdi:target" icon="mdi:target"
title=${localize('thumbnail.timeline')} title=${localize('thumbnail.timeline')}
@@ -364,10 +364,9 @@ export class FrigateCardThumbnail extends LitElement {
}) })
.removeContext('timeline') .removeContext('timeline')
.dispatchChangeEvent(this); .dispatchChangeEvent(this);
} }}
}
></ha-icon>` ></ha-icon>`
: ''}`; : ''}`;
} }
/** /**
+32 -30
View File
@@ -149,7 +149,6 @@ export class FrigateCardTimelineThumbnail extends LitElement {
return html` <frigate-card-thumbnail return html` <frigate-card-thumbnail
.hass=${dataRequest.hass} .hass=${dataRequest.hass}
.cameraManager=${dataRequest.cameraManager} .cameraManager=${dataRequest.cameraManager}
.cameraConfig=${dataRequest.cameraConfig}
.media=${dataRequest.media} .media=${dataRequest.media}
.view=${dataRequest.view} .view=${dataRequest.view}
?details=${this.details} ?details=${this.details}
@@ -259,26 +258,34 @@ export class FrigateCardTimelineCore extends LitElement {
if (!this.hass || !this.view || !this.timelineConfig) { if (!this.hass || !this.view || !this.timelineConfig) {
return; return;
} }
return html`<div const capabilities = this.cameraManager?.getAggregateCameraCapabilities(
@frigate-card:timeline:thumbnail-data-request=${this._handleThumbnailDataRequest.bind( this._getTimelineCameraIDs(),
this, );
)}
class="timeline" return html` ${capabilities?.supportsTimeline
${ref(this._refTimeline)} ? html` <div
> @frigate-card:timeline:thumbnail-data-request=${this._handleThumbnailDataRequest.bind(
<ha-icon this,
class="lock" )}
.icon=${`mdi:${this._locked ? 'lock' : 'lock-open-variant'}`} class="timeline"
@click=${() => { ${ref(this._refTimeline)}
this._locked = !this._locked; >
}} <ha-icon
aria-label="${this._locked class="lock"
? localize('timeline.unlock') .icon=${`mdi:${this._locked ? 'lock' : 'lock-open-variant'}`}
: localize('timeline.lock')}" @click=${() => {
title="${this._locked ? localize('timeline.unlock') : localize('timeline.lock')}" this._locked = !this._locked;
> }}
</ha-icon> aria-label="${this._locked
</div>`; ? localize('timeline.unlock')
: localize('timeline.lock')}"
title="${this._locked
? localize('timeline.unlock')
: localize('timeline.lock')}"
>
</ha-icon>
</div>`
: ''}`;
} }
/** /**
@@ -686,15 +693,10 @@ export class FrigateCardTimelineCore extends LitElement {
if (!this.hass || !cameraConfig || !this.cameraManager) { if (!this.hass || !cameraConfig || !this.cameraManager) {
return; return;
} }
const cameraMetadata = this.cameraManager.getCameraMetadata( const cameraMetadata = this.cameraManager.getCameraMetadata(this.hass, cameraID);
this.hass, const cameraCapabilities = this.cameraManager.getCameraCapabilities(cameraID);
cameraConfig,
); if (cameraMetadata && cameraCapabilities?.supportsTimeline) {
if (
cameraMetadata &&
cameraConfig.frigate.camera_name &&
cameraConfig.frigate.camera_name !== CAMERA_BIRDSEYE
) {
groups.push({ groups.push({
id: cameraID, id: cameraID,
content: cameraMetadata.title, content: cameraMetadata.title,