Reset the query on grid change in a smarter way.
This commit is contained in:
+29
-14
@@ -48,12 +48,12 @@ import {
|
|||||||
CameraConfig,
|
CameraConfig,
|
||||||
CardWideConfig,
|
CardWideConfig,
|
||||||
ExtendedHomeAssistant,
|
ExtendedHomeAssistant,
|
||||||
|
FRIGATE_CARD_VIEW_DEFAULT,
|
||||||
FrigateCardConfig,
|
FrigateCardConfig,
|
||||||
frigateCardConfigSchema,
|
frigateCardConfigSchema,
|
||||||
FrigateCardCustomAction,
|
FrigateCardCustomAction,
|
||||||
FrigateCardError,
|
FrigateCardError,
|
||||||
FrigateCardView,
|
FrigateCardView,
|
||||||
FRIGATE_CARD_VIEW_DEFAULT,
|
|
||||||
MediaLoadedInfo,
|
MediaLoadedInfo,
|
||||||
MenuButton,
|
MenuButton,
|
||||||
Message,
|
Message,
|
||||||
@@ -87,6 +87,7 @@ import { supportsFeature } from './utils/ha/update.js';
|
|||||||
import { FrigateCardInitializer } from './utils/initializer.js';
|
import { FrigateCardInitializer } from './utils/initializer.js';
|
||||||
import { MediaLoadedInfoController } from './utils/media-info-controller';
|
import { MediaLoadedInfoController } from './utils/media-info-controller';
|
||||||
import { isValidMediaLoadedInfo } from './utils/media-info.js';
|
import { isValidMediaLoadedInfo } from './utils/media-info.js';
|
||||||
|
import { executeMediaQueryForView } from './utils/media-to-view';
|
||||||
import { MenuButtonController } from './utils/menu-controller';
|
import { MenuButtonController } from './utils/menu-controller';
|
||||||
import { MicrophoneController } from './utils/microphone';
|
import { MicrophoneController } from './utils/microphone';
|
||||||
import { getActionsFromQueryString } from './utils/querystring.js';
|
import { getActionsFromQueryString } from './utils/querystring.js';
|
||||||
@@ -1085,7 +1086,7 @@ class FrigateCard extends LitElement {
|
|||||||
// Note: This function needs to process (view-related) commands even when
|
// Note: This function needs to process (view-related) commands even when
|
||||||
// _view has not yet been initialized (since it may be used to set a view
|
// _view has not yet been initialized (since it may be used to set a view
|
||||||
// via the querystring).
|
// via the querystring).
|
||||||
if (!this._cameraManager) {
|
if (!this._cameraManager || !this._hass) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1241,19 +1242,33 @@ class FrigateCard extends LitElement {
|
|||||||
this._conditionController?.setState({
|
this._conditionController?.setState({
|
||||||
displayMode: this._viewDisplayMode,
|
displayMode: this._viewDisplayMode,
|
||||||
});
|
});
|
||||||
// If the new mode is for all cameras, but the current query does not
|
|
||||||
// have a query for every cameraID, reset it.
|
const newView = this._view?.evolve({
|
||||||
const resetQuery =
|
displayMode: frigateCardAction.display_mode,
|
||||||
frigateCardAction.mode === 'grid' &&
|
|
||||||
!this._view?.query?.hasQueriesForCameraIDs(
|
|
||||||
this._cameraManager.getStore().getVisibleCameraIDs(),
|
|
||||||
);
|
|
||||||
this._changeView({
|
|
||||||
view: this._view?.evolve({
|
|
||||||
displayMode: frigateCardAction.display_mode,
|
|
||||||
...(resetQuery && { query: null, queryResults: null }),
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const generateNewQuery =
|
||||||
|
newView?.isGrid() &&
|
||||||
|
newView.query &&
|
||||||
|
(newView.query.getQueryCameraIDs()?.size ?? 0) <= 1;
|
||||||
|
|
||||||
|
if (generateNewQuery && newView && newView.query) {
|
||||||
|
// If the user requests a grid but the current query does not have a
|
||||||
|
// query for more than one camera, reset the query results, change the
|
||||||
|
// existing query to refer to all cameras and execute it to fetch new
|
||||||
|
// results.
|
||||||
|
executeMediaQueryForView(
|
||||||
|
this,
|
||||||
|
this._hass,
|
||||||
|
this._cameraManager,
|
||||||
|
newView,
|
||||||
|
newView.query
|
||||||
|
.clone()
|
||||||
|
.setQueryCameraIDs(this._cameraManager.getStore().getVisibleCameraIDs()),
|
||||||
|
).then((view) => view && this._changeView({ view: view }));
|
||||||
|
} else {
|
||||||
|
this._changeView({ view: newView });
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.warn(`Frigate card received unknown card action: ${action}`);
|
console.warn(`Frigate card received unknown card action: ${action}`);
|
||||||
|
|||||||
@@ -363,7 +363,12 @@ export class FrigateCardLiveGrid extends LitElement {
|
|||||||
|
|
||||||
protected _needsGrid(): boolean {
|
protected _needsGrid(): boolean {
|
||||||
const cameraIDs = this.cameraManager?.getStore().getVisibleCameraIDs();
|
const cameraIDs = this.cameraManager?.getStore().getVisibleCameraIDs();
|
||||||
return !!this.view?.isGrid() && !!cameraIDs && cameraIDs.size >= 1;
|
return (
|
||||||
|
!!this.view?.isGrid() &&
|
||||||
|
!!this.view?.supportsMultipleDisplayModes() &&
|
||||||
|
!!cameraIDs &&
|
||||||
|
cameraIDs.size > 1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected willUpdate(changedProps: PropertyValues): void {
|
protected willUpdate(changedProps: PropertyValues): void {
|
||||||
@@ -377,7 +382,7 @@ export class FrigateCardLiveGrid extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const cameraIDs = this.cameraManager?.getStore().getVisibleCameraIDs();
|
const cameraIDs = this.cameraManager?.getStore().getVisibleCameraIDs();
|
||||||
if (!this._needsGrid() || !cameraIDs) {
|
if (!cameraIDs || !this._needsGrid()) {
|
||||||
return this._renderCarousel();
|
return this._renderCarousel();
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
// TODO: Performance of video scanning (pause/play?)
|
// TODO: Performance of video scanning (pause/play?)
|
||||||
// TODO: Investigate query spam during a grid load
|
// TODO: Investigate query spam during a grid load
|
||||||
// TODO: Is the query reset in card.ts correct for media filter multi-camera queries that are not all cameras?
|
|
||||||
// TODO: Do I need column max?
|
// TODO: Do I need column max?
|
||||||
|
// TODO: What thumbnails do I show in the live drawer?
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
|
|||||||
@@ -149,7 +149,6 @@ export class FrigateCardViewer extends LitElement {
|
|||||||
{
|
{
|
||||||
allCameras: this.view.isGrid(),
|
allCameras: this.view.isGrid(),
|
||||||
targetView: 'recording',
|
targetView: 'recording',
|
||||||
select: 'latest',
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -163,7 +162,6 @@ export class FrigateCardViewer extends LitElement {
|
|||||||
allCameras: this.view.isGrid(),
|
allCameras: this.view.isGrid(),
|
||||||
targetView: 'media',
|
targetView: 'media',
|
||||||
mediaType: mediaType,
|
mediaType: mediaType,
|
||||||
select: 'latest',
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -648,18 +646,23 @@ export class FrigateCardViewerGrid extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected willUpdate(changedProps: PropertyValues): void {
|
protected willUpdate(changedProps: PropertyValues): void {
|
||||||
if (
|
if (changedProps.has('view') && this._needsGrid()) {
|
||||||
changedProps.has('view') &&
|
|
||||||
this.view?.isGrid() &&
|
|
||||||
this.view?.hasMultipleDisplayModes()
|
|
||||||
) {
|
|
||||||
import('./media-grid.js');
|
import('./media-grid.js');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected _needsGrid(): boolean {
|
||||||
|
const cameraIDs = this.view?.queryResults?.getCameraIDs();
|
||||||
|
return (
|
||||||
|
!!this.view?.isGrid() &&
|
||||||
|
!!this.view?.supportsMultipleDisplayModes() &&
|
||||||
|
(cameraIDs?.size ?? 0) > 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const cameraIDs = this.view?.queryResults?.getCameraIDs();
|
const cameraIDs = this.view?.queryResults?.getCameraIDs();
|
||||||
if (!cameraIDs || !this.view?.isGrid() || !this.view?.hasMultipleDisplayModes()) {
|
if (!cameraIDs || !this._needsGrid()) {
|
||||||
return this._renderCarousel();
|
return this._renderCarousel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -394,7 +394,7 @@ export class MenuButtonController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (view.hasMultipleDisplayModes(visibleCameras.size)) {
|
if (view.supportsMultipleDisplayModes() && visibleCameras.size > 1) {
|
||||||
const isGrid = view.isGrid();
|
const isGrid = view.isGrid();
|
||||||
const action = createFrigateCardCustomAction('display_mode_select', {
|
const action = createFrigateCardCustomAction('display_mode_select', {
|
||||||
display_mode: isGrid ? 'single' : 'grid',
|
display_mode: isGrid ? 'single' : 'grid',
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import cloneDeep from 'lodash-es/cloneDeep.js';
|
import cloneDeep from 'lodash-es/cloneDeep.js';
|
||||||
|
import isEqual from 'lodash-es/isEqual.js';
|
||||||
|
import uniqWith from 'lodash-es/uniqWith.js';
|
||||||
import { EventQuery, MediaQuery, RecordingQuery } from '../camera-manager/types.js';
|
import { EventQuery, MediaQuery, RecordingQuery } from '../camera-manager/types.js';
|
||||||
|
|
||||||
export type MediaQueries = EventMediaQueries | RecordingMediaQueries;
|
export type MediaQueries = EventMediaQueries | RecordingMediaQueries;
|
||||||
@@ -12,7 +14,7 @@ class MediaQueriesBase<T extends MediaQuery> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public clone(): MediaQueriesBase<T> {
|
public clone(): this {
|
||||||
return cloneDeep(this);
|
return cloneDeep(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,8 +22,32 @@ class MediaQueriesBase<T extends MediaQuery> {
|
|||||||
return this._queries;
|
return this._queries;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setQueries(queries: T[]): void {
|
public setQueries(queries: T[]): this {
|
||||||
this._queries = queries;
|
this._queries = queries;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getQueryCameraIDs(): Set<string> | null {
|
||||||
|
if (!this._queries) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const cameraIDs: Set<string> = new Set();
|
||||||
|
this._queries.forEach((query) =>
|
||||||
|
[...query.cameraIDs].forEach((cameraID) => cameraIDs.add(cameraID)),
|
||||||
|
);
|
||||||
|
return cameraIDs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setQueryCameraIDs(cameraIDs: Set<string>): this {
|
||||||
|
if (!this._queries) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
const rewrittenQueries: T[] = [];
|
||||||
|
this._queries.forEach((query) =>
|
||||||
|
rewrittenQueries.push({ ...query, cameraIDs: cameraIDs }),
|
||||||
|
);
|
||||||
|
this._queries = uniqWith(rewrittenQueries, isEqual);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public hasQueriesForCameraIDs(cameraIDs: Set<string>) {
|
public hasQueriesForCameraIDs(cameraIDs: Set<string>) {
|
||||||
@@ -42,10 +68,6 @@ export class EventMediaQueries extends MediaQueriesBase<EventQuery> {
|
|||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public clone(): EventMediaQueries {
|
|
||||||
return cloneDeep(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RecordingMediaQueries extends MediaQueriesBase<RecordingQuery> {}
|
export class RecordingMediaQueries extends MediaQueriesBase<RecordingQuery> {}
|
||||||
|
|||||||
+2
-5
@@ -231,11 +231,8 @@ export class View {
|
|||||||
return ['clip', 'snapshot', 'media', 'recording'].includes(this.view);
|
return ['clip', 'snapshot', 'media', 'recording'].includes(this.view);
|
||||||
}
|
}
|
||||||
|
|
||||||
public hasMultipleDisplayModes(cameraCount?: number): boolean {
|
public supportsMultipleDisplayModes(): boolean {
|
||||||
return (
|
return this.isViewerView() || this.is('live');
|
||||||
(this.is('live') && (cameraCount ?? 0) > 1) ||
|
|
||||||
(this.isViewerView() && (this.queryResults?.getCameraIDs().size ?? 0) > 1)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ describe('MediaGridController', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
initLayout: false,
|
initLayout: false,
|
||||||
percentPosition: true,
|
percentPosition: true,
|
||||||
transitionDuration: '0.3s',
|
transitionDuration: '0.2s',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -352,10 +352,7 @@ describe('MediaGridController', () => {
|
|||||||
const controller = createController(host);
|
const controller = createController(host);
|
||||||
|
|
||||||
expect(controller.getSelected()).toBeNull();
|
expect(controller.getSelected()).toBeNull();
|
||||||
|
children[1].click();
|
||||||
const touchEvent = new TouchEvent('touchend');
|
|
||||||
children[1].dispatchEvent(touchEvent);
|
|
||||||
|
|
||||||
expect(controller.getSelected()).toBe('1');
|
expect(controller.getSelected()).toBe('1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,30 @@ describe('EventMediaQueries', () => {
|
|||||||
const queries = new EventMediaQueries(rawQueries);
|
const queries = new EventMediaQueries(rawQueries);
|
||||||
expect(queries.clone().getQueries()).toEqual(queries.getQueries());
|
expect(queries.clone().getQueries()).toEqual(queries.getQueries());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should get camera IDs when queries are null', () => {
|
||||||
|
expect(new EventMediaQueries().getQueryCameraIDs()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get camera IDs', () => {
|
||||||
|
const cameraIDs = ['office', 'kitchen'];
|
||||||
|
const queries = new EventMediaQueries(createRawEventQueries(new Set(cameraIDs)));
|
||||||
|
expect(queries.getQueryCameraIDs()).toEqual(new Set(cameraIDs));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set camera IDs when queries are null', () => {
|
||||||
|
expect(
|
||||||
|
new EventMediaQueries().setQueryCameraIDs(new Set(['office'])).getQueryCameraIDs(),
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set camera IDs', () => {
|
||||||
|
const queries = new EventMediaQueries(createRawEventQueries('sitting_room'));
|
||||||
|
const newCameraIDs = new Set(['office', 'kitchen']);
|
||||||
|
expect(queries.setQueryCameraIDs(newCameraIDs).getQueryCameraIDs()).toEqual(
|
||||||
|
newCameraIDs,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('RecordingMediaQueries', () => {
|
describe('RecordingMediaQueries', () => {
|
||||||
@@ -93,10 +117,4 @@ describe('RecordingMediaQueries', () => {
|
|||||||
const queries = new RecordingMediaQueries(rawQueries);
|
const queries = new RecordingMediaQueries(rawQueries);
|
||||||
expect(queries.getQueries()).toBe(rawQueries);
|
expect(queries.getQueries()).toBe(rawQueries);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clone', () => {
|
|
||||||
const rawQueries = createRawRecordingQueries('office');
|
|
||||||
const queries = new RecordingMediaQueries(rawQueries);
|
|
||||||
expect(queries.clone().getQueries()).toEqual(queries.getQueries());
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
+10
-60
@@ -427,67 +427,17 @@ describe('View.adoptFromViewIfAppropriate', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should determine if view supports multiple display modes', () => {
|
it('should determine if view supports multiple display modes', () => {
|
||||||
const resultsOne = new MediaQueriesResults({
|
expect(createView({ view: 'live' }).supportsMultipleDisplayModes()).toBeFalsy();
|
||||||
results: generateViewMediaArray({ cameraIDs: ['office'] }),
|
expect(createView({ view: 'media' }).supportsMultipleDisplayModes()).toBeTruthy();
|
||||||
});
|
expect(createView({ view: 'clip' }).supportsMultipleDisplayModes()).toBeTruthy();
|
||||||
const resultsTwo = new MediaQueriesResults({
|
expect(createView({ view: 'snapshot' }).supportsMultipleDisplayModes()).toBeTruthy();
|
||||||
results: generateViewMediaArray({ cameraIDs: ['office', 'kitchen'] }),
|
expect(createView({ view: 'recording' }).supportsMultipleDisplayModes()).toBeTruthy();
|
||||||
});
|
|
||||||
|
|
||||||
expect(createView({ view: 'live' }).hasMultipleDisplayModes()).toBeFalsy();
|
expect(createView({ view: 'clips' }).supportsMultipleDisplayModes()).toBeFalsy();
|
||||||
expect(createView({ view: 'live' }).hasMultipleDisplayModes(0)).toBeFalsy();
|
expect(createView({ view: 'snapshots' }).supportsMultipleDisplayModes()).toBeFalsy();
|
||||||
expect(createView({ view: 'live' }).hasMultipleDisplayModes(1)).toBeFalsy();
|
expect(createView({ view: 'recordings' }).supportsMultipleDisplayModes()).toBeFalsy();
|
||||||
expect(createView({ view: 'live' }).hasMultipleDisplayModes(2)).toBeTruthy();
|
expect(createView({ view: 'image' }).supportsMultipleDisplayModes()).toBeFalsy();
|
||||||
|
expect(createView({ view: 'timeline' }).supportsMultipleDisplayModes()).toBeFalsy();
|
||||||
expect(createView({ view: 'media' }).hasMultipleDisplayModes()).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
createView({ view: 'media', queryResults: resultsOne }).hasMultipleDisplayModes(),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
createView({ view: 'media', queryResults: resultsTwo }).hasMultipleDisplayModes(),
|
|
||||||
).toBeTruthy();
|
|
||||||
|
|
||||||
expect(createView({ view: 'clip' }).hasMultipleDisplayModes()).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
createView({ view: 'clip', queryResults: resultsOne }).hasMultipleDisplayModes(),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
createView({ view: 'clip', queryResults: resultsTwo }).hasMultipleDisplayModes(),
|
|
||||||
).toBeTruthy();
|
|
||||||
|
|
||||||
expect(createView({ view: 'snapshot' }).hasMultipleDisplayModes()).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
createView({
|
|
||||||
view: 'snapshot',
|
|
||||||
queryResults: resultsOne,
|
|
||||||
}).hasMultipleDisplayModes(),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
createView({
|
|
||||||
view: 'snapshot',
|
|
||||||
queryResults: resultsTwo,
|
|
||||||
}).hasMultipleDisplayModes(),
|
|
||||||
).toBeTruthy();
|
|
||||||
|
|
||||||
expect(createView({ view: 'recording' }).hasMultipleDisplayModes()).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
createView({
|
|
||||||
view: 'recording',
|
|
||||||
queryResults: resultsOne,
|
|
||||||
}).hasMultipleDisplayModes(),
|
|
||||||
).toBeFalsy();
|
|
||||||
expect(
|
|
||||||
createView({
|
|
||||||
view: 'recording',
|
|
||||||
queryResults: resultsTwo,
|
|
||||||
}).hasMultipleDisplayModes(),
|
|
||||||
).toBeTruthy();
|
|
||||||
|
|
||||||
expect(createView({ view: 'clips' }).hasMultipleDisplayModes()).toBeFalsy();
|
|
||||||
expect(createView({ view: 'snapshots' }).hasMultipleDisplayModes()).toBeFalsy();
|
|
||||||
expect(createView({ view: 'recordings' }).hasMultipleDisplayModes()).toBeFalsy();
|
|
||||||
expect(createView({ view: 'image' }).hasMultipleDisplayModes()).toBeFalsy();
|
|
||||||
expect(createView({ view: 'timeline' }).hasMultipleDisplayModes()).toBeFalsy();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user