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()],
);
};