diff --git a/src/browse-media-util.ts b/src/browse-media-util.ts index 84007d30..ff438705 100644 --- a/src/browse-media-util.ts +++ b/src/browse-media-util.ts @@ -1,16 +1,21 @@ +import { HomeAssistant } from 'custom-card-helpers'; +import dayjs from 'dayjs'; +import dayjs_custom_parse_format from 'dayjs/plugin/customParseFormat.js'; + import type { BrowseMediaQueryParameters, BrowseMediaSource, CameraConfig, ExtendedHomeAssistant, } from './types.js'; -import { HomeAssistant } from 'custom-card-helpers'; -import { homeAssistantWSRequest } from './common.js'; -import { browseMediaSourceSchema } from './types.js'; - -import dayjs from 'dayjs'; -import dayjs_custom_parse_format from 'dayjs/plugin/customParseFormat.js'; import { View } from './view.js'; +import { browseMediaSourceSchema } from './types.js'; +import { + dispatchErrorMessageEvent, + dispatchMessageEvent, + homeAssistantWSRequest, +} from './common.js'; +import { localize } from './localize/localize.js'; dayjs.extend(dayjs_custom_parse_format); @@ -154,4 +159,77 @@ export class BrowseMediaUtil { cameraConfig, ); } + + /** + * Fetch the latest media and dispatch a change view event to reflect the + * results. If no media is found a suitable message event will be triggered + * instead. + * @param node The HTMLElement to dispatch events from. + * @param hass The Home Assistant object. + * @param view The current view to evolve. + * @param browseMediaQueryParameters The media parameters to query with. + * @returns + */ + static async fetchLatestMediaAndDispatchViewChange( + node: HTMLElement, + hass: HomeAssistant & ExtendedHomeAssistant, + view: Readonly, + browseMediaQueryParameters: BrowseMediaQueryParameters, + ): Promise { + let parent: BrowseMediaSource | null; + try { + parent = await BrowseMediaUtil.browseMediaQuery(hass, browseMediaQueryParameters); + } catch (e) { + return dispatchErrorMessageEvent(node, (e as Error).message); + } + const childIndex = BrowseMediaUtil.getFirstTrueMediaChildIndex(parent); + if (!parent || !parent.children || childIndex == null) { + return dispatchMessageEvent( + node, + browseMediaQueryParameters.mediaType == 'clips' + ? localize('common.no_clip') + : localize('common.no_snapshot'), + browseMediaQueryParameters.mediaType == 'clips' + ? 'mdi:filmstrip-off' + : 'mdi:camera-off', + ); + } + + view + .evolve({ + target: parent, + childIndex: childIndex, + }) + .dispatchChangeEvent(node); + } + + /** + * Fetch the media of a child BrowseMediaSource object and dispatch a change + * view event to reflect the results. + * @param node The HTMLElement to dispatch events from. + * @param hass The Home Assistant object. + * @param view The current view to evolve. + * @param child The BrowseMediaSource child to query for. + * @returns + */ + static async fetchChildMediaAndDispatchViewChange( + node: HTMLElement, + hass: HomeAssistant & ExtendedHomeAssistant, + view: Readonly, + child: Readonly, + ): Promise { + let parent: BrowseMediaSource; + try { + parent = await BrowseMediaUtil.browseMedia(hass, child.media_content_id); + } catch (e) { + return dispatchErrorMessageEvent(node, (e as Error).message); + } + + view + .evolve({ + target: parent, + previous: view, + }) + .dispatchChangeEvent(node); + } } diff --git a/src/components/gallery.ts b/src/components/gallery.ts index 45703875..672f2292 100644 --- a/src/components/gallery.ts +++ b/src/components/gallery.ts @@ -3,21 +3,13 @@ import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit import { HomeAssistant } from 'custom-card-helpers'; import { customElement, property, state } from 'lit/decorators.js'; import { styleMap } from 'lit/directives/style-map.js'; -import { until } from 'lit/directives/until.js'; -import type { - BrowseMediaSource, - BrowseMediaQueryParameters, - ExtendedHomeAssistant, -} from '../types.js'; +import type { BrowseMediaQueryParameters, ExtendedHomeAssistant } from '../types.js'; import { BrowseMediaUtil } from '../browse-media-util.js'; import { View } from '../view.js'; -import { dispatchErrorMessageEvent, dispatchMessageEvent } from '../common.js'; -import { localize } from '../localize/localize.js'; import { renderProgressIndicator } from './message.js'; import galleryStyle from '../scss/gallery.scss'; -import { actionHandler } from '../action-handler-directive.js'; const MAX_THUMBNAIL_WIDTH = 175; const DEFAULT_COLUMNS = 5; @@ -28,7 +20,7 @@ export class FrigateCardGallery extends LitElement { protected hass?: HomeAssistant & ExtendedHomeAssistant; @property({ attribute: false }) - protected view?: View; + protected view?: Readonly; @property({ attribute: false }) protected browseMediaQueryParameters?: BrowseMediaQueryParameters; @@ -38,58 +30,24 @@ export class FrigateCardGallery extends LitElement { * @returns A rendered template. */ protected render(): TemplateResult | void { - return html`${until(this._render(), renderProgressIndicator())}`; - } - - /** - * Asyncronously render the element. - * @returns A rendered template. - */ - protected async _render(): Promise { - if ( - !this.hass || - !this.view || - !this.browseMediaQueryParameters || - !(this.view.is('clips') || this.view.is('snapshots')) - ) { - return html``; + if (!this.hass || !this.view || !this.browseMediaQueryParameters) { + return; } - let parent: BrowseMediaSource | null; - try { - if (this.view.target) { - parent = await BrowseMediaUtil.browseMedia( - this.hass, - this.view.target.media_content_id, - ); - } else { - parent = await BrowseMediaUtil.browseMediaQuery( - this.hass, - this.browseMediaQueryParameters, - ); - } - } catch (e: any) { - return dispatchErrorMessageEvent(this, e.message); - } - - if ( - !parent || - !parent.children || - BrowseMediaUtil.getFirstTrueMediaChildIndex(parent) == null - ) { - return dispatchMessageEvent( + if (!this.view.target) { + BrowseMediaUtil.fetchLatestMediaAndDispatchViewChange( this, - this.view.is('clips') - ? localize('common.no_clips') - : localize('common.no_snapshots'), - this.view.is('clips') ? 'mdi:filmstrip-off' : 'mdi:camera-off', + this.hass, + this.view, + this.browseMediaQueryParameters, ); + return renderProgressIndicator(); } - this.view.target = parent; - - return html` - `; + return html` + + + `; } /** @@ -106,7 +64,7 @@ export class FrigateCardGalleryCore extends LitElement { protected hass?: HomeAssistant & ExtendedHomeAssistant; @property({ attribute: false }) - protected view?: View; + protected view?: Readonly; protected _resizeObserver: ResizeObserver; @@ -137,6 +95,7 @@ export class FrigateCardGalleryCore extends LitElement { protected render(): TemplateResult | void { if ( + !this.hass || !this.view || !this.view.target || !this.view.target.children || @@ -156,11 +115,7 @@ export class FrigateCardGalleryCore extends LitElement {
{ + @click=${() => { if (this.view && this.view.previous) { this.view.previous.dispatchChangeEvent(this); } @@ -181,18 +136,14 @@ export class FrigateCardGalleryCore extends LitElement { ${child.can_expand ? html`
{ - if (this.view) { - new View({ - view: this.view.view, - camera: this.view.camera, - target: child, - previous: this.view ?? undefined, - }).dispatchChangeEvent(this); + @click=${() => { + if (this.hass && this.view) { + BrowseMediaUtil.fetchChildMediaAndDispatchViewChange( + this, + this.hass, + this.view, + child, + ); } }} outlined="" @@ -206,21 +157,15 @@ export class FrigateCardGalleryCore extends LitElement { title="${child.title}" class="mdc-image-list__image" src="${child.thumbnail}" - .actionHandler=${actionHandler({ - hasHold: false, - hasDoubleClick: false, - })} - @action=${() => { + @click=${() => { if (this.view) { - new View({ - view: this.view.is('clips') - ? 'clip' - : 'snapshot', - camera: this.view.camera, - target: this.view.target ?? undefined, - childIndex: index, - previous: this.view ?? undefined, - }).dispatchChangeEvent(this); + this.view + .evolve({ + view: this.view.is('clips') ? 'clip' : 'snapshot', + childIndex: index, + previous: this.view, + }) + .dispatchChangeEvent(this); } }} />` diff --git a/src/components/viewer.ts b/src/components/viewer.ts index 789513a9..33f3d663 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -33,9 +33,7 @@ import { View } from '../view.js'; import { createMediaShowInfo, dispatchErrorMessageEvent, - dispatchMessageEvent, } from '../common.js'; -import { localize } from '../localize/localize.js'; import { renderProgressIndicator } from '../components/message.js'; import './next-prev-control.js'; @@ -60,44 +58,6 @@ export class FrigateCardViewer extends LitElement { @property({ attribute: false }) protected resolvedMediaCache?: ResolvedMediaCache; - /** - * Asyncronously render the element. - * @returns A rendered template. - */ - protected async _fetchLatestMedia(): Promise { - if (!this.view || !this.hass || !this.browseMediaQueryParameters) { - return; - } - let parent: BrowseMediaSource | null; - try { - parent = await BrowseMediaUtil.browseMediaQuery( - this.hass, - this.browseMediaQueryParameters, - ); - } catch (e) { - return dispatchErrorMessageEvent(this, (e as Error).message); - } - const childIndex = BrowseMediaUtil.getFirstTrueMediaChildIndex(parent); - if (!parent || !parent.children || childIndex == null) { - return dispatchMessageEvent( - this, - this.browseMediaQueryParameters.mediaType == 'clips' - ? localize('common.no_clip') - : localize('common.no_snapshot'), - this.browseMediaQueryParameters.mediaType == 'clips' - ? 'mdi:filmstrip-off' - : 'mdi:camera-off', - ); - } - - this.view - .evolve({ - target: parent, - childIndex: childIndex, - }) - .dispatchChangeEvent(this); - } - /** * Master render method. * @returns A rendered template. @@ -108,7 +68,12 @@ export class FrigateCardViewer extends LitElement { } if (!this.view.target) { - this._fetchLatestMedia(); + BrowseMediaUtil.fetchLatestMediaAndDispatchViewChange( + this, + this.hass, + this.view, + this.browseMediaQueryParameters, + ); return renderProgressIndicator(); }