From c910b5d2a4cb47e8c57cc83f539f64557fbf02ee Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Wed, 21 Sep 2022 20:57:53 -0700 Subject: [PATCH] Fix substantial performance issue with thumbnails. --- src/components/thumbnail.ts | 68 ++++++++++++++++++++++----- src/scss/thumbnail-feature-event.scss | 1 + src/utils/thumbnail.ts | 24 ++++++---- 3 files changed, 70 insertions(+), 23 deletions(-) diff --git a/src/components/thumbnail.ts b/src/components/thumbnail.ts index 9f597684..af124630 100644 --- a/src/components/thumbnail.ts +++ b/src/components/thumbnail.ts @@ -16,6 +16,7 @@ import { renderTask } from '../utils/task.js'; import { createFetchThumbnailTask } from '../utils/thumbnail.js'; import { View } from '../view.js'; import { MediaSeek } from './viewer.js'; +import { TaskStatus } from '@lit-labs/task'; import type { ExtendedHomeAssistant, @@ -38,22 +39,63 @@ export class FrigateCardThumbnailFeatureEvent extends LitElement { this, () => this.hass, () => this.thumbnail, + false, ); + // Only load thumbnails on view in case there is a very large number of them. + protected _intersectionObserver: IntersectionObserver; + + constructor() { + super(); + this._intersectionObserver = new IntersectionObserver( + this._intersectionHandler.bind(this), + ); + } + + /** + * Component connected callback. + */ + connectedCallback(): void { + this._intersectionObserver.observe(this); + super.connectedCallback(); + } + + /** + * Component disconnected callback. + */ + disconnectedCallback(): void { + super.disconnectedCallback(); + this._intersectionObserver.disconnect(); + } + + /** + * Called when the live view intersects with the viewport. + * @param entries The IntersectionObserverEntry entries (should be only 1). + */ + protected _intersectionHandler(entries: IntersectionObserverEntry[]): void { + if ( + this._embedThumbnailTask.status === TaskStatus.INITIAL && + entries.some((entry) => entry.isIntersecting) + ) { + this._embedThumbnailTask.run(); + } + } + protected render(): TemplateResult | void { - return html` - ${this.thumbnail - ? renderTask( - this, - this._embedThumbnailTask, - (embeddedThumbnail: string | null) => - embeddedThumbnail ? html`` : html``, - ) - : html` `} - `; + const imageOff = html` `; + + return html`${this.thumbnail + ? renderTask( + this, + this._embedThumbnailTask, + (embeddedThumbnail: string | null) => + embeddedThumbnail ? html`` : html``, + () => imageOff, + ) + : imageOff} `; } static get styles(): CSSResult { diff --git a/src/scss/thumbnail-feature-event.scss b/src/scss/thumbnail-feature-event.scss index 04df7733..84590fd3 100644 --- a/src/scss/thumbnail-feature-event.scss +++ b/src/scss/thumbnail-feature-event.scss @@ -30,4 +30,5 @@ ha-icon { align-items: center; border: 1px solid rgba(255, 255, 255, 0.3); box-sizing: border-box; + opacity: 0.2; } diff --git a/src/utils/thumbnail.ts b/src/utils/thumbnail.ts index 2a002adb..6c2f0b53 100644 --- a/src/utils/thumbnail.ts +++ b/src/utils/thumbnail.ts @@ -55,19 +55,23 @@ export const createFetchThumbnailTask = ( host: ReactiveControllerHost, getHASS: () => HomeAssistant | undefined, getThumbnailURL: () => string | undefined, + autoRun = true, ): Task => { return new Task( host, - async ([haveHASS, thumbnailURL]: FetchThumbnailTaskArgs): Promise< - string | null - > => { - const hass = getHASS(); - if (!haveHASS || !hass || !thumbnailURL) { - return null; - } - return fetchThumbnail(hass, thumbnailURL); + { + // Do not re-run the task if hass changes, unless it was previously undefined. + args: (): FetchThumbnailTaskArgs => [!!getHASS(), getThumbnailURL()], + task: async ([haveHASS, thumbnailURL]: FetchThumbnailTaskArgs): Promise< + string | null + > => { + const hass = getHASS(); + if (!haveHASS || !hass || !thumbnailURL) { + return null; + } + return fetchThumbnail(hass, thumbnailURL); + }, + autoRun: autoRun, }, - // Do not re-run the task if hass changes, unless it was previously undefined. - (): FetchThumbnailTaskArgs => [!!getHASS(), getThumbnailURL()], ); };