Add new lazily unload criteria .
This commit is contained in:
@@ -1,16 +1,18 @@
|
||||
import { EmblaCarouselType, EmblaEventType, EmblaPluginType } from 'embla-carousel';
|
||||
import { LazyUnloadCondition } from '../../types';
|
||||
|
||||
export type LazyloadOptionsType = {
|
||||
// Number of slides to lazyload left/right of selected (0 == only selected
|
||||
// slide).
|
||||
lazyloadCount?: number;
|
||||
lazyLoadCount?: number;
|
||||
lazyUnloadCondition?: LazyUnloadCondition;
|
||||
|
||||
lazyloadCallback?: (index: number, slide: HTMLElement) => void;
|
||||
lazyunloadCallback?: (index: number, slide: HTMLElement) => void;
|
||||
lazyLoadCallback?: (index: number, slide: HTMLElement) => void;
|
||||
lazyUnloadCallback?: (index: number, slide: HTMLElement) => void;
|
||||
};
|
||||
|
||||
export const defaultOptions: Partial<LazyloadOptionsType> = {
|
||||
lazyloadCount: 0,
|
||||
lazyLoadCount: 0,
|
||||
};
|
||||
|
||||
export type LazyloadType = EmblaPluginType<LazyloadOptionsType> & {
|
||||
@@ -22,7 +24,7 @@ export function Lazyload(userOptions?: LazyloadOptionsType): LazyloadType {
|
||||
|
||||
let carousel: EmblaCarouselType;
|
||||
let slides: HTMLElement[];
|
||||
const isSlideLazyloaded: Record<number, boolean> = {};
|
||||
const lazyLoadedSlides: Set<number> = new Set();
|
||||
|
||||
const loadEvents: EmblaEventType[] = ['init', 'select', 'resize'];
|
||||
const unloadEvents: EmblaEventType[] = ['select'];
|
||||
@@ -34,11 +36,15 @@ export function Lazyload(userOptions?: LazyloadOptionsType): LazyloadType {
|
||||
carousel = embla;
|
||||
slides = carousel.slideNodes();
|
||||
|
||||
if (options.lazyloadCallback) {
|
||||
loadEvents.forEach((evt) => carousel.on(evt, lazyloadHandler));
|
||||
if (options.lazyLoadCallback) {
|
||||
loadEvents.forEach((evt) => carousel.on(evt, lazyLoadHandler));
|
||||
}
|
||||
if (options.lazyunloadCallback) {
|
||||
unloadEvents.forEach((evt) => carousel.on(evt, lazyunloadHandler));
|
||||
if (
|
||||
options.lazyUnloadCallback &&
|
||||
options.lazyUnloadCondition &&
|
||||
['all', 'unselected'].includes(options.lazyUnloadCondition)
|
||||
) {
|
||||
unloadEvents.forEach((evt) => carousel.on(evt, lazyUnloadPreviousHandler));
|
||||
}
|
||||
document.addEventListener('visibilitychange', visibilityHandler);
|
||||
}
|
||||
@@ -47,11 +53,11 @@ export function Lazyload(userOptions?: LazyloadOptionsType): LazyloadType {
|
||||
* Destroy the plugin.
|
||||
*/
|
||||
function destroy(): void {
|
||||
if (options.lazyloadCallback) {
|
||||
loadEvents.forEach((evt) => carousel.off(evt, lazyloadHandler));
|
||||
if (options.lazyLoadCallback) {
|
||||
loadEvents.forEach((evt) => carousel.off(evt, lazyLoadHandler));
|
||||
}
|
||||
if (options.lazyunloadCallback) {
|
||||
unloadEvents.forEach((evt) => carousel.off(evt, lazyunloadHandler));
|
||||
if (options.lazyUnloadCallback) {
|
||||
unloadEvents.forEach((evt) => carousel.off(evt, lazyUnloadPreviousHandler));
|
||||
}
|
||||
document.removeEventListener('visibilitychange', visibilityHandler);
|
||||
}
|
||||
@@ -60,10 +66,15 @@ export function Lazyload(userOptions?: LazyloadOptionsType): LazyloadType {
|
||||
* Handle document visibility changes.
|
||||
*/
|
||||
function visibilityHandler(): void {
|
||||
if (document.visibilityState == 'hidden' && lazyunloadHandler) {
|
||||
lazyunloadHandler();
|
||||
} else if (document.visibilityState == 'visible' && lazyloadHandler) {
|
||||
lazyloadHandler();
|
||||
if (
|
||||
document.visibilityState === 'hidden' &&
|
||||
options.lazyUnloadCallback &&
|
||||
options.lazyUnloadCondition &&
|
||||
['all', 'hidden'].includes(options.lazyUnloadCondition)
|
||||
) {
|
||||
lazyUnloadAllHandler();
|
||||
} else if (document.visibilityState === 'visible' && options.lazyLoadCallback) {
|
||||
lazyLoadHandler();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,14 +84,14 @@ export function Lazyload(userOptions?: LazyloadOptionsType): LazyloadType {
|
||||
* @returns `true` if the slide has been lazily loaded.
|
||||
*/
|
||||
function hasLazyloaded(index: number): boolean {
|
||||
return !!isSlideLazyloaded[index];
|
||||
return lazyLoadedSlides.has(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily load media in the carousel.
|
||||
*/
|
||||
function lazyloadHandler(): void {
|
||||
const lazyLoadCount = options.lazyloadCount ?? 0;
|
||||
function lazyLoadHandler(): void {
|
||||
const lazyLoadCount = options.lazyLoadCount ?? 0;
|
||||
const currentIndex = carousel.selectedScrollSnap();
|
||||
const slidesToLoad = new Set<number>();
|
||||
|
||||
@@ -94,30 +105,34 @@ export function Lazyload(userOptions?: LazyloadOptionsType): LazyloadType {
|
||||
}
|
||||
|
||||
slidesToLoad.forEach((index) => {
|
||||
// Only lazy load slides that are not already loaded.
|
||||
if (isSlideLazyloaded[index]) {
|
||||
return;
|
||||
}
|
||||
if (options.lazyloadCallback) {
|
||||
isSlideLazyloaded[index] = true;
|
||||
options.lazyloadCallback(index, slides[index]);
|
||||
if (!hasLazyloaded(index) && options.lazyLoadCallback) {
|
||||
lazyLoadedSlides.add(index);
|
||||
options.lazyLoadCallback(index, slides[index]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily unload media in the carousel.
|
||||
* Lazily unload all media in the carousel.
|
||||
*/
|
||||
function lazyunloadHandler(): void {
|
||||
function lazyUnloadAllHandler(): void {
|
||||
lazyLoadedSlides.forEach((index) => {
|
||||
if (options.lazyUnloadCallback) {
|
||||
options.lazyUnloadCallback(index, slides[index]);
|
||||
lazyLoadedSlides.delete(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily unload the previously selected media in the carousel.
|
||||
*/
|
||||
function lazyUnloadPreviousHandler(): void {
|
||||
const index = carousel.previousScrollSnap();
|
||||
|
||||
// Only lazy unload slides that are lazy loaded.
|
||||
if (!isSlideLazyloaded[index]) {
|
||||
return;
|
||||
}
|
||||
if (options.lazyunloadCallback) {
|
||||
options.lazyunloadCallback(index, slides[index]);
|
||||
isSlideLazyloaded[index] = false;
|
||||
if (hasLazyloaded(index) && options.lazyUnloadCallback) {
|
||||
options.lazyUnloadCallback(index, slides[index]);
|
||||
lazyLoadedSlides.delete(index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-11
@@ -288,12 +288,14 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
|
||||
return [
|
||||
...super._getPlugins(),
|
||||
Lazyload({
|
||||
lazyloadCallback: this.liveConfig?.lazy_load
|
||||
? (...args) => this._lazyloadOrUnloadSlide('load', ...args)
|
||||
: undefined,
|
||||
lazyunloadCallback: this.liveConfig?.lazy_unload
|
||||
? (...args) => this._lazyloadOrUnloadSlide('unload', ...args)
|
||||
: undefined,
|
||||
...(this.liveConfig?.lazy_load && {
|
||||
lazyLoadCallback: (index, slide) =>
|
||||
this._lazyloadOrUnloadSlide('load', index, slide),
|
||||
}),
|
||||
|
||||
lazyUnloadCondition: this.liveConfig?.lazy_unload,
|
||||
lazyUnloadCallback: (index, slide) =>
|
||||
this._lazyloadOrUnloadSlide('unload', index, slide),
|
||||
}),
|
||||
AutoMediaPlugin({
|
||||
playerSelector: 'frigate-card-live-provider',
|
||||
@@ -380,7 +382,7 @@ export class FrigateCardLiveCarousel extends FrigateCardMediaCarousel {
|
||||
'frigate-card-live-provider',
|
||||
) as FrigateCardLiveProvider;
|
||||
if (liveProvider) {
|
||||
liveProvider.disabled = action == 'load' ? false : true;
|
||||
liveProvider.disabled = action !== 'load';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -795,7 +797,7 @@ export class FrigateCardLiveWebRTCCard extends LitElement {
|
||||
/**
|
||||
* Create the WebRTC element. May throw.
|
||||
*/
|
||||
protected _createWebRTC(): HTMLElement | undefined {
|
||||
protected _createWebRTC(): HTMLElement | null {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const webrtcElement = this._webrtcTask.value;
|
||||
if (webrtcElement && this.hass) {
|
||||
@@ -818,7 +820,7 @@ export class FrigateCardLiveWebRTCCard extends LitElement {
|
||||
webrtc.hass = this.hass;
|
||||
return webrtc;
|
||||
}
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -827,7 +829,7 @@ export class FrigateCardLiveWebRTCCard extends LitElement {
|
||||
*/
|
||||
protected render(): TemplateResult | void {
|
||||
const render = (): TemplateResult | void => {
|
||||
let webrtcElement: HTMLElement | undefined;
|
||||
let webrtcElement: HTMLElement | null;
|
||||
try {
|
||||
webrtcElement = this._createWebRTC();
|
||||
} catch (e) {
|
||||
@@ -973,7 +975,7 @@ export class FrigateCardLiveJSMPEG extends LitElement {
|
||||
canvas: this._jsmpegCanvasElement,
|
||||
},
|
||||
{
|
||||
// The media carousel automatically pauses when the browser tab is
|
||||
// The media carousel may automatically pause when the browser tab is
|
||||
// inactive, JSMPEG does not need to also do so independently.
|
||||
pauseWhenHidden: false,
|
||||
protocols: [],
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
CONF_IMAGE_URL,
|
||||
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_SIZE,
|
||||
CONF_LIVE_CONTROLS_THUMBNAILS_SIZE,
|
||||
CONF_LIVE_LAZY_UNLOAD,
|
||||
CONF_LIVE_PRELOAD,
|
||||
CONF_LIVE_WEBRTC_CARD,
|
||||
CONF_MENU,
|
||||
@@ -322,6 +323,19 @@ const upgradeWithOverrides = function (
|
||||
return upgradeMoveToWithOverrides(path, path, transform);
|
||||
};
|
||||
|
||||
/**
|
||||
* Upgrade a property in place without overrides.
|
||||
* @param path The old property path.
|
||||
* @param transform An optional transform for the value.
|
||||
* @returns A function that returns `true` if the configuration was modified.
|
||||
*/
|
||||
const upgrade = function (
|
||||
path: string,
|
||||
transform?: (valueIn: unknown) => unknown,
|
||||
): (obj: RawFrigateCardConfig) => boolean {
|
||||
return upgradeMoveTo(path, path, transform);
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a path to an array, apply an upgrade to each object in the array.
|
||||
* @param arrayPath The path to the array to upgrade.
|
||||
@@ -575,4 +589,7 @@ const UPGRADES = [
|
||||
upgradeWithOverrides(CONF_MENU_BUTTONS_DOWNLOAD, menuButtonBooleanToObject),
|
||||
upgradeWithOverrides(CONF_MENU_BUTTONS_FRIGATE_UI, menuButtonBooleanToObject),
|
||||
upgradeWithOverrides(CONF_MENU_BUTTONS_FULLSCREEN, menuButtonBooleanToObject),
|
||||
upgrade(CONF_LIVE_LAZY_UNLOAD, (val) =>
|
||||
typeof val === 'boolean' ? (val ? 'all' : 'never') : undefined,
|
||||
),
|
||||
];
|
||||
|
||||
+12
-1
@@ -349,6 +349,14 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
{ value: 'auto', label: localize('config.view.dark_modes.auto') },
|
||||
];
|
||||
|
||||
protected _lazyUnloadConditions: EditorSelectOption[] = [
|
||||
{ value: '', label: '' },
|
||||
{ value: 'all', label: localize('config.live.lazy_unload_conditions.all') },
|
||||
{ value: 'unselected', label: localize('config.live.lazy_unload_conditions.unselected') },
|
||||
{ value: 'hidden', label: localize('config.live.lazy_unload_conditions.hidden') },
|
||||
{ value: 'never', label: localize('config.live.lazy_unload_conditions.never') },
|
||||
];
|
||||
|
||||
public setConfig(config: RawFrigateCardConfig): void {
|
||||
// Note: This does not use Zod to parse the configuration, so it may be
|
||||
// partially or completely invalid. It's more useful to have a partially
|
||||
@@ -990,7 +998,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
${this._renderSwitch(CONF_LIVE_PRELOAD, defaults.live.preload)}
|
||||
${this._renderSwitch(CONF_LIVE_DRAGGABLE, defaults.live.draggable)}
|
||||
${this._renderSwitch(CONF_LIVE_LAZY_LOAD, defaults.live.lazy_load)}
|
||||
${this._renderSwitch(CONF_LIVE_LAZY_UNLOAD, defaults.live.lazy_unload)}
|
||||
${this._renderOptionSelector(
|
||||
CONF_LIVE_LAZY_UNLOAD,
|
||||
this._lazyUnloadConditions,
|
||||
)}
|
||||
${this._renderSwitch(CONF_LIVE_AUTO_UNMUTE, defaults.live.auto_unmute)}
|
||||
${this._renderOptionSelector(
|
||||
CONF_LIVE_CONTROLS_NEXT_PREVIOUS_STYLE,
|
||||
|
||||
@@ -117,6 +117,12 @@
|
||||
"draggable": "Live cameras view can be dragged/swiped",
|
||||
"lazy_load": "Live cameras are lazily loaded",
|
||||
"lazy_unload": "Live cameras are lazily unloaded",
|
||||
"lazy_unload_conditions": {
|
||||
"all": "All opportunities",
|
||||
"unselected": "On unselection",
|
||||
"hidden": "On browser/tab hiding",
|
||||
"never": "Never"
|
||||
},
|
||||
"auto_unmute": "Automatically unmute live cameras",
|
||||
"transition_effect": "Live camera transition effect",
|
||||
"controls": {
|
||||
|
||||
+5
-2
@@ -64,6 +64,9 @@ export const FRIGATE_MENU_PRIORITY_MAX = 100;
|
||||
const LIVE_PROVIDERS = ['auto', 'ha', 'frigate-jsmpeg', 'webrtc-card'] as const;
|
||||
export type LiveProvider = typeof LIVE_PROVIDERS[number];
|
||||
|
||||
const LAZY_UNLOAD_CONDITIONS = ['all', 'unselected', 'hidden', 'never'] as const;
|
||||
export type LazyUnloadCondition = typeof LAZY_UNLOAD_CONDITIONS[number];
|
||||
|
||||
export class FrigateCardError extends Error {}
|
||||
|
||||
/**
|
||||
@@ -560,7 +563,7 @@ const liveConfigDefault = {
|
||||
auto_unmute: false,
|
||||
preload: false,
|
||||
lazy_load: true,
|
||||
lazy_unload: false,
|
||||
lazy_unload: 'never' as const,
|
||||
draggable: true,
|
||||
transition_effect: 'slide' as const,
|
||||
controls: {
|
||||
@@ -665,7 +668,7 @@ const liveConfigSchema = liveOverridableConfigSchema
|
||||
auto_unmute: z.boolean().default(liveConfigDefault.auto_unmute),
|
||||
preload: z.boolean().default(liveConfigDefault.preload),
|
||||
lazy_load: z.boolean().default(liveConfigDefault.lazy_load),
|
||||
lazy_unload: z.boolean().default(liveConfigDefault.lazy_unload),
|
||||
lazy_unload: z.enum(LAZY_UNLOAD_CONDITIONS).default(liveConfigDefault.lazy_unload),
|
||||
draggable: z.boolean().default(liveConfigDefault.draggable),
|
||||
transition_effect: transitionEffectConfigSchema.default(
|
||||
liveConfigDefault.transition_effect,
|
||||
|
||||
Reference in New Issue
Block a user