From e0d2dfaa608b8a2f2121713559d127bee846367d Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Mon, 2 Jan 2023 20:52:12 -0800 Subject: [PATCH] Dispatch message if the viewer has no media to show. --- src/components/timeline-core.ts | 6 ++++-- src/components/viewer.ts | 11 +++++++++-- src/localize/languages/en.json | 6 +----- src/localize/languages/it.json | 5 +---- src/localize/languages/pt-BR.json | 5 +---- src/utils/data/data-manager.ts | 12 +++++------- src/utils/media-to-view.ts | 4 ++-- src/view.ts | 7 +++---- 8 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/components/timeline-core.ts b/src/components/timeline-core.ts index 1d8ab4b8..1bee6125 100644 --- a/src/components/timeline-core.ts +++ b/src/components/timeline-core.ts @@ -509,8 +509,10 @@ export class FrigateCardTimelineCore extends LitElement { targetView: 'media', }, ); - const results = view?.queryResults?.getResults() ?? null; - if (!results || !view) { + const results = view?.queryResults?.getResults(); + // Specifically ensure there are _some_ results before dispatching the + // view change. + if (!view || !results || !results.length) { return null; } view.mergeInContext( diff --git a/src/components/viewer.ts b/src/components/viewer.ts index 02031b71..87793467 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -12,7 +12,7 @@ import { import { customElement, property } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { createRef, Ref, ref } from 'lit/directives/ref.js'; -import { renderProgressIndicator } from '../components/message.js'; +import { dispatchMessageEvent, renderProgressIndicator } from '../components/message.js'; import viewerStyle from '../scss/viewer.scss'; import viewerCarouselStyle from '../scss/viewer-carousel.scss'; import { @@ -53,6 +53,7 @@ import { } from '../utils/media-to-view.js'; import { ViewMedia, ViewMediaClassifier } from '../view-media.js'; import { guard } from 'lit/directives/guard.js'; +import { localize } from '../localize/localize.js'; export interface MediaSeek { // Specifies the point at which this recording should be played, the @@ -566,6 +567,12 @@ export class FrigateCardViewerCarousel extends LitElement { * @returns A template to display to the user. */ protected _render(): TemplateResult | void { + if ((this.view?.queryResults?.getResultsCount() ?? 0) === 0) { + return dispatchMessageEvent(this, localize('common.no_media'), 'info', { + icon: 'mdi:multimedia', + }); + } + const media = this.view?.queryResults?.getSelectedResult(); if (!media || !this.cameras) { return; @@ -622,7 +629,7 @@ export class FrigateCardViewerCarousel extends LitElement { protected _recordingSeekHandler(): void { const selectedIndex = this.view?.queryResults?.getSelectedIndex() ?? null; const seek = - selectedIndex !== null + selectedIndex !== null ? this.view?.context?.mediaViewer?.seek.get(selectedIndex) : null; const player = this._getPlayer(); diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index a8cf3e39..47220e12 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -3,11 +3,7 @@ "frigate_card": "Frigate card", "frigate_card_description": "A Lovelace card for use with Frigate", "live": "Live", - "no_clip": "No recent clip", - "no_clips": "No clips", - "no_snapshot": "No recent snapshot", - "no_snapshots": "No snapshots", - "no_recording": "No recent recording", + "no_media": "No media to display", "recordings": "Recordings", "version": "Version" }, diff --git a/src/localize/languages/it.json b/src/localize/languages/it.json index 9def01a7..5889e7c1 100644 --- a/src/localize/languages/it.json +++ b/src/localize/languages/it.json @@ -3,10 +3,7 @@ "frigate_card": "Frigate card", "frigate_card_description": "Una scheda Lovelace per l'uso con Frigate", "live": "Live", - "no_clip": "Nessuna clip recente", - "no_clips": "Nessun clip", - "no_snapshot": "Nessuna istantanea recente", - "no_snapshots": "Nessuna istantanea", + "no_media": "", "recordings": "Registrazioni", "version": "Versione" }, diff --git a/src/localize/languages/pt-BR.json b/src/localize/languages/pt-BR.json index 4aa0a965..61295352 100644 --- a/src/localize/languages/pt-BR.json +++ b/src/localize/languages/pt-BR.json @@ -3,10 +3,7 @@ "frigate_card": "Cartão Frigate", "frigate_card_description": "Um cartão da Lovelace para usar com Frigate", "live": "Ao Vivo", - "no_clip": "Sem clip recente", - "no_clips": "Sem clips", - "no_snapshot": "Sem snapshot recente", - "no_snapshots": "Sem snapshots", + "no_media": "", "recordings": "Gravações", "version": "Versão" }, diff --git a/src/utils/data/data-manager.ts b/src/utils/data/data-manager.ts index a6027d54..75ab4a27 100644 --- a/src/utils/data/data-manager.ts +++ b/src/utils/data/data-manager.ts @@ -202,13 +202,11 @@ export class DataManager { } } - return mediaArray.length - ? new MediaQueriesResults( - orderBy(mediaArray, (media) => media.getStartTime(), 'desc'), - // Select the first (most-recent) item. - 0, - ) - : null; + return new MediaQueriesResults( + orderBy(mediaArray, (media) => media.getStartTime(), 'desc'), + // Select the first (most-recent) item. + mediaArray.length ? 0 : null, + ); } public getMediaDownloadPath(media: ViewMedia): string | null { diff --git a/src/utils/media-to-view.ts b/src/utils/media-to-view.ts index cd14ab7b..eec0346b 100644 --- a/src/utils/media-to-view.ts +++ b/src/utils/media-to-view.ts @@ -54,9 +54,9 @@ export const createViewForEvents = async ( const queries = dataManager.generateDefaultEventQueries(cameraIDs, { ...(options?.limit && { limit: options.limit }), ...((!options?.mediaType || ['clips', 'all'].includes(options.mediaType)) && { - has_clip: true, + hasClip: true, }), - ...(options?.mediaType === 'snapshots' && { has_snapshot: true }), + ...(options?.mediaType === 'snapshots' && { hasSnapshot: true }), }); query = new EventMediaQueries(queries); } diff --git a/src/view.ts b/src/view.ts index b331739c..8a10bea4 100644 --- a/src/view.ts +++ b/src/view.ts @@ -6,7 +6,6 @@ // - TODO: Are there elements of ViewMedia (e.g. getEventCount) that should be moved into subclasses (e.g. a recording subclass). // - TODO: In MediaQueriesBase, do we need to generic? Just have T be a MediaQuery? // - TODO: Are areEventQueries and areRecordingQueries should be in a classifier to keep with the pattern used elsewhere. -// - TODO: Callers to the creation of new views for events/recordings need to dispatch events themselves when none are found. // - TODO: In the viewer @click handlers should I use this.selected instead of calling carouselScrollPrevious() // - TODO: Can _timelineClickHandler be an async method in timeline-core to improve cleanliness? // - TODO: Can _timelineRangeChangedHandler be an async method in timeline-core to improve cleanliness? @@ -124,7 +123,7 @@ export class MediaQueriesResults { protected _resultsTimestamp: Date | null = null; protected _selectedIndex: number | null = null; - constructor(results?: ViewMedia[], selectedIndex?: number) { + constructor(results?: ViewMedia[], selectedIndex?: number | null) { if (results) { this.setResults(results); } @@ -177,8 +176,8 @@ export class MediaQueriesResults { return this._resultsTimestamp; } - public selectResult(index: number): MediaQueriesResults { - if (this._results && index >= 0 && index < this._results.length) { + public selectResult(index: number | null): MediaQueriesResults { + if (index === null || (this._results && index >= 0 && index < this._results.length)) { this._selectedIndex = index; } return this;