Set the correct view depending on media filter selections.

This commit is contained in:
Dermot Duffy
2023-01-29 13:31:21 -08:00
parent 32edaee192
commit b1f56dd02c
6 changed files with 145 additions and 36 deletions
+2 -1
View File
@@ -25,7 +25,8 @@ export const getRecordingTitle = (
cameraConfig: CameraConfig, cameraConfig: CameraConfig,
recording: FrigateRecording, recording: FrigateRecording,
): string => { ): string => {
return `${prettifyTitle(cameraConfig.frigate.camera_name)} ${formatDateAndTime( const cameraName = prettifyTitle(cameraConfig.frigate.camera_name ?? '');
return `${cameraName ? `${cameraName} ` : ''}${formatDateAndTime(
recording.startTime, recording.startTime,
)}`; )}`;
}; };
-1
View File
@@ -94,7 +94,6 @@ import { ViewContext } from 'view';
import { CameraManager } from './camera/manager.js'; import { CameraManager } from './camera/manager.js';
import { setLowPerformanceProfile, setPerformanceCSSStyles } from './performance.js'; import { setLowPerformanceProfile, setPerformanceCSSStyles } from './performance.js';
import { CameraManagerEngineFactory } from './camera/engine-factory.js'; import { CameraManagerEngineFactory } from './camera/engine-factory.js';
import { RequestCache } from './camera/cache.js';
/** A note on media callbacks: /** A note on media callbacks:
* *
+12
View File
@@ -232,6 +232,7 @@ export class FrigateCardMediaFilterCore extends LitElement {
.label=${localize('media_filter.media_type')} .label=${localize('media_filter.media_type')}
.items=${this._mediaTypeOptions} .items=${this._mediaTypeOptions}
.allowCustomValue=${false} .allowCustomValue=${false}
.value=${this.defaults?.mediaType}
@value-changed=${this._valueChangedHandler.bind(this)} @value-changed=${this._valueChangedHandler.bind(this)}
></ha-combo-box>` ></ha-combo-box>`
: ''} : ''}
@@ -242,6 +243,7 @@ export class FrigateCardMediaFilterCore extends LitElement {
.label=${localize('media_filter.when')} .label=${localize('media_filter.when')}
.items=${this._whenOptions} .items=${this._whenOptions}
.allowCustomValue=${false} .allowCustomValue=${false}
.value=${this.defaults?.when}
@value-changed=${this._valueChangedHandler.bind(this)} @value-changed=${this._valueChangedHandler.bind(this)}
></ha-combo-box>` ></ha-combo-box>`
: ''} : ''}
@@ -252,6 +254,9 @@ export class FrigateCardMediaFilterCore extends LitElement {
.label=${localize('media_filter.camera')} .label=${localize('media_filter.camera')}
.items=${this._cameraOptions} .items=${this._cameraOptions}
.allowCustomValue=${false} .allowCustomValue=${false}
.value=${this.defaults?.cameraIDs?.size === 1
? [...this.defaults.cameraIDs][0]
: undefined}
@value-changed=${this._valueChangedHandler.bind(this)} @value-changed=${this._valueChangedHandler.bind(this)}
></ha-combo-box>` ></ha-combo-box>`
: ''} : ''}
@@ -262,6 +267,9 @@ export class FrigateCardMediaFilterCore extends LitElement {
.label=${localize('media_filter.what')} .label=${localize('media_filter.what')}
.items="${this._whatOptions}" .items="${this._whatOptions}"
.allowCustomValue=${false} .allowCustomValue=${false}
.value=${this.defaults?.what?.size === 1
? [...this.defaults.what][0]
: undefined}
@value-changed=${this._valueChangedHandler.bind(this)} @value-changed=${this._valueChangedHandler.bind(this)}
></ha-combo-box>` ></ha-combo-box>`
: ''} : ''}
@@ -272,6 +280,9 @@ export class FrigateCardMediaFilterCore extends LitElement {
.label=${localize('media_filter.where')} .label=${localize('media_filter.where')}
.items=${this._whereOptions} .items=${this._whereOptions}
.allowCustomValue=${false} .allowCustomValue=${false}
.value=${this.defaults?.where?.size === 1
? [...this.defaults.where][0]
: undefined}
@value-changed=${this._valueChangedHandler.bind(this)} @value-changed=${this._valueChangedHandler.bind(this)}
></ha-combo-box>` ></ha-combo-box>`
: ''} : ''}
@@ -283,6 +294,7 @@ export class FrigateCardMediaFilterCore extends LitElement {
.label=${localize('media_filter.favorite')} .label=${localize('media_filter.favorite')}
.items=${this._favoriteOptions} .items=${this._favoriteOptions}
.allowCustomValue=${false} .allowCustomValue=${false}
.value=${this.defaults?.favorite}
@value-changed=${this._valueChangedHandler.bind(this)} @value-changed=${this._valueChangedHandler.bind(this)}
></ha-combo-box> ></ha-combo-box>
` `
+107 -7
View File
@@ -16,9 +16,7 @@ import {
unsafeCSS, unsafeCSS,
} from 'lit'; } from 'lit';
import { customElement, property } from 'lit/decorators.js'; import { customElement, property } from 'lit/decorators.js';
import { import { CameraManager } from '../camera/manager';
CameraManager,
} from '../camera/manager';
import { DateRange } from '../camera/range'; import { DateRange } from '../camera/range';
import { CameraConfig, ExtendedHomeAssistant } from '../types'; import { CameraConfig, ExtendedHomeAssistant } from '../types';
import { getCameraTitle } from '../utils/camera'; import { getCameraTitle } from '../utils/camera';
@@ -44,6 +42,8 @@ import parse from 'date-fns/parse';
import endOfMonth from 'date-fns/endOfMonth'; import endOfMonth from 'date-fns/endOfMonth';
import mediaFilterStyle from '../scss/media-filter.scss'; import mediaFilterStyle from '../scss/media-filter.scss';
import { MediaQueriesClassifier } from '../view/media-queries-classifier'; import { MediaQueriesClassifier } from '../view/media-queries-classifier';
import uniqWith from 'lodash-es/uniqWith';
import isEqual from 'lodash-es/isEqual';
@customElement('frigate-card-media-filter') @customElement('frigate-card-media-filter')
export class FrigateCardMediaFilter extends LitElement { export class FrigateCardMediaFilter extends LitElement {
@@ -114,21 +114,33 @@ export class FrigateCardMediaFilter extends LitElement {
const convertedTime = this._convertWhenToDateRange(mediaFilter.when); const convertedTime = this._convertWhenToDateRange(mediaFilter.when);
const convertedFavorite = this._convertFavoriteToBoolean(mediaFilter.favorite); const convertedFavorite = this._convertFavoriteToBoolean(mediaFilter.favorite);
const cameraIDs = mediaFilter.cameraIDs ?? new Set(this.cameras.keys());
// A note on views:
// - In the below, if the user selects a camera to view media for, the main
// view camera is also set to that value (e.g. a user browsing the
// gallery, chooses a different camera in the media filter, then
// subsequently chooses the live button -- they would expect the live view
// for that filtered camera not the prior camera).
// - Similarly, if the user chooses clips or snapshots, set the actual view
// to 'clips' or 'snapshots' in order to ensure the right icon is shown as
// selected in the menu.
if ( if (
mediaFilter.mediaType === MediaFilterMediaType.Clips || mediaFilter.mediaType === MediaFilterMediaType.Clips ||
mediaFilter.mediaType === MediaFilterMediaType.Snapshots mediaFilter.mediaType === MediaFilterMediaType.Snapshots
) { ) {
const query: EventQuery = { const query: EventQuery = {
type: QueryType.Event, type: QueryType.Event,
cameraIDs: mediaFilter.cameraIDs ?? new Set(this.cameras.keys()), cameraIDs: cameraIDs,
...(mediaFilter.what && { what: mediaFilter.what }), ...(mediaFilter.what && { what: mediaFilter.what }),
...(mediaFilter.where && { where: mediaFilter.where }), ...(mediaFilter.where && { where: mediaFilter.where }),
...(convertedFavorite !== null && { favorite: convertedFavorite }), ...(convertedFavorite !== null && { favorite: convertedFavorite }),
...(convertedTime && { start: convertedTime.start, end: convertedTime.end }), ...(convertedTime && { start: convertedTime.start, end: convertedTime.end }),
...(this.mediaLimit && { limit: this.mediaLimit }), ...(this.mediaLimit && { limit: this.mediaLimit }),
...(mediaFilter.mediaType === MediaFilterMediaType.Clips && { hasClip: true }), ...(mediaFilter.mediaType === MediaFilterMediaType.Clips && { hasClip: true }),
...(mediaFilter.mediaType === MediaFilterMediaType.Snapshots && { hasSnapshot: true }) ...(mediaFilter.mediaType === MediaFilterMediaType.Snapshots && {
hasSnapshot: true,
}),
}; };
( (
@@ -140,13 +152,20 @@ export class FrigateCardMediaFilter extends LitElement {
this.view, this.view,
{ {
query: new EventMediaQueries([query]), query: new EventMediaQueries([query]),
// See 'A note on views' above for these two arguments.
...(cameraIDs.size === 1 && { targetCameraID: [...cameraIDs][0] }),
targetView:
mediaFilter.mediaType === MediaFilterMediaType.Clips
? 'clips'
: 'snapshots',
}, },
) )
)?.dispatchChangeEvent(this); )?.dispatchChangeEvent(this);
} else if (mediaFilter.mediaType === MediaFilterMediaType.Recordings) { } else if (mediaFilter.mediaType === MediaFilterMediaType.Recordings) {
const query: RecordingQuery = { const query: RecordingQuery = {
type: QueryType.Recording, type: QueryType.Recording,
cameraIDs: mediaFilter.cameraIDs ?? new Set(this.cameras.keys()), cameraIDs: cameraIDs,
...(convertedTime && { start: convertedTime.start, end: convertedTime.end }), ...(convertedTime && { start: convertedTime.start, end: convertedTime.end }),
}; };
@@ -159,6 +178,10 @@ export class FrigateCardMediaFilter extends LitElement {
this.view, this.view,
{ {
query: new RecordingMediaQueries([query]), query: new RecordingMediaQueries([query]),
// See 'A note on views' above for these two arguments.
...(cameraIDs.size === 1 && { targetCameraID: [...cameraIDs][0] }),
targetView: 'recordings',
}, },
) )
)?.dispatchChangeEvent(this); )?.dispatchChangeEvent(this);
@@ -184,6 +207,82 @@ export class FrigateCardMediaFilter extends LitElement {
} }
} }
protected _getDefaultsFromView(): MediaFilterCoreSelection | undefined {
if (!this.view) {
return undefined;
}
let mediaType: MediaFilterMediaType | undefined;
let cameraIDs: Set<string> | undefined;
let what: Set<string> | undefined;
let where: Set<string> | undefined;
let favorite: boolean | undefined;
if (MediaQueriesClassifier.areEventQueries(this.view.query)) {
const queries = this.view.query.getQueries();
if (!queries) {
return;
}
const hasClips = uniqWith(
queries.map((query) => query.hasClip),
isEqual,
);
const hasSnapshots = uniqWith(
queries.map((query) => query.hasSnapshot),
isEqual,
);
if (hasClips.length === 1 && hasSnapshots.length === 1) {
mediaType = !!hasClips[0]
? MediaFilterMediaType.Clips
: !!hasSnapshots[0]
? MediaFilterMediaType.Snapshots
: undefined;
}
const cameraIDSets = uniqWith(
queries.map((query) => query.cameraIDs),
isEqual,
);
if (cameraIDSets.length === 1) {
cameraIDs = queries[0].cameraIDs;
}
const whatSets = uniqWith(
queries.map((query) => query.what),
isEqual,
);
if (whatSets.length === 1) {
what = queries[0].what;
}
const whereSets = uniqWith(
queries.map((query) => query.where),
isEqual,
);
if (whereSets.length === 1) {
where = queries[0].where;
}
const favoriteValues = uniqWith(
queries.map((query) => query.favorite),
isEqual,
);
if (favoriteValues.length === 1) {
favorite = queries[0].favorite;
}
}
return {
...(mediaType && { mediaType: mediaType }),
...(cameraIDs && { cameraIDs: cameraIDs }),
...(what && { what: what }),
...(where && { where: where }),
...(favorite !== undefined && {
favorite: favorite
? MediaFilterCoreFavoriteSelection.Favorite
: MediaFilterCoreFavoriteSelection.NotFavorite,
}),
};
}
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
const areEvents = !!( const areEvents = !!(
this.view?.query && MediaQueriesClassifier.areEventQueries(this.view.query) this.view?.query && MediaQueriesClassifier.areEventQueries(this.view.query)
@@ -203,6 +302,7 @@ export class FrigateCardMediaFilter extends LitElement {
? !!managerCapabilities?.canFavoriteRecordings ? !!managerCapabilities?.canFavoriteRecordings
: false, : false,
}; };
const defaults = this._getDefaultsFromView();
return html` <frigate-card-media-filter-core return html` <frigate-card-media-filter-core
.hass=${this.hass} .hass=${this.hass}
@@ -211,6 +311,7 @@ export class FrigateCardMediaFilter extends LitElement {
.whatOptions=${this._mediaMetadataController?.whatOptions} .whatOptions=${this._mediaMetadataController?.whatOptions}
.whereOptions=${this._mediaMetadataController?.whereOptions} .whereOptions=${this._mediaMetadataController?.whereOptions}
.controls=${controls} .controls=${controls}
.defaults=${defaults}
@frigate-card:media-filter-core:change=${this._mediaFilterHandler.bind(this)} @frigate-card:media-filter-core:change=${this._mediaFilterHandler.bind(this)}
> >
</frigate-card-media-filter-core>`; </frigate-card-media-filter-core>`;
@@ -249,7 +350,6 @@ export class MediaMetadataController implements ReactiveController {
errorToConsole(e as Error); errorToConsole(e as Error);
return; return;
} }
if (!metadata) { if (!metadata) {
return; return;
} }
+1
View File
@@ -88,6 +88,7 @@ export class FrigateCardSurround extends LitElement {
this.cameras, this.cameras,
this.view, this.view,
{ {
targetView: this.view.view,
mediaType: this.fetchMedia, mediaType: this.fetchMedia,
}, },
); );
+21 -25
View File
@@ -45,6 +45,7 @@ export const createViewForEvents = async (
query?: EventMediaQueries; query?: EventMediaQueries;
cameraIDs?: Set<string>; cameraIDs?: Set<string>;
mediaType?: ClipsOrSnapshotsOrAll; mediaType?: ClipsOrSnapshotsOrAll;
targetCameraID?: string;
targetView?: FrigateCardView; targetView?: FrigateCardView;
limit?: number; limit?: number;
}, },
@@ -72,17 +73,11 @@ export const createViewForEvents = async (
return null; return null;
} }
return executeMediaQueryForView( return executeMediaQueryForView(element, hass, cameraManager, view, query, {
element,
hass,
cameraManager,
view,
query,
options?.targetView ?? 'clips',
{
cameraIDs: cameraIDs, cameraIDs: cameraIDs,
}, targetView: options?.targetView,
); targetCameraID: options?.targetCameraID,
});
}; };
/** /**
@@ -136,6 +131,7 @@ export const createViewForRecordings = async (
options?: { options?: {
query?: RecordingMediaQueries; query?: RecordingMediaQueries;
cameraIDs?: Set<string>; cameraIDs?: Set<string>;
targetCameraID?: string;
targetView?: 'recording' | 'recordings'; targetView?: 'recording' | 'recordings';
targetTime?: Date; targetTime?: Date;
start?: Date; start?: Date;
@@ -162,18 +158,12 @@ export const createViewForRecordings = async (
query = new RecordingMediaQueries(recordingQueries); query = new RecordingMediaQueries(recordingQueries);
} }
return executeMediaQueryForView( return executeMediaQueryForView(element, hass, cameraManager, view, query, {
element,
hass,
cameraManager,
view,
query,
options?.targetView ?? 'recordings',
{
cameraIDs: cameraIDs, cameraIDs: cameraIDs,
...(options?.targetTime && { targetTime: options.targetTime }), targetView: options?.targetView,
}, targetCameraID: options?.targetCameraID,
); targetTime: options?.targetTime,
});
}; };
const executeMediaQueryForView = async ( const executeMediaQueryForView = async (
@@ -182,9 +172,10 @@ const executeMediaQueryForView = async (
cameraManager: CameraManager, cameraManager: CameraManager,
view: View, view: View,
query: MediaQueries, query: MediaQueries,
targetView: FrigateCardView,
options?: { options?: {
cameraIDs: Set<string>; cameraIDs?: Set<string>;
targetCameraID?: string;
targetView?: FrigateCardView;
targetTime?: Date; targetTime?: Date;
}, },
): Promise<View | null> => { ): Promise<View | null> => {
@@ -214,9 +205,10 @@ const executeMediaQueryForView = async (
return ( return (
view view
?.evolve({ ?.evolve({
view: targetView,
query: query, query: query,
queryResults: queryResults, queryResults: queryResults,
view: options?.targetView,
camera: options?.targetCameraID,
}) })
.mergeInContext(viewerContext) ?? null .mergeInContext(viewerContext) ?? null
); );
@@ -235,7 +227,7 @@ const executeMediaQueryForView = async (
export const findClosestMediaIndex = ( export const findClosestMediaIndex = (
mediaArray: ViewMedia[], mediaArray: ViewMedia[],
targetTime: Date, targetTime: Date,
cameraIDs: Set<string>, cameraIDs?: Set<string>,
refPoint?: 'start' | 'end', refPoint?: 'start' | 'end',
): number | null => { ): number | null => {
let bestMatch: let bestMatch:
@@ -245,6 +237,10 @@ export const findClosestMediaIndex = (
} }
| undefined; | undefined;
if (!cameraIDs) {
return null;
}
for (const [i, media] of mediaArray.entries()) { for (const [i, media] of mediaArray.entries()) {
if (!cameraIDs.has(media.getCameraID())) { if (!cameraIDs.has(media.getCameraID())) {
continue; continue;