Add initial media filtering for the gallery.

This commit is contained in:
Dermot Duffy
2023-01-24 21:55:38 -08:00
parent 6ca7ef85ec
commit bd19ed7a74
10 changed files with 208 additions and 116 deletions
+3 -2
View File
@@ -268,13 +268,14 @@ export class FrigateCameraManagerEngine implements CameraManagerEngine {
const nativeQuery: NativeFrigateEventQuery = { const nativeQuery: NativeFrigateEventQuery = {
instance_id: instanceID, instance_id: instanceID,
cameras: Array.from(this._getFrigateCameraNamesForCameraIDs(cameras, cameraIDs)), cameras: Array.from(this._getFrigateCameraNamesForCameraIDs(cameras, cameraIDs)),
...(query.what && { label: Array.from(query.what) }), ...(query.what && { labels: Array.from(query.what) }),
...(query.where && { zone: Array.from(query.where) }), ...(query.where && { zones: Array.from(query.where) }),
...(query.end && { before: Math.floor(query.end.getTime() / 1000) }), ...(query.end && { before: Math.floor(query.end.getTime() / 1000) }),
...(query.start && { after: Math.floor(query.start.getTime() / 1000) }), ...(query.start && { after: Math.floor(query.start.getTime() / 1000) }),
...(query.limit && { limit: query.limit }), ...(query.limit && { limit: query.limit }),
...(query.hasClip && { has_clip: query.hasClip }), ...(query.hasClip && { has_clip: query.hasClip }),
...(query.hasSnapshot && { has_snapshot: query.hasSnapshot }), ...(query.hasSnapshot && { has_snapshot: query.hasSnapshot }),
...(query.favorite && { favorites: query.favorite }),
limit: query?.limit ?? CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT, limit: query?.limit ?? CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT,
}; };
+1
View File
@@ -103,6 +103,7 @@ export interface NativeFrigateEventQuery {
limit?: number; limit?: number;
has_clip?: boolean; has_clip?: boolean;
has_snapshot?: boolean; has_snapshot?: boolean;
favorites?: boolean;
} }
/** /**
+6 -6
View File
@@ -87,31 +87,31 @@ export class CameraManager {
public generateDefaultEventQueries( public generateDefaultEventQueries(
cameraIDs: string | Set<string>, cameraIDs: string | Set<string>,
partialQuery: PartialEventQuery, partialQuery?: PartialEventQuery,
): EventQuery[] | null { ): EventQuery[] | null {
return this._generateDefaultQueries(cameraIDs, { return this._generateDefaultQueries(cameraIDs, {
...partialQuery,
type: QueryType.Event, type: QueryType.Event,
...partialQuery,
}); });
} }
public generateDefaultRecordingQueries( public generateDefaultRecordingQueries(
cameraIDs: string | Set<string>, cameraIDs: string | Set<string>,
partialQuery: PartialRecordingQuery, partialQuery?: PartialRecordingQuery,
): RecordingQuery[] | null { ): RecordingQuery[] | null {
return this._generateDefaultQueries(cameraIDs, { return this._generateDefaultQueries(cameraIDs, {
...partialQuery,
type: QueryType.Recording, type: QueryType.Recording,
...partialQuery,
}); });
} }
public generateDefaultRecordingSegmentsQueries( public generateDefaultRecordingSegmentsQueries(
cameraIDs: string | Set<string>, cameraIDs: string | Set<string>,
partialQuery: PartialRecordingSegmentsQuery, partialQuery?: PartialRecordingSegmentsQuery,
): RecordingSegmentsQuery[] | null { ): RecordingSegmentsQuery[] | null {
return this._generateDefaultQueries(cameraIDs, { return this._generateDefaultQueries(cameraIDs, {
...partialQuery,
type: QueryType.RecordingSegments, type: QueryType.RecordingSegments,
...partialQuery,
}); });
} }
+2
View File
@@ -86,6 +86,8 @@ export interface EventQuery extends MediaQuery {
// Frigate equivalent: zone // Frigate equivalent: zone
where?: Set<string>; where?: Set<string>;
favorite?: boolean;
} }
export type PartialEventQuery = Partial<EventQuery>; export type PartialEventQuery = Partial<EventQuery>;
+2
View File
@@ -115,6 +115,8 @@ export class FrigateCardGallery extends LitElement {
.hass=${this.hass} .hass=${this.hass}
.cameras=${this.cameras} .cameras=${this.cameras}
.cameraManager=${this.cameraManager} .cameraManager=${this.cameraManager}
.view=${this.view}
.mediaLimit=${GALLERY_MEDIA_CHUNK_SIZE}
slot="right" slot="right"
> >
</frigate-card-media-filter> </frigate-card-media-filter>
+95 -56
View File
@@ -1,4 +1,11 @@
import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit'; import {
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
unsafeCSS,
} from 'lit';
import { customElement, property } from 'lit/decorators.js'; import { customElement, property } from 'lit/decorators.js';
import { createRef, ref, Ref } from 'lit/directives/ref.js'; import { createRef, ref, Ref } from 'lit/directives/ref.js';
import { DateRange } from '../camera/range'; import { DateRange } from '../camera/range';
@@ -13,9 +20,9 @@ export interface ValueLabel<T> {
} }
export interface MediaFilterCoreSelection { export interface MediaFilterCoreSelection {
camera?: string[]; cameraIDs?: Set<string>;
what?: string[]; what?: Set<string>;
where?: string[]; where?: Set<string>;
when?: MediaFilterCoreWhenSelection; when?: MediaFilterCoreWhenSelection;
favorite?: MediaFilterCoreFavoriteSelection; favorite?: MediaFilterCoreFavoriteSelection;
} }
@@ -25,13 +32,11 @@ type FilterElement<T> = HTMLElement & {
}; };
export enum MediaFilterCoreFavoriteSelection { export enum MediaFilterCoreFavoriteSelection {
All = 'all',
Favorite = 'favorite', Favorite = 'favorite',
NotFavorite = 'not-favorite', NotFavorite = 'not-favorite',
} }
export enum MediaFilterCoreWhen { export enum MediaFilterCoreWhen {
All = 'all',
Today = 'today', Today = 'today',
Yesterday = 'yesterday', Yesterday = 'yesterday',
PastWeek = 'past-week', PastWeek = 'past-week',
@@ -50,10 +55,10 @@ export class FrigateCardMediaFilterCore extends LitElement {
public hass?: ExtendedHomeAssistant; public hass?: ExtendedHomeAssistant;
@property({ attribute: false }) @property({ attribute: false })
public whenOptions?: ValueLabel<MediaFilterCoreWhenSelection>[]; public cameraOptions?: ValueLabel<string>[];
@property({ attribute: false }) @property({ attribute: false })
public cameraOptions?: ValueLabel<string>[]; public whenOptions?: ValueLabel<MediaFilterCoreWhenSelection>[];
@property({ attribute: false }) @property({ attribute: false })
public whatOptions?: ValueLabel<string>[]; public whatOptions?: ValueLabel<string>[];
@@ -61,54 +66,27 @@ export class FrigateCardMediaFilterCore extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
public whereOptions?: ValueLabel<string>[]; public whereOptions?: ValueLabel<string>[];
protected _refWhen: Ref<FilterElement<MediaFilterCoreWhenSelection>> = createRef(); @property({ attribute: false })
public defaults?: MediaFilterCoreSelection;
protected _cameraOptions?: ValueLabel<string>[];
protected _whenOptions?: ValueLabel<MediaFilterCoreWhenSelection>[];
protected _whatOptions?: ValueLabel<string>[];
protected _whereOptions?: ValueLabel<string>[];
protected _favoriteOptions: ValueLabel<MediaFilterCoreFavoriteSelection>[];
protected _refCamera: Ref<FilterElement<string>> = createRef(); protected _refCamera: Ref<FilterElement<string>> = createRef();
protected _refWhen: Ref<FilterElement<MediaFilterCoreWhenSelection>> = createRef();
protected _refWhat: Ref<FilterElement<string>> = createRef(); protected _refWhat: Ref<FilterElement<string>> = createRef();
protected _refWhere: Ref<FilterElement<string>> = createRef(); protected _refWhere: Ref<FilterElement<string>> = createRef();
protected _refFavorite: Ref<FilterElement<MediaFilterCoreFavoriteSelection>> = protected _refFavorite: Ref<FilterElement<MediaFilterCoreFavoriteSelection>> =
createRef(); createRef();
protected _valueChangedHandler(ev: CustomEvent<{ value: unknown }>): void { constructor() {
// Handler is called on initial load -- skip it. super();
if (!ev.detail.value) { this._favoriteOptions = [
return;
}
const values: MediaFilterCoreSelection = {
...(this._refWhen.value && {
when: this._refWhen.value.selectedItem?.value as MediaFilterCoreWhenSelection,
}),
...(this._refCamera.value && {
camera: this._refCamera.value.selectedItem?.value
? [this._refCamera.value.selectedItem?.value]
: undefined,
}),
...(this._refWhat.value && {
what: this._refWhat.value.selectedItem?.value
? [this._refWhat.value.selectedItem?.value]
: undefined,
}),
...(this._refWhere.value && {
where: this._refWhere.value.selectedItem?.value
? [this._refWhere.value.selectedItem?.value]
: undefined,
}),
...(this._refFavorite.value && {
favorite: this._refFavorite.value.selectedItem?.value as
| MediaFilterCoreFavoriteSelection
| undefined,
}),
};
dispatchFrigateCardEvent(this, 'media-filter-core:change', values);
}
/**
* Master render method.
* @returns A rendered template.
*/
protected render(): TemplateResult | void {
const favoriteOptions: ValueLabel<MediaFilterCoreFavoriteSelection>[] = [
{ {
value: MediaFilterCoreFavoriteSelection.All, value: undefined,
label: localize('media_filter.all'), label: localize('media_filter.all'),
}, },
{ {
@@ -120,12 +98,54 @@ export class FrigateCardMediaFilterCore extends LitElement {
label: localize('media_filter.not_favorite'), label: localize('media_filter.not_favorite'),
}, },
]; ];
}
protected _valueChangedHandler(ev: CustomEvent<{ value: unknown }>): void {
// Handler is called on initial load -- skip it.
if (!ev.detail.value) {
return;
}
const values: MediaFilterCoreSelection = {
...(this._refCamera.value &&
this._refCamera.value.selectedItem?.value && {
cameraIDs: new Set([this._refCamera.value.selectedItem.value]),
}),
...(this._refWhen.value &&
this._refWhen.value.selectedItem?.value && {
when: this._refWhen.value.selectedItem?.value,
}),
...(this._refWhat.value &&
this._refWhat.value.selectedItem?.value && {
what: new Set([this._refWhat.value.selectedItem.value]),
}),
...(this._refWhere.value &&
this._refWhere.value.selectedItem?.value && {
where: new Set([this._refWhere.value.selectedItem?.value]),
}),
...(this._refFavorite.value &&
this._refFavorite.value.selectedItem?.value !== undefined && {
favorite: this._refFavorite.value.selectedItem?.value,
}),
};
dispatchFrigateCardEvent(this, 'media-filter-core:change', values);
}
protected willUpdate(changedProps: PropertyValues): void {
if (changedProps.has('cameraOptions')) {
this._cameraOptions = [
{
value: undefined,
label: localize('media_filter.all'),
},
...(this.cameraOptions ?? []),
];
}
if (changedProps.has('whenOptions')) {
// Time based options are not pre-computed here to ensure relative dates // Time based options are not pre-computed here to ensure relative dates
// (e.g. 'today') are always calculated when activated not when rendered. // (e.g. 'today') are always calculated when activated not when rendered.
const whenOptions = [ this._whenOptions = [
{ {
value: { selection: MediaFilterCoreWhen.All }, value: undefined,
label: localize('media_filter.all'), label: localize('media_filter.all'),
}, },
{ {
@@ -146,13 +166,32 @@ export class FrigateCardMediaFilterCore extends LitElement {
}, },
...(this.whenOptions ?? []), ...(this.whenOptions ?? []),
]; ];
}
if (changedProps.has('whatOptions')) {
this._whatOptions = [
{ value: undefined, label: localize('media_filter.all') },
...(this.whatOptions ?? []),
];
}
if (changedProps.has('whereOptions')) {
this._whereOptions = [
{ value: undefined, label: localize('media_filter.all') },
...(this.whereOptions ?? []),
];
}
}
/**
* Master render method.
* @returns A rendered template.
*/
protected render(): TemplateResult | void {
return html` return html`
<ha-combo-box <ha-combo-box
${ref(this._refWhen)} ${ref(this._refWhen)}
.hass=${this.hass} .hass=${this.hass}
.label=${localize('media_filter.when')} .label=${localize('media_filter.when')}
.items=${whenOptions} .items=${this._whenOptions}
.allowCustomValue=${false} .allowCustomValue=${false}
@value-changed=${this._valueChangedHandler.bind(this)} @value-changed=${this._valueChangedHandler.bind(this)}
></ha-combo-box> ></ha-combo-box>
@@ -161,7 +200,7 @@ export class FrigateCardMediaFilterCore extends LitElement {
${ref(this._refCamera)} ${ref(this._refCamera)}
.hass=${this.hass} .hass=${this.hass}
.label=${localize('media_filter.camera')} .label=${localize('media_filter.camera')}
.items=${this.cameraOptions} .items=${this._cameraOptions}
.allowCustomValue=${false} .allowCustomValue=${false}
@value-changed=${this._valueChangedHandler.bind(this)} @value-changed=${this._valueChangedHandler.bind(this)}
></ha-combo-box>` ></ha-combo-box>`
@@ -171,7 +210,7 @@ export class FrigateCardMediaFilterCore extends LitElement {
${ref(this._refWhat)} ${ref(this._refWhat)}
.hass=${this.hass} .hass=${this.hass}
.label=${localize('media_filter.what')} .label=${localize('media_filter.what')}
.items=${this.whatOptions} .items="${this._whatOptions}"
.allowCustomValue=${false} .allowCustomValue=${false}
@value-changed=${this._valueChangedHandler.bind(this)} @value-changed=${this._valueChangedHandler.bind(this)}
></ha-combo-box>` ></ha-combo-box>`
@@ -181,7 +220,7 @@ export class FrigateCardMediaFilterCore extends LitElement {
${ref(this._refWhere)} ${ref(this._refWhere)}
.hass=${this.hass} .hass=${this.hass}
.label=${localize('media_filter.where')} .label=${localize('media_filter.where')}
.items=${this.whereOptions} .items=${this._whereOptions}
.allowCustomValue=${false} .allowCustomValue=${false}
@value-changed=${this._valueChangedHandler.bind(this)} @value-changed=${this._valueChangedHandler.bind(this)}
></ha-combo-box>` ></ha-combo-box>`
@@ -190,7 +229,7 @@ export class FrigateCardMediaFilterCore extends LitElement {
${ref(this._refFavorite)} ${ref(this._refFavorite)}
.hass=${this.hass} .hass=${this.hass}
.label=${localize('media_filter.favorite')} .label=${localize('media_filter.favorite')}
.items=${favoriteOptions} .items=${this._favoriteOptions}
.allowCustomValue=${false} .allowCustomValue=${false}
@value-changed=${this._valueChangedHandler.bind(this)} @value-changed=${this._valueChangedHandler.bind(this)}
></ha-combo-box> ></ha-combo-box>
+50 -16
View File
@@ -1,8 +1,9 @@
import { sub } from 'date-fns'; import sub from 'date-fns/sub';
import endOfDay from 'date-fns/endOfDay'; import endOfDay from 'date-fns/endOfDay';
import endOfYesterday from 'date-fns/endOfYesterday'; import endOfYesterday from 'date-fns/endOfYesterday';
import endOfToday from 'date-fns/esm/endOfToday'; import endOfToday from 'date-fns/esm/endOfToday';
import startOfToday from 'date-fns/esm/startOfToday'; import startOfToday from 'date-fns/esm/startOfToday';
import startOfDay from 'date-fns/startOfDay';
import startOfYesterday from 'date-fns/startOfYesterday'; import startOfYesterday from 'date-fns/startOfYesterday';
import { import {
css, css,
@@ -17,6 +18,7 @@ import { CameraManager } from '../camera/manager';
import { DateRange } from '../camera/range'; import { DateRange } from '../camera/range';
import { CameraConfig, ExtendedHomeAssistant } from '../types'; import { CameraConfig, ExtendedHomeAssistant } from '../types';
import { getCameraTitle } from '../utils/camera'; import { getCameraTitle } from '../utils/camera';
import { View } from '../view/view';
import { import {
MediaFilterCoreFavoriteSelection, MediaFilterCoreFavoriteSelection,
MediaFilterCoreSelection, MediaFilterCoreSelection,
@@ -26,6 +28,9 @@ import {
} from './media-filter-core'; } from './media-filter-core';
import './surround.js'; import './surround.js';
import './timeline-core.js'; import './timeline-core.js';
import { EventQuery, QueryType } from '../camera/types';
import { EventMediaQueries } from '../view/media-queries';
import { createViewForEvents } from '../utils/media-to-view.js';
@customElement('frigate-card-media-filter') @customElement('frigate-card-media-filter')
export class FrigateCardMediaFilter extends LitElement { export class FrigateCardMediaFilter extends LitElement {
@@ -38,6 +43,12 @@ export class FrigateCardMediaFilter extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
public cameraManager?: CameraManager; public cameraManager?: CameraManager;
@property({ attribute: false })
public view?: View;
@property({ attribute: false })
public mediaLimit?: number;
protected _cameraOptions: ValueLabel<string>[] = []; protected _cameraOptions: ValueLabel<string>[] = [];
protected _convertWhenToDateRange( protected _convertWhenToDateRange(
@@ -46,9 +57,6 @@ export class FrigateCardMediaFilter extends LitElement {
if (!value) { if (!value) {
return null; return null;
} }
if (value.selection === MediaFilterCoreWhen.Custom && value.custom) {
return value.custom;
}
const now = new Date(); const now = new Date();
switch (value.selection) { switch (value.selection) {
case MediaFilterCoreWhen.Today: case MediaFilterCoreWhen.Today:
@@ -56,9 +64,13 @@ export class FrigateCardMediaFilter extends LitElement {
case MediaFilterCoreWhen.Yesterday: case MediaFilterCoreWhen.Yesterday:
return { start: startOfYesterday(), end: endOfYesterday() }; return { start: startOfYesterday(), end: endOfYesterday() };
case MediaFilterCoreWhen.PastWeek: case MediaFilterCoreWhen.PastWeek:
return { start: sub(now, { days: 7 }), end: endOfDay(now) }; return { start: startOfDay(sub(now, { days: 7 })), end: endOfDay(now) };
case MediaFilterCoreWhen.PastMonth: case MediaFilterCoreWhen.PastMonth:
return { start: sub(now, { months: 1 }), end: endOfDay(now) }; return { start: startOfDay(sub(now, { months: 1 })), end: endOfDay(now) };
case MediaFilterCoreWhen.Custom:
if (value.custom) {
return value.custom;
}
} }
return null; return null;
} }
@@ -66,22 +78,44 @@ export class FrigateCardMediaFilter extends LitElement {
protected _convertFavoriteToBoolean( protected _convertFavoriteToBoolean(
value?: MediaFilterCoreFavoriteSelection, value?: MediaFilterCoreFavoriteSelection,
): boolean | null { ): boolean | null {
if (!value || value === MediaFilterCoreFavoriteSelection.All) { if (!value) {
return null; return null;
} }
return value === MediaFilterCoreFavoriteSelection.Favorite; return value === MediaFilterCoreFavoriteSelection.Favorite;
} }
protected _mediaFilterHandler(ev: CustomEvent<MediaFilterCoreSelection>): void { protected async _mediaFilterHandler(
const convertedTime = this._convertWhenToDateRange(ev.detail.when); ev: CustomEvent<MediaFilterCoreSelection>,
const convertedFavorite = this._convertFavoriteToBoolean(ev.detail.favorite); ): Promise<void> {
const details = { if (!this.cameras || !this.cameraManager || !this.hass || !this.view) {
...ev.detail, return;
...(convertedTime && { when: convertedTime }), }
...(convertedFavorite && { favorite: convertedFavorite }), const mediaFilter = ev.detail;
const convertedTime = this._convertWhenToDateRange(mediaFilter.when);
const convertedFavorite = this._convertFavoriteToBoolean(mediaFilter.favorite);
const query: EventQuery = {
type: QueryType.Event,
cameraIDs: mediaFilter.cameraIDs ?? new Set(this.cameras.keys()),
...(mediaFilter.what && { what: mediaFilter.what }),
...(mediaFilter.where && { where: mediaFilter.where }),
...(convertedFavorite !== null && { favorite: convertedFavorite }),
...(convertedTime && { start: convertedTime.start, end: convertedTime.end }),
...(this.mediaLimit && { limit: this.mediaLimit }),
}; };
// TODO: remove.
console.debug('Received media filter choices:', details); (
await createViewForEvents(
this,
this.hass,
this.cameraManager,
this.cameras,
this.view,
{
query: new EventMediaQueries([query]),
},
)
)?.dispatchChangeEvent(this);
} }
protected willUpdate(changedProps: PropertyValues): void { protected willUpdate(changedProps: PropertyValues): void {
+24 -12
View File
@@ -1,6 +1,5 @@
import format from 'date-fns/format'; import format from 'date-fns/format';
import fromUnixTime from 'date-fns/fromUnixTime'; import { CSSResult, html, LitElement, PropertyValues, TemplateResult, unsafeCSS } from 'lit';
import { CSSResult, html, LitElement, TemplateResult, unsafeCSS } from 'lit';
import { customElement, property } from 'lit/decorators.js'; import { customElement, property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js'; import { classMap } from 'lit/directives/class-map.js';
import { localize } from '../localize/localize.js'; import { localize } from '../localize/localize.js';
@@ -12,9 +11,9 @@ import { stopEventFromActivatingCardWideActions } from '../utils/action.js';
import { errorToConsole, getDurationString, prettifyTitle } from '../utils/basic.js'; import { errorToConsole, getDurationString, prettifyTitle } from '../utils/basic.js';
import { getCameraTitle } from '../utils/camera.js'; import { getCameraTitle } from '../utils/camera.js';
import { renderTask } from '../utils/task.js'; import { renderTask } from '../utils/task.js';
import { createFetchThumbnailTask } from '../utils/thumbnail.js'; import { createFetchThumbnailTask, FetchThumbnailTaskArgs } from '../utils/thumbnail.js';
import { View } from '../view/view.js'; import { View } from '../view/view.js';
import { TaskStatus } from '@lit-labs/task'; import { Task, TaskStatus } from '@lit-labs/task';
import type { CameraConfig, ExtendedHomeAssistant } from '../types.js'; import type { CameraConfig, ExtendedHomeAssistant } from '../types.js';
import { EventViewMedia, RecordingViewMedia, ViewMedia } from '../view/media.js'; import { EventViewMedia, RecordingViewMedia, ViewMedia } from '../view/media.js';
@@ -32,12 +31,7 @@ export class FrigateCardThumbnailFeatureEvent extends LitElement {
@property({ attribute: false }) @property({ attribute: false })
public hass?: ExtendedHomeAssistant; public hass?: ExtendedHomeAssistant;
protected _embedThumbnailTask = createFetchThumbnailTask( protected _embedThumbnailTask?: Task<FetchThumbnailTaskArgs, string | null>;
this,
() => this.hass,
() => this.thumbnail,
false,
);
// Only load thumbnails on view in case there is a very large number of them. // Only load thumbnails on view in case there is a very large number of them.
protected _intersectionObserver: IntersectionObserver; protected _intersectionObserver: IntersectionObserver;
@@ -65,20 +59,38 @@ export class FrigateCardThumbnailFeatureEvent extends LitElement {
this._intersectionObserver.disconnect(); this._intersectionObserver.disconnect();
} }
protected willUpdate(changedProps: PropertyValues): void {
if (changedProps.has('thumbnail')) {
this._embedThumbnailTask = createFetchThumbnailTask(
this,
() => this.hass,
() => this.thumbnail,
false,
);
// Reset the observer so the initial intersection handler call will set
// the visibility correctly.
this._intersectionObserver.unobserve(this);
this._intersectionObserver.observe(this);
}
}
/** /**
* Called when the live view intersects with the viewport. * Called when the live view intersects with the viewport.
* @param entries The IntersectionObserverEntry entries (should be only 1). * @param entries The IntersectionObserverEntry entries (should be only 1).
*/ */
protected _intersectionHandler(entries: IntersectionObserverEntry[]): void { protected _intersectionHandler(entries: IntersectionObserverEntry[]): void {
if ( if (
this._embedThumbnailTask.status === TaskStatus.INITIAL && this._embedThumbnailTask?.status === TaskStatus.INITIAL &&
entries.some((entry) => entry.isIntersecting) entries.some((entry) => entry.isIntersecting)
) { ) {
this._embedThumbnailTask.run(); this._embedThumbnailTask?.run();
} }
} }
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
if (!this._embedThumbnailTask) {
return;
}
const imageOff = html`<ha-icon const imageOff = html`<ha-icon
icon="mdi:image-off" icon="mdi:image-off"
title=${localize('thumbnail.no_thumbnail')} title=${localize('thumbnail.no_thumbnail')}
+1 -1
View File
@@ -42,7 +42,7 @@ export const fetchThumbnail = async (
}); });
}; };
type FetchThumbnailTaskArgs = [boolean, string | undefined]; export type FetchThumbnailTaskArgs = [boolean, string | undefined];
/** /**
* Create a Lit task to fetch a thumbnail. * Create a Lit task to fetch a thumbnail.
+1
View File
@@ -9,6 +9,7 @@
// - TODO: Handle all TODOs in media filter and media filter core. // - TODO: Handle all TODOs in media filter and media filter core.
// - TODO: Make media filter slot choice configurable (gallery.ts). // - TODO: Make media filter slot choice configurable (gallery.ts).
// - TODO: Filter panel expands from right can occasionally 'stick' open. // - TODO: Filter panel expands from right can occasionally 'stick' open.
// - TODO: Media type selection, i.e. events or recordings.
// Hard: // Hard:
// - TODO: Implement gallery. // - TODO: Implement gallery.