fix: Fix issues with lazy loading and overridden configuration (#2012)

- Closes #2011
This commit is contained in:
Dermot Duffy
2025-04-15 17:10:18 +01:00
committed by GitHub
parent 54ee1e1cf3
commit 168b9b0c90
4 changed files with 62 additions and 48 deletions
+10 -7
View File
@@ -7,22 +7,25 @@ export class LazyLoadController implements ReactiveController {
private _host: ReactiveControllerHost & HTMLElement; private _host: ReactiveControllerHost & HTMLElement;
private _documentVisible = true; private _documentVisible = true;
private _intersects = false; private _intersects = false;
private _loaded: boolean; private _loaded = false;
private _unloadConditions: LazyUnloadCondition[] | null = null; private _unloadConditions: LazyUnloadCondition[] | null = null;
private _intersectionObserver = new IntersectionObserver( private _intersectionObserver = new IntersectionObserver(
this._intersectionHandler.bind(this), this._intersectionHandler.bind(this),
); );
private _listeners: LazyLoadListener[] = []; private _listeners: LazyLoadListener[] = [];
constructor( constructor(host: ReactiveControllerHost & HTMLElement) {
host: ReactiveControllerHost & HTMLElement, this._host = host;
this._host.addController(this);
}
public setConfiguration(
lazyLoad?: boolean, lazyLoad?: boolean,
lazyUnloadConditions?: LazyUnloadCondition[], lazyUnloadConditions?: LazyUnloadCondition[],
) { ) {
this._host = host; if (!lazyLoad && !this._loaded) {
this._host.addController(this); this._setLoaded(true);
}
this._loaded = !lazyLoad;
this._unloadConditions = lazyUnloadConditions ?? null; this._unloadConditions = lazyUnloadConditions ?? null;
} }
+12 -11
View File
@@ -69,7 +69,7 @@ export class AdvancedCameraCardLiveProvider extends LitElement implements MediaP
protected _showStreamTroubleshooting = false; protected _showStreamTroubleshooting = false;
protected _refProvider: Ref<MediaPlayerElement> = createRef(); protected _refProvider: Ref<MediaPlayerElement> = createRef();
protected _lazyLoadController: LazyLoadController | null = null; protected _lazyLoadController: LazyLoadController = new LazyLoadController(this);
// A note on dynamic imports: // A note on dynamic imports:
// //
@@ -83,6 +83,16 @@ export class AdvancedCameraCardLiveProvider extends LitElement implements MediaP
// background. These calls fail without waiting for loading here. // background. These calls fail without waiting for loading here.
protected _importPromises: Promise<unknown>[] = []; protected _importPromises: Promise<unknown>[] = [];
constructor() {
super();
this._lazyLoadController.addListener((loaded: boolean) => {
if (!loaded) {
this._isVideoMediaLoaded = false;
dispatchMediaUnloadedEvent(this);
}
});
}
public async getMediaPlayerController(): Promise<MediaPlayerController | null> { public async getMediaPlayerController(): Promise<MediaPlayerController | null> {
await this.updateComplete; await this.updateComplete;
return (await this._refProvider.value?.getMediaPlayerController()) ?? null; return (await this._refProvider.value?.getMediaPlayerController()) ?? null;
@@ -145,19 +155,10 @@ export class AdvancedCameraCardLiveProvider extends LitElement implements MediaP
changedProps.has('liveConfig') || changedProps.has('liveConfig') ||
(!this._lazyLoadController && this.liveConfig) (!this._lazyLoadController && this.liveConfig)
) { ) {
this._lazyLoadController?.destroy(); this._lazyLoadController.setConfiguration(
this._lazyLoadController?.removeController();
this._lazyLoadController = new LazyLoadController(
this,
this.liveConfig?.lazy_load, this.liveConfig?.lazy_load,
this.liveConfig?.lazy_unload, this.liveConfig?.lazy_unload,
); );
this._lazyLoadController.addListener((loaded: boolean) => {
if (!loaded) {
this._isVideoMediaLoaded = false;
dispatchMediaUnloadedEvent(this);
}
});
} }
if (changedProps.has('liveConfig')) { if (changedProps.has('liveConfig')) {
+7 -8
View File
@@ -69,11 +69,16 @@ export class AdvancedCameraCardViewerProvider extends LitElement implements Medi
public cardWideConfig?: CardWideConfig; public cardWideConfig?: CardWideConfig;
protected _refProvider: Ref<MediaPlayerElement> = createRef(); protected _refProvider: Ref<MediaPlayerElement> = createRef();
protected _lazyLoadController: LazyLoadController | null = null; protected _lazyLoadController: LazyLoadController = new LazyLoadController(this);
@state() @state()
protected _url: string | null = null; protected _url: string | null = null;
constructor() {
super();
this._lazyLoadController.addListener((loaded) => loaded && this._setURL());
}
public async getMediaPlayerController(): Promise<MediaPlayerController | null> { public async getMediaPlayerController(): Promise<MediaPlayerController | null> {
await this.updateComplete; await this.updateComplete;
return (await this._refProvider.value?.getMediaPlayerController()) ?? null; return (await this._refProvider.value?.getMediaPlayerController()) ?? null;
@@ -184,13 +189,7 @@ export class AdvancedCameraCardViewerProvider extends LitElement implements Medi
changedProps.has('viewerConfig') || changedProps.has('viewerConfig') ||
(!this._lazyLoadController && this.viewerConfig) (!this._lazyLoadController && this.viewerConfig)
) { ) {
this._lazyLoadController?.destroy(); this._lazyLoadController.setConfiguration(this.viewerConfig?.lazy_load);
this._lazyLoadController?.removeController();
this._lazyLoadController = new LazyLoadController(
this,
this.viewerConfig?.lazy_load,
);
this._lazyLoadController.addListener((loaded) => loaded && this._setURL());
} }
if ( if (
@@ -37,13 +37,14 @@ describe('LazyLoadController', () => {
vi.restoreAllMocks(); vi.restoreAllMocks();
}); });
it('should be loaded by default', () => { it('should be unloaded by default', () => {
const controller = new LazyLoadController(createLitElement()); const controller = new LazyLoadController(createLitElement());
expect(controller.isLoaded()).toBe(true); expect(controller.isLoaded()).toBe(false);
}); });
it('should not be loaded by default when lazy load is set to true', () => { it('should not be loaded by default when lazy load is set to true', () => {
const controller = new LazyLoadController(createLitElement(), true); const controller = new LazyLoadController(createLitElement());
controller.setConfiguration(true);
expect(controller.isLoaded()).toBe(false); expect(controller.isLoaded()).toBe(false);
}); });
@@ -61,10 +62,8 @@ describe('LazyLoadController', () => {
}); });
it('should remove handlers and listeners on destroy', () => { it('should remove handlers and listeners on destroy', () => {
const controller = new LazyLoadController(createLitElement(), true, [ const controller = new LazyLoadController(createLitElement());
'unselected', controller.setConfiguration(true, ['unselected', 'hidden']);
'hidden',
]);
controller.hostConnected(); controller.hostConnected();
const listener = vi.fn(); const listener = vi.fn();
@@ -84,9 +83,26 @@ describe('LazyLoadController', () => {
expect(listener).not.toBeCalled(); expect(listener).not.toBeCalled();
}); });
describe('should set configuration', () => {
it('should set loaded if lazy loading set to false', () => {
const listener = vi.fn();
const controller = new LazyLoadController(createLitElement());
controller.addListener(listener);
expect(controller.isLoaded()).toBe(false);
expect(listener).not.toBeCalled();
controller.setConfiguration(false);
expect(controller.isLoaded()).toBe(true);
expect(listener).toBeCalled();
});
});
describe('should lazy load', () => { describe('should lazy load', () => {
it('should load when both visible and intersecting', () => { it('should load when both visible and intersecting', () => {
const controller = new LazyLoadController(createLitElement(), true); const controller = new LazyLoadController(createLitElement());
controller.setConfiguration(true);
controller.hostConnected(); controller.hostConnected();
expect(controller.isLoaded()).toBe(false); expect(controller.isLoaded()).toBe(false);
@@ -102,6 +118,9 @@ describe('LazyLoadController', () => {
describe('should lazy unload', () => { describe('should lazy unload', () => {
it('should unload on DOM disconnection', () => { it('should unload on DOM disconnection', () => {
const controller = new LazyLoadController(createLitElement()); const controller = new LazyLoadController(createLitElement());
// No lazy loading.
controller.setConfiguration(false);
controller.hostConnected(); controller.hostConnected();
expect(controller.isLoaded()).toBe(true); expect(controller.isLoaded()).toBe(true);
@@ -127,11 +146,8 @@ describe('LazyLoadController', () => {
])( ])(
'when unload conditions are: %s', 'when unload conditions are: %s',
(unloadConditions: LazyUnloadCondition[], shouldBeLoaded: boolean) => { (unloadConditions: LazyUnloadCondition[], shouldBeLoaded: boolean) => {
const controller = new LazyLoadController( const controller = new LazyLoadController(createLitElement());
createLitElement(), controller.setConfiguration(true, unloadConditions);
true,
unloadConditions,
);
controller.hostConnected(); controller.hostConnected();
callIntersectionHandler(true); callIntersectionHandler(true);
@@ -153,11 +169,8 @@ describe('LazyLoadController', () => {
])( ])(
'when unload conditions are: %s', 'when unload conditions are: %s',
(unloadConditions: LazyUnloadCondition[], shouldBeLoaded: boolean) => { (unloadConditions: LazyUnloadCondition[], shouldBeLoaded: boolean) => {
const controller = new LazyLoadController( const controller = new LazyLoadController(createLitElement());
createLitElement(), controller.setConfiguration(true, unloadConditions);
true,
unloadConditions,
);
controller.hostConnected(); controller.hostConnected();
callIntersectionHandler(true); callIntersectionHandler(true);
@@ -173,10 +186,8 @@ describe('LazyLoadController', () => {
it('should call listeners', () => { it('should call listeners', () => {
const listener = vi.fn(); const listener = vi.fn();
const controller = new LazyLoadController(createLitElement(), true, [ const controller = new LazyLoadController(createLitElement());
'unselected', controller.setConfiguration(true, ['unselected', 'hidden']);
'hidden',
]);
controller.hostConnected(); controller.hostConnected();
controller.addListener(listener); controller.addListener(listener);