Allow camera capabilities to be configured.

This commit is contained in:
Dermot Duffy
2024-03-26 21:00:15 -07:00
parent 0c7260d95a
commit 95c44adcf1
63 changed files with 2308 additions and 1447 deletions
+9 -9
View File
@@ -324,7 +324,7 @@ export class FrigateCardLiveGrid extends LitElement {
}
protected _needsGrid(): boolean {
const cameraIDs = this.cameraManager?.getStore().getVisibleCameraIDs();
const cameraIDs = this.cameraManager?.getStore().getCameraIDsWithCapability('live');
return (
!!this.view?.isGrid() &&
!!this.view?.supportsMultipleDisplayModes() &&
@@ -343,8 +343,8 @@ export class FrigateCardLiveGrid extends LitElement {
if (!this.conditionsManagerEpoch || !this.nonOverriddenLiveConfig) {
return;
}
const cameraIDs = this.cameraManager?.getStore().getVisibleCameraIDs();
if (!cameraIDs || !this._needsGrid()) {
const cameraIDs = this.cameraManager?.getStore().getCameraIDsWithCapability('live');
if (!cameraIDs?.size || !this._needsGrid()) {
return this._renderCarousel();
}
return html`
@@ -416,8 +416,8 @@ export class FrigateCardLiveCarousel extends LitElement {
}
protected _getSelectedCameraIndex(): number {
const cameraIDs = this.cameraManager?.getStore().getVisibleCameraIDs();
if (!cameraIDs || !this.view || this.viewFilterCameraID) {
const cameraIDs = this.cameraManager?.getStore().getCameraIDsWithCapability('live');
if (!cameraIDs?.size || !this.view || this.viewFilterCameraID) {
// If the carousel is limited to a single cameraID, the first (only)
// element is always the selected one.
return 0;
@@ -481,7 +481,7 @@ export class FrigateCardLiveCarousel extends LitElement {
const cameraIDs = this.viewFilterCameraID
? new Set([this.viewFilterCameraID])
: this.cameraManager.getStore().getVisibleCameraIDs();
: this.cameraManager?.getStore().getCameraIDsWithCapability('live');
const slides: TemplateResult[] = [];
const cameraToSlide: Record<string, number> = {};
@@ -508,8 +508,8 @@ export class FrigateCardLiveCarousel extends LitElement {
}
protected _setViewHandler(ev: CustomEvent<CarouselSelected>): void {
const cameraIDs = this.cameraManager?.getStore().getVisibleCameraIDs();
if (cameraIDs && ev.detail.index !== this._getSelectedCameraIndex()) {
const cameraIDs = this.cameraManager?.getStore().getCameraIDsWithCapability('live');
if (cameraIDs?.size && ev.detail.index !== this._getSelectedCameraIndex()) {
this._setViewCameraID([...cameraIDs][ev.detail.index]);
}
}
@@ -597,7 +597,7 @@ export class FrigateCardLiveCarousel extends LitElement {
protected _getCameraIDsOfNeighbors(): [string | null, string | null] {
const cameraIDs = this.cameraManager
? [...this.cameraManager.getStore().getVisibleCameraIDs()]
? [...this.cameraManager?.getStore().getCameraIDsWithCapability('live')]
: [];
if (this.viewFilterCameraID || cameraIDs.length <= 1 || !this.view || !this.hass) {
return [null, null];
+14 -6
View File
@@ -159,14 +159,22 @@ export class FrigateCardSurround extends LitElement {
}
protected _getCameraIDsForTimeline(): Set<string> | null {
if (!this.view) {
if (!this.view || !this.cameraManager) {
return null;
}
if (this.view?.is('live')) {
return this.view.isGrid()
? this.cameraManager?.getStore().getVisibleCameraIDs() ?? null
: this.cameraManager?.getStore().getAllDependentCameras(this.view.camera) ??
null;
if (this.view.is('live')) {
const capabilitySearch = {
anyCapabilities: ['clips' as const, 'snapshots' as const, 'recordings' as const],
};
if (this.view.supportsMultipleDisplayModes() && this.view.isGrid()) {
return this.cameraManager
.getStore()
.getCameraIDsWithCapability(capabilitySearch);
} else {
return this.cameraManager
.getStore()
.getAllDependentCameras(this.view.camera, capabilitySearch);
}
}
if (this.view.isViewerView()) {
return this.view.query?.getQueryCameraIDs() ?? null;
+43 -71
View File
@@ -27,7 +27,7 @@ import {
import { CameraManager } from '../camera-manager/manager';
import { rangesOverlap } from '../camera-manager/range';
import { MediaQuery } from '../camera-manager/types';
import { convertRangeToCacheFriendlyTimes } from '../camera-manager/utils';
import { convertRangeToCacheFriendlyTimes } from '../camera-manager/utils/range-to-cache-friendly';
import {
FrigateCardTimelineItem,
TimelineDataSource,
@@ -72,7 +72,6 @@ import { MediaQueriesResults } from '../view/media-queries-results';
import { View } from '../view/view';
import './date-picker.js';
import { DatePickerEvent, FrigateCardDatePicker } from './date-picker.js';
import { dispatchMessageEvent } from './message.js';
import './thumbnail.js';
interface FrigateCardGroupData {
@@ -275,18 +274,11 @@ export class FrigateCardTimelineCore extends LitElement {
request.detail.view = this.view;
}
/**
* Master render method.
* @returns A rendered template.
*/
protected render(): TemplateResult | void {
const cameraIDs = this._getTimelineCameraIDs();
if (!this.hass || !this.view || !this.timelineConfig || !cameraIDs) {
if (!this.hass || !this.view || !this.timelineConfig || !this.cameraIDs?.size) {
return;
}
const capabilities = this.cameraManager?.getAggregateCameraCapabilities(cameraIDs);
const panMode = this._getEffectivePanMode();
const panTitle =
@@ -306,55 +298,43 @@ export class FrigateCardTimelineCore extends LitElement {
? 'mdi:play-box-lock'
: 'mdi:camera-lock';
return html` ${capabilities?.supportsTimeline
? html` <div
@frigate-card:timeline:thumbnail-data-request=${this._handleThumbnailDataRequest.bind(
this,
)}
class="timeline"
${ref(this._refTimeline)}
>
<div class="timeline-tools">
${this._shouldSupportSeeking()
? html` <ha-icon
.icon=${panIcon}
@click=${() => {
this._panMode =
panMode === 'pan'
? 'seek'
: panMode === 'seek'
? 'seek-in-media'
: panMode === 'seek-in-media'
? 'seek-in-camera'
: 'pan';
}}
aria-label="${panTitle}"
title="${panTitle}"
>
</ha-icon>`
: ''}
<frigate-card-date-picker
${ref(this._refDatePicker)}
@frigate-card:date-picker:change=${(ev: CustomEvent<DatePickerEvent>) => {
if (ev.detail.date) {
this._timeline?.moveTo(ev.detail.date);
}
return html` <div
@frigate-card:timeline:thumbnail-data-request=${this._handleThumbnailDataRequest.bind(
this,
)}
class="timeline"
${ref(this._refTimeline)}
>
<div class="timeline-tools">
${this._shouldSupportSeeking()
? html` <ha-icon
.icon=${panIcon}
@click=${() => {
this._panMode =
panMode === 'pan'
? 'seek'
: panMode === 'seek'
? 'seek-in-media'
: panMode === 'seek-in-media'
? 'seek-in-camera'
: 'pan';
}}
aria-label="${panTitle}"
title="${panTitle}"
>
</frigate-card-date-picker>
</div>
</div>`
: ''}`;
}
/**
* Get all the keys of the cameras in scope for this timeline.
* @returns A set of camera ids (may be empty).
*/
protected _getTimelineCameraIDs(): Set<string> | null {
return (
this.cameraIDs ?? this.cameraManager?.getStore().getVisibleCameraIDs() ?? null
);
</ha-icon>`
: ''}
<frigate-card-date-picker
${ref(this._refDatePicker)}
@frigate-card:date-picker:change=${(ev: CustomEvent<DatePickerEvent>) => {
if (ev.detail.date) {
this._timeline?.moveTo(ev.detail.date);
}
}}
>
</frigate-card-date-picker>
</div>
</div>`;
}
/**
@@ -555,7 +535,6 @@ export class FrigateCardTimelineCore extends LitElement {
stopEventFromActivatingCardWideActions(properties.event);
}
const timelineCameraIDs = this._getTimelineCameraIDs();
if (
this._ignoreClick ||
!this.hass ||
@@ -563,7 +542,8 @@ export class FrigateCardTimelineCore extends LitElement {
!this.view ||
!this.cameraManager ||
!this.cardWideConfig ||
!timelineCameraIDs ||
!this.cameraIDs ||
!this.cameraIDs.size ||
!this._timelineSource ||
!properties.what
) {
@@ -767,14 +747,13 @@ export class FrigateCardTimelineCore extends LitElement {
*/
protected _getGroups(): DataGroupCollectionType {
const groups: FrigateCardGroupData[] = [];
(this._getTimelineCameraIDs() ?? []).forEach((cameraID) => {
(this.cameraIDs ?? []).forEach((cameraID: string) => {
if (!this.hass || !this.cameraManager) {
return;
}
const cameraMetadata = this.cameraManager.getCameraMetadata(cameraID);
const cameraCapabilities = this.cameraManager.getCameraCapabilities(cameraID);
if (cameraMetadata && cameraCapabilities?.supportsTimeline) {
if (cameraMetadata) {
groups.push({
id: cameraID,
content: cameraMetadata.title,
@@ -1142,11 +1121,10 @@ export class FrigateCardTimelineCore extends LitElement {
changedProps.has('timelineConfig') ||
changedProps.has('cameraIDs')
) {
const cameraIDs = this._getTimelineCameraIDs();
if (cameraIDs && this.cameraManager && this.timelineConfig) {
if (this.cameraIDs?.size && this.cameraManager && this.timelineConfig) {
this._timelineSource = new TimelineDataSource(
this.cameraManager,
cameraIDs,
this.cameraIDs,
this.timelineConfig.events_media_type,
this.timelineConfig.show_recordings,
);
@@ -1191,12 +1169,6 @@ export class FrigateCardTimelineCore extends LitElement {
const groups = this._getGroups();
if (!groups.length) {
if (!this.mini) {
// Don't show an empty timeline, show a message instead.
dispatchMessageEvent(this, localize('error.timeline_no_cameras'), 'info', {
icon: 'mdi:chart-gantt',
});
}
return;
}
+3
View File
@@ -37,6 +37,9 @@ export class FrigateCardTimeline extends LitElement {
.timelineConfig=${this.timelineConfig}
.thumbnailConfig=${this.timelineConfig.controls.thumbnails}
.cameraManager=${this.cameraManager}
.cameraIDs=${this.cameraManager?.getStore().getCameraIDsWithCapability({
anyCapabilities: ['clips', 'snapshots', 'recordings'],
})}
.cardWideConfig=${this.cardWideConfig}
.itemClickAction=${this.timelineConfig.controls.thumbnails.mode === 'none'
? 'play'