diff --git a/src/components/media-filter.ts b/src/components/media-filter.ts
index fb8ef755..f58cab60 100644
--- a/src/components/media-filter.ts
+++ b/src/components/media-filter.ts
@@ -13,7 +13,7 @@ import { createRef, ref, Ref } from 'lit/directives/ref.js';
import { DateRange } from '../camera-manager/range';
import { localize } from '../localize/localize';
import mediaFilterStyle from '../scss/media-filter.scss';
-import { executeMediaQueryForView } from '../utils/media-to-view.js';
+import { executeMediaQueryForViewWithErrorDispatching } from '../utils/media-to-view.js';
import { errorToConsole, formatDate, prettifyTitle } from '../utils/basic';
import { ScopedRegistryHost } from '@lit-labs/scoped-registry-mixin';
import './select';
@@ -228,7 +228,7 @@ class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) {
]);
(
- await executeMediaQueryForView(
+ await executeMediaQueryForViewWithErrorDispatching(
this,
this.cameraManager,
this.view,
@@ -251,7 +251,7 @@ class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) {
]);
(
- await executeMediaQueryForView(
+ await executeMediaQueryForViewWithErrorDispatching(
this,
this.cameraManager,
this.view,
@@ -404,7 +404,7 @@ class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) {
...(what && { what: what }),
...(where && { where: where }),
...(favorite !== undefined && { favorite: favorite }),
- ...(tags && { tags: tags })
+ ...(tags && { tags: tags }),
};
}
@@ -472,18 +472,18 @@ class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) {
`
: ''}
${areEvents && this._mediaMetadataController.tagsOptions.length
- ? html`
- `
- : ''}
+ ? html`
+ `
+ : ''}
${areEvents && this._mediaMetadataController.whereOptions.length
? html` {
- if (
- !this.view ||
- !this.timelineConfig ||
- !this._timelineSource ||
- !this._timeline
- ) {
+ if (!this.view || !this.timelineConfig || !this._timelineSource || !this._timeline) {
return;
}
diff --git a/src/utils/card-controller/view-manager.ts b/src/utils/card-controller/view-manager.ts
index 3600ea7e..568d65f8 100644
--- a/src/utils/card-controller/view-manager.ts
+++ b/src/utils/card-controller/view-manager.ts
@@ -101,7 +101,6 @@ export class ViewManager {
}
public async setViewWithNewDisplayMode(displayMode: ViewDisplayMode): Promise {
- const host = this._api.getCardElementManager().getElement();
const hass = this._api.getHASSManager().getHASS();
if (this._view && hass) {
@@ -125,18 +124,22 @@ export class ViewManager {
// 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.
- const viewWithNewQuery = await executeMediaQueryForView(
- host,
- this._api.getCameraManager(),
- view,
- view.query
- .clone()
- .setQueryCameraIDs(
- view.isGrid()
- ? this._api.getCameraManager().getStore().getVisibleCameraIDs()
- : view.camera,
- ),
- );
+ let viewWithNewQuery: View | null = null;
+ try {
+ viewWithNewQuery = await executeMediaQueryForView(
+ this._api.getCameraManager(),
+ view,
+ view.query
+ .clone()
+ .setQueryCameraIDs(
+ view.isGrid()
+ ? this._api.getCameraManager().getStore().getVisibleCameraIDs()
+ : view.camera,
+ ),
+ );
+ } catch (e: unknown) {
+ this._api.getMessageManager().setErrorIfHigherPriority(e);
+ }
if (viewWithNewQuery) {
return this._setView(viewWithNewQuery);
diff --git a/src/utils/media-to-view.ts b/src/utils/media-to-view.ts
index 89e5b25a..0fba9008 100644
--- a/src/utils/media-to-view.ts
+++ b/src/utils/media-to-view.ts
@@ -44,10 +44,16 @@ export const changeViewToRecentEventsForCameraAndDependents = async (
}
(
- await executeMediaQueryForView(element, cameraManager, view, queries, {
- targetView: options?.targetView,
- select: options?.select,
- })
+ await executeMediaQueryForViewWithErrorDispatching(
+ element,
+ cameraManager,
+ view,
+ queries,
+ {
+ targetView: options?.targetView,
+ select: options?.select,
+ },
+ )
)?.dispatchChangeEvent(element);
};
@@ -106,10 +112,16 @@ export const changeViewToRecentRecordingForCameraAndDependents = async (
}
(
- await executeMediaQueryForView(element, cameraManager, view, queries, {
- targetView: options?.targetView,
- select: options?.select,
- })
+ await executeMediaQueryForViewWithErrorDispatching(
+ element,
+ cameraManager,
+ view,
+ queries,
+ {
+ targetView: options?.targetView,
+ select: options?.select,
+ },
+ )
)?.dispatchChangeEvent(element);
};
@@ -127,7 +139,6 @@ const createQueriesForRecordingsView = (
};
export const executeMediaQueryForView = async (
- element: HTMLElement,
cameraManager: CameraManager,
view: View,
query: MediaQueries,
@@ -138,21 +149,12 @@ export const executeMediaQueryForView = async (
select?: ResultSelectType;
},
): Promise => {
- let mediaArray: ViewMedia[] | null;
-
const queries = query.getQueries();
if (!queries) {
return null;
}
- try {
- mediaArray = await cameraManager.executeMediaQueries(queries);
- } catch (e) {
- errorToConsole(e as Error);
- dispatchFrigateCardErrorEvent(element, e as Error);
- return null;
- }
-
+ const mediaArray = await cameraManager.executeMediaQueries(queries);
if (!mediaArray) {
return null;
}
@@ -182,6 +184,32 @@ export const executeMediaQueryForView = async (
.mergeInContext(viewerContext);
};
+export const executeMediaQueryForViewWithErrorDispatching = async (
+ element: HTMLElement,
+ cameraManager: CameraManager,
+ view: View,
+ query: MediaQueries,
+ options?: {
+ targetCameraID?: string;
+ targetView?: FrigateCardView;
+ targetTime?: Date;
+ select?: ResultSelectType;
+ },
+): Promise => {
+ try {
+ return await executeMediaQueryForView(cameraManager, view, query, {
+ targetCameraID: options?.targetCameraID,
+ targetView: options?.targetView,
+ targetTime: options?.targetTime,
+ select: options?.select,
+ });
+ } catch (e: unknown) {
+ errorToConsole(e as Error);
+ dispatchFrigateCardErrorEvent(element, e as Error);
+ }
+ return null;
+};
+
/**
* Find the longest matching media object that contains a given targetTime.
* Longest is chosen to give the most stability to the media viewer.
diff --git a/tests/utils/card-controller/view-manager.test.ts b/tests/utils/card-controller/view-manager.test.ts
index 00db236e..9d498389 100644
--- a/tests/utils/card-controller/view-manager.test.ts
+++ b/tests/utils/card-controller/view-manager.test.ts
@@ -496,7 +496,7 @@ describe('ViewManager.setViewWithNewDisplayMode', () => {
manager.setView(originalView);
// Query execution fails / returns null.
- vi.mocked(cameraManager.executeMediaQueries).mockResolvedValue(null);
+ vi.mocked(cameraManager.executeMediaQueries).mockRejectedValue(null);
await manager.setViewWithNewDisplayMode('single');
diff --git a/tests/utils/media-to-view.test.ts b/tests/utils/media-to-view.test.ts
index a1c5d3f6..579c594b 100644
--- a/tests/utils/media-to-view.test.ts
+++ b/tests/utils/media-to-view.test.ts
@@ -6,6 +6,7 @@ import {
changeViewToRecentEventsForCameraAndDependents,
changeViewToRecentRecordingForCameraAndDependents,
executeMediaQueryForView,
+ executeMediaQueryForViewWithErrorDispatching,
findBestMediaIndex,
} from '../../src/utils/media-to-view';
import { ViewMedia } from '../../src/view/media';
@@ -205,13 +206,11 @@ describe('executeMediaQueryForView', () => {
});
it('should not execute empty queries', async () => {
- const elementHandler = createElementListenForView();
const cameraConfigs: CameraConfigs = new Map();
const cameraManager = createCameraManager({ configs: cameraConfigs });
expect(
await executeMediaQueryForView(
- elementHandler.element,
cameraManager,
createView(),
new EventMediaQueries(),
@@ -219,8 +218,23 @@ describe('executeMediaQueryForView', () => {
).toBeNull();
});
+ it('should throw on failure', async () => {
+ const cameraConfigs: CameraConfigs = new Map();
+ const cameraManager = createCameraManager({ configs: cameraConfigs });
+ vi.mocked(cameraManager.executeMediaQueries).mockRejectedValue(new Error());
+
+ await expect(
+ executeMediaQueryForView(
+ cameraManager,
+ createView(),
+ new EventMediaQueries(
+ cameraManager.generateDefaultEventQueries('camera') ?? undefined,
+ ),
+ ),
+ ).rejects.toThrowError();
+ });
+
it('should select time-based result', async () => {
- const elementHandler = createElementListenForView();
const cameraConfigs: CameraConfigs = new Map();
const cameraManager = createCameraManager({ configs: cameraConfigs });
@@ -233,7 +247,6 @@ describe('executeMediaQueryForView', () => {
vi.mocked(cameraManager.executeMediaQueries).mockResolvedValue(mediaArray);
const view = await executeMediaQueryForView(
- elementHandler.element,
cameraManager,
createView(),
new EventMediaQueries(
@@ -251,7 +264,6 @@ describe('executeMediaQueryForView', () => {
});
it('should select nothing when time-based selection does not match', async () => {
- const elementHandler = createElementListenForView();
const cameraConfigs: CameraConfigs = new Map();
const cameraManager = createCameraManager({ configs: cameraConfigs });
@@ -265,7 +277,6 @@ describe('executeMediaQueryForView', () => {
vi.mocked(cameraManager.executeMediaQueries).mockResolvedValue(mediaArray);
const view = await executeMediaQueryForView(
- elementHandler.element,
cameraManager,
createView(),
new EventMediaQueries(
@@ -378,6 +389,29 @@ describe('changeViewToRecentRecordingForCameraAndDependents', () => {
});
});
+// @vitest-environment jsdom
+describe('executeMediaQueryForViewWithErrorDispatching', () => {
+ it('should dispatch error message on fail', async () => {
+ vi.spyOn(global.console, 'warn').mockImplementation(() => true);
+
+ const elementHandler = createElementListenForView();
+ const cameraManager = createCameraManager();
+ vi.mocked(cameraManager.executeMediaQueries).mockRejectedValue(new Error());
+
+ await executeMediaQueryForViewWithErrorDispatching(
+ elementHandler.element,
+ cameraManager,
+ createView(),
+ new EventMediaQueries(
+ cameraManager.generateDefaultEventQueries('camera') ?? undefined,
+ ),
+ );
+
+ expect(elementHandler.viewHandler).not.toBeCalled();
+ expect(elementHandler.messageHandler).toBeCalled();
+ });
+});
+
// @vitest-environment jsdom
describe('findBestMediaIndex', () => {
it('should find best media index', async () => {