tweb/src/lib/rlottie/rlottieIcon.ts

207 lines
5.8 KiB
TypeScript

/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import liteMode from '../../helpers/liteMode';
import noop from '../../helpers/noop';
import safeAssign from '../../helpers/object/safeAssign';
import rootScope from '../rootScope';
import lottieLoader, {LottieAssetName} from './lottieLoader';
import type RLottiePlayer from './rlottiePlayer';
import {RLottieColor} from './rlottiePlayer';
export type RLottieIconOptions = {
width: number,
height: number,
container?: HTMLElement,
skipAnimation?: boolean
};
export type RLottieIconItemPartOptions = {
startFrame: number,
endFrame: number,
name?: string
};
export type RLottieIconItemOptions = {
name: LottieAssetName,
parts: RLottieIconItemPartOptions[],
initFrame?: number,
player?: RLottiePlayer,
autoplay?: boolean,
color?: RLottieColor,
inverseColor?: RLottieColor,
};
export class RLottieIconItemPart implements RLottieIconItemPartOptions {
public startFrame: number;
public endFrame: number;
public name?: string;
constructor(public item: RLottieIconItem, options: RLottieIconItemPartOptions) {
safeAssign(this, options);
}
public play(callback?: () => void) {
return this.item.playPart(this, callback);
}
}
// export type RLottieIconItemPart = RLottieIconItemPartOptions;
export class RLottieIconItem implements RLottieIconItemOptions {
public name: LottieAssetName;
public parts: RLottieIconItemPart[];
public player: RLottiePlayer;
public initFrame: number;
public autoplay: boolean;
public color: RLottieColor;
public inverseColor: RLottieColor;
public loadPromise: Promise<void>;
public onLoadForPart: () => void;
public onLoadForColor: () => void;
constructor(public icon: RLottieIcon, options: RLottieIconItemOptions) {
this.autoplay = false;
safeAssign(this, options);
this.parts = this.parts.map((options) => this.createPart(options));
}
public load() {
let loadPromise = this.loadPromise;
if(loadPromise) {
return loadPromise;
}
const {container, canvas, width, height} = this.icon;
loadPromise = lottieLoader.loadAnimationAsAsset({
container,
canvas,
width,
height,
group: 'none',
loop: false,
autoplay: this.autoplay ?? false,
initFrame: this.initFrame,
skipFirstFrameRendering: this.initFrame === undefined,
color: this.color,
inverseColor: this.inverseColor
}, this.name).then((player) => {
return lottieLoader.waitForFirstFrame(player);
}).then((player) => {
this.player = player;
if(this.onLoadForColor) {
this.onLoadForColor();
this.onLoadForColor = undefined;
}
if(this.onLoadForPart) {
this.onLoadForPart();
this.onLoadForPart = undefined;
}
});
this.loadPromise = loadPromise;
this.icon.loadPromises.set(this.name, loadPromise);
return loadPromise;
}
public createPart(options: RLottieIconItemPartOptions) {
return new RLottieIconItemPart(this, options);
}
public getPart(index: string | number | RLottieIconItemPart) {
if(index instanceof RLottieIconItemPart) return index;
else if(typeof(index) === 'string') return this.parts.find((part) => part.name === index);
else return this.parts[index];
}
public playPart(part: RLottieIconItemPart, callback?: () => void) {
return this.icon.playPart(this, part, callback);
}
}
export default class RLottieIcon {
public container: HTMLElement;
public canvas: HTMLCanvasElement;
public width: number;
public height: number;
protected items: Map<LottieAssetName, RLottieIconItem>;
public loadPromises: Map<LottieAssetName, Promise<void>>;
protected skipAnimation: boolean;
constructor(options: RLottieIconOptions) {
safeAssign(this, options);
if(!this.container) this.container = document.createElement('div');
this.container.classList.add('rlottie-icon');
const {width, height} = this;
this.container.style.width = width + 'px';
this.container.style.height = height + 'px';
const canvas = this.canvas = document.createElement('canvas');
canvas.classList.add('rlottie');
canvas.width = width;
canvas.height = height;
this.items = new Map();
this.loadPromises = new Map();
}
public get loadPromise() {
return Promise.all([...this.loadPromises.values()]).then(noop);
}
public getItem(name?: LottieAssetName): RLottieIconItem {
return !name && this.items.size === 1 ? this.items.values().next().value : this.items.get(name);
}
public add(options: Omit<RLottieIconItemOptions, 'player'>) {
const item = new RLottieIconItem(this, options);
this.items.set(options.name, item);
return item;
}
public playPart(item: RLottieIconItem, index: Parameters<RLottieIconItem['getPart']>[0], callback?: () => void) {
if(!item.player) {
item.onLoadForPart = () => {
this.playPart(item, index, callback);
};
return;
}
const part = item.getPart(index);
item.player.playPart({
from: liteMode.isAvailable('animations') && !this.skipAnimation ? part.startFrame : part.endFrame,
to: part.endFrame,
callback
});
}
/* public playToPart(item: RLottieIconItem, index: Parameters<RLottieIconItem['getPart']>[0], toEnd: boolean) {
if(!item.player) return;
const part = item.getPart(index);
const toFrame = toEnd ? part.endFrame : part.startFrame;
item.player.playToFrame({
frame: toFrame
});
} */
public static generateEqualParts(length: number, frameCount: number): RLottieIconItemPartOptions[] {
return new Array(length).fill(0).map((_, idx) => {
const startFrame = idx * frameCount;
return {startFrame, endFrame: startFrame + frameCount - 1};
});
}
}