tweb/src/components/gifsMasonry.ts

211 lines
5.6 KiB
TypeScript
Raw Permalink Normal View History

2023-01-06 20:27:29 +01:00
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import type {MyDocument} from '../lib/appManagers/appDocsManager';
import animationIntersector, {AnimationItemGroup} from './animationIntersector';
import Scrollable from './scrollable';
import deferredPromise, {CancellablePromise} from '../helpers/cancellablePromise';
import {doubleRaf} from '../helpers/schedulers';
import {AppManagers} from '../lib/appManagers/managers';
import rootScope from '../lib/rootScope';
import LazyLoadQueueRepeat2 from './lazyLoadQueueRepeat2';
import wrapVideo from './wrappers/video';
import noop from '../helpers/noop';
2023-11-28 12:44:50 +01:00
import {MiddlewareHelper, getMiddleware} from '../helpers/middleware';
2024-05-04 15:30:32 +02:00
import positionElementByIndex from '../helpers/dom/positionElementByIndex';
2023-01-06 20:27:29 +01:00
export default class GifsMasonry {
public lazyLoadQueue: LazyLoadQueueRepeat2;
private scrollPromise: CancellablePromise<void> = Promise.resolve();
private timeout: number = 0;
private managers: AppManagers;
2023-11-28 12:44:50 +01:00
private middlewareHelper: MiddlewareHelper;
2024-05-04 15:30:32 +02:00
private map: Map<DocId, HTMLElement>;
2023-01-06 20:27:29 +01:00
constructor(
private element: HTMLElement,
private group: AnimationItemGroup,
private scrollable: Scrollable,
attach = true
) {
this.managers = rootScope.managers;
2023-11-28 12:44:50 +01:00
this.middlewareHelper = getMiddleware();
2024-05-04 15:30:32 +02:00
this.map = new Map();
2023-01-06 20:27:29 +01:00
this.lazyLoadQueue = new LazyLoadQueueRepeat2(undefined, ({target, visible}) => {
if(visible) {
this.processVisibleDiv(target);
} else {
this.processInvisibleDiv(target);
}
});
if(attach) {
this.attach();
}
}
private onScroll = () => {
if(this.timeout) {
clearTimeout(this.timeout);
} else {
this.scrollPromise = deferredPromise<void>();
// animationIntersector.checkAnimations(true, group);
}
this.timeout = window.setTimeout(() => {
this.timeout = 0;
this.scrollPromise.resolve();
// animationIntersector.checkAnimations(false, group);
}, 150);
};
public attach() {
this.scrollable.container.addEventListener('scroll', this.onScroll);
}
public detach() {
this.clear();
this.scrollable.container.removeEventListener('scroll', this.onScroll);
2023-11-28 12:44:50 +01:00
this.middlewareHelper.destroy();
2023-01-06 20:27:29 +01:00
}
public clear() {
this.lazyLoadQueue.clear();
}
private processVisibleDiv(div: HTMLElement) {
const video = div.querySelector('video');
if(video) {
return;
}
const load = () => {
const docId = div.dataset.docId;
const promise = Promise.all([this.managers.appDocsManager.getDoc(docId), this.scrollPromise]).then(async([doc]) => {
2024-05-04 15:30:32 +02:00
if(!this.lazyLoadQueue.intersector.isVisible(div)) {
this.processInvisibleDiv(div);
return;
}
div.middlewareHelper.clean();
const middleware = div.middlewareHelper.get().create().get();
2023-01-06 20:27:29 +01:00
const res = await wrapVideo({
doc,
container: div as HTMLDivElement,
lazyLoadQueue: null,
// lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue,
group: this.group,
noInfo: true,
2024-05-04 15:30:32 +02:00
noPreview: true,
middleware
2023-01-06 20:27:29 +01:00
});
const promise = res.loadPromise;
promise.finally(() => {
2024-05-04 15:30:32 +02:00
middleware.onDestroy(() => {
res.video?.remove();
});
2023-01-06 20:27:29 +01:00
2024-05-04 15:30:32 +02:00
if(!middleware() || !this.lazyLoadQueue.intersector.isVisible(div)) {
2023-01-06 20:27:29 +01:00
this.processInvisibleDiv(div);
2024-05-04 15:30:32 +02:00
return;
2023-01-06 20:27:29 +01:00
}
2024-05-04 15:30:32 +02:00
const thumb = div.querySelector('img, canvas');
thumb && thumb.classList.add('hide');
2023-01-06 20:27:29 +01:00
});
return promise;
});
return promise;
};
// return load();
this.lazyLoadQueue.push({div, load});
}
public processInvisibleDiv = (div: HTMLElement) => {
return this.scrollPromise.then(async() => {
// return;
if(this.lazyLoadQueue.intersector.isVisible(div)) {
return;
}
const thumb = div.querySelector('img, canvas');
if(thumb) {
thumb.classList.remove('hide');
await doubleRaf();
}
if(this.lazyLoadQueue.intersector.isVisible(div)) {
return;
}
2024-05-04 15:30:32 +02:00
div.middlewareHelper.clean();
2023-01-06 20:27:29 +01:00
});
};
2024-05-04 15:30:32 +02:00
public addBatch(docs: MyDocument[]) {
docs.forEach((doc) => this.add(doc));
}
public update(docs: MyDocument[]) {
for(const [docId] of this.map) {
if(!docs.some((doc) => doc.id === docId)) {
this.delete(docId);
}
}
this.addBatch(docs);
for(let i = 0, length = docs.length; i < length; ++i) {
const element = this.map.get(docs[i].id);
positionElementByIndex(element, this.element, i);
}
}
2023-01-06 20:27:29 +01:00
public add(doc: MyDocument, appendTo = this.element) {
2024-05-04 15:30:32 +02:00
if(this.map.has(doc.id)) {
return;
}
2023-01-06 20:27:29 +01:00
const div = document.createElement('div');
div.classList.add('gif', 'grid-item'/* , 'fade-in-transition' */);
// div.style.opacity = '0';
div.dataset.docId = '' + doc.id;
2024-05-04 15:30:32 +02:00
div.middlewareHelper = this.middlewareHelper.get().create();
this.map.set(doc.id, div);
2023-01-06 20:27:29 +01:00
appendTo.append(div);
this.lazyLoadQueue.observe({div, load: noop as any});
// let preloader = new ProgressivePreloader(div);
wrapVideo({
doc,
container: div as HTMLDivElement,
lazyLoadQueue: null,
noInfo: true,
2023-11-28 12:44:50 +01:00
onlyPreview: true,
2024-05-04 15:30:32 +02:00
middleware: div.middlewareHelper.get()
2023-01-06 20:27:29 +01:00
});
}
2024-05-04 15:30:32 +02:00
public delete(docId: DocId) {
const div = this.map.get(docId);
if(div) {
div.remove();
div.middlewareHelper.destroy();
this.map.delete(docId);
}
}
2023-01-06 20:27:29 +01:00
}