@v4fire/client
Version:
V4Fire client core library
219 lines (174 loc) • 4.88 kB
text/typescript
/*!
* V4Fire Client Core
* https://github.com/V4Fire/Client
*
* Released under the MIT license
* https://github.com/V4Fire/Client/blob/master/LICENSE
*/
import type ImageLoader from 'core/dom/image/loader';
import {
ID,
INIT_LOAD,
IS_LOADED,
IS_LOADING
} from 'core/dom/image/const';
import type { ImageNode, ImagePlaceholderType } from 'core/dom/image/interface';
/**
* Helper class that provides API to work with an image lifecycle
*/
export default class Lifecycle {
/**
* Parent class
*/
protected parent: ImageLoader;
/**
* @param parent
*/
constructor(parent: ImageLoader) {
this.parent = parent;
}
/**
* Initializes lifecycle of the specified element
* @param el
*/
init(el: ImageNode): void {
const
previewShadowState = this.parent.getShadowStateByType(el, 'preview'),
mainShadowState = this.parent.getShadowStateByType(el, 'main');
if (mainShadowState?.mainOptions.stageClasses) {
this.parent.setLifecycleClass(el, mainShadowState, 'initial');
}
if (previewShadowState != null) {
this.initPlaceholderImage(el, 'preview');
}
this.initMain(el);
}
/**
* Initializes the main image
* @param el
*/
protected initMain(el: ImageNode): void {
const mainShadowState = this.parent.getShadowStateByType(el, 'main');
if (mainShadowState == null) {
return;
}
if (mainShadowState.imgNode.complete === true && mainShadowState.imgNode[IS_LOADED] === true) {
this.onMainImageLoad(el);
} else {
const
$a = mainShadowState.mainOptions.ctx?.unsafe.$async;
if ($a != null) {
mainShadowState.loadPromise = $a.promise(mainShadowState.imgNode.init, {group: '[[v-image:main]]', label: el[ID]})
.then(this.onMainImageLoad.bind(this, el))
.catch(this.onMainImageLoadError.bind(this, el));
} else {
mainShadowState.loadPromise = mainShadowState.imgNode.init
.then(this.onMainImageLoad.bind(this, el))
.catch(this.onMainImageLoadError.bind(this, el));
}
}
}
/**
* Initializes a placeholder image
*
* @param el
* @param type
*/
protected initPlaceholderImage(el: ImageNode, type: ImagePlaceholderType): void {
const
successCallback = this.trySetPlaceholderImage.bind(this, el, type),
errorCallback = this.onPlaceholderImageError.bind(this, el, type);
const
shadowState = this.parent.getShadowStateByType(el, type);
if (shadowState == null) {
return;
}
const
{mainOptions} = shadowState,
{imgNode} = shadowState;
if (imgNode[IS_LOADED] === true) {
// If the img is ready – set it to the element
return successCallback();
}
if (imgNode[IS_LOADING] == null) {
// If the loading hasn't started – this is a broken image that should be loaded lazily
imgNode[INIT_LOAD]!();
}
if (mainOptions.ctx != null) {
shadowState.loadPromise = mainOptions.ctx.unsafe.$async.promise(
imgNode.init,
{
group: `[[v-image:${type}]]`,
label: el[ID]
}
).then(successCallback, errorCallback);
} else {
imgNode.init.then(successCallback, errorCallback);
}
}
/**
* Tries to set a placeholder image to the specified element
*
* @param el
* @param type
*/
protected trySetPlaceholderImage(el: ImageNode, type: ImagePlaceholderType): void {
const
shadowState = this.parent.getShadowStateByType(el, type),
mainShadowState = this.parent.getShadowStateByType(el, 'main');
if (shadowState == null || mainShadowState == null) {
return;
}
const {selfOptions} = shadowState;
selfOptions.load?.(el);
if (mainShadowState.imgNode.complete === true && mainShadowState.isFailed === false) {
// If the main image is ready – ignore the preview
return;
}
this.parent.render(el, shadowState);
}
/**
* Handler: helper image error occurs
*
* @param el
* @param type
*/
protected onPlaceholderImageError(el: ImageNode, type: ImagePlaceholderType): void {
const shadowState = this.parent.getShadowStateByType(el, type);
if (shadowState == null) {
return;
}
shadowState.isFailed = true;
shadowState.selfOptions.error?.(el);
shadowState.loadPromise = undefined;
}
/**
* Handler: main image load complete
* @param el
*/
protected onMainImageLoad(el: ImageNode): void {
const
shadowState = this.parent.getShadowStateByType(el, 'main');
if (shadowState == null) {
return;
}
this.parent.render(el, shadowState);
shadowState.loadPromise = undefined;
shadowState.selfOptions.load?.(el);
}
/**
* Handler: main image loading error
* @param el
*/
protected onMainImageLoadError(el: ImageNode): void {
const
shadowState = this.parent.getShadowStateByType(el, 'main');
if (shadowState == null) {
return;
}
shadowState.loadPromise = undefined;
shadowState.selfOptions.error?.(el);
shadowState.isFailed = true;
this.initPlaceholderImage(el, 'broken');
}
}