/* * https://github.com/morethanwords/tweb * Copyright (C) 2019-2021 Eduard Kuzmenko * https://github.com/morethanwords/tweb/blob/master/LICENSE */ import {CancellablePromise} from '../helpers/cancellablePromise'; import SetTransition from './singleTransition'; import {fastRaf} from '../helpers/schedulers'; import cancelEvent from '../helpers/dom/cancelEvent'; import {attachClickEvent} from '../helpers/dom/clickEvent'; import isInDOM from '../helpers/dom/isInDOM'; import safeAssign from '../helpers/object/safeAssign'; const TRANSITION_TIME = 200; export default class ProgressivePreloader { public preloader: HTMLDivElement; public circle: SVGCircleElement; private cancelSvg: SVGSVGElement; private downloadSvg: HTMLElement; private tempId = 0; public detached = true; public promise: CancellablePromise = null; public isUpload = false; private cancelable = true; private streamable = false; private rtmp = false; private tryAgainOnFail = true; private attachMethod: 'append' | 'prepend' = 'append'; public loadFunc: (e?: Event) => any; public totalLength: number; constructor(options?: Partial<{ isUpload: ProgressivePreloader['isUpload'], cancelable: ProgressivePreloader['cancelable'], streamable: ProgressivePreloader['streamable'], rtmp: ProgressivePreloader['rtmp'], tryAgainOnFail: ProgressivePreloader['tryAgainOnFail'], attachMethod: ProgressivePreloader['attachMethod'] }>) { if(options) { safeAssign(this, options); } if(this.isUpload) { this.tryAgainOnFail = false; } } public constructContainer(options: Partial<{ color: 'transparent', bold: boolean }> = {}) { if(!this.preloader) { this.preloader = document.createElement('div'); if(this.rtmp) { this.preloader.classList.add('preloader-container-rtmp'); } else { this.preloader.classList.add('preloader-container'); } if(options.color) { this.preloader.classList.add('preloader-' + options.color); } if(options.bold) { this.preloader.classList.add('preloader-bold'); } if(this.streamable) { this.preloader.classList.add('preloader-streamable'); } } } public constructDownloadIcon() { this.constructContainer(); } public construct() { this.construct = null; this.constructContainer(); if(this.rtmp) { this.preloader.innerHTML = `
`; } else { this.preloader.innerHTML = `
`; } if(this.streamable) { this.totalLength = 118.61124420166016; } else { this.totalLength = 149.82473754882812; } if(this.cancelable) { this.preloader.innerHTML += ` `; this.downloadSvg = this.preloader.lastElementChild as HTMLElement; this.cancelSvg = this.downloadSvg.previousElementSibling as any; } else { this.preloader.classList.add('preloader-swing'); } this.circle = this.preloader.firstElementChild.firstElementChild.firstElementChild as SVGCircleElement; if(this.cancelable) { attachClickEvent(this.preloader, this.onClick); } } public onClick = (e?: Event) => { if(e) { cancelEvent(e); } if(this.preloader.classList.contains('manual')) { this.loadFunc?.(e); } else { this.promise?.cancel?.(); } }; public setDownloadFunction(func: ProgressivePreloader['loadFunc']) { this.loadFunc = func; } public setManual() { this.preloader.classList.add('manual'); this.setProgress(0); } public attachPromise(promise: CancellablePromise) { if(this.isUpload && this.promise) return; this.promise = promise; const tempId = --this.tempId; const startTime = Date.now(); const onEnd = (err: Error) => { promise.notify = promise.notifyAll = null; if(tempId !== this.tempId) { return; } const elapsedTime = Date.now() - startTime; // console.log('[PP]: end', this.detached, performance.now()); if(!err && this.cancelable) { this.setProgress(100); const delay = TRANSITION_TIME * 0.75; if(elapsedTime < delay) { this.detach(); } else { setTimeout(() => { // * wait for transition complete if(tempId === this.tempId) { this.detach(); } }, delay); } } else { if(this.tryAgainOnFail) { this.attach(this.preloader.parentElement); fastRaf(() => { this.setManual(); }); } else { this.detach(); } } this.promise = promise = null; }; promise .then(() => onEnd(null)) .catch((err) => onEnd(err)); promise.addNotifyListener?.((details: {done: number, total: number}) => { /* if(details.done >= details.total) { onEnd(); } */ if(tempId !== this.tempId) return; // console.log('preloader download', promise, details); const percents = details.done / details.total * 100; this.setProgress(percents); }); } public attach(elem: Element, reset = false, promise?: CancellablePromise) { if(!this.detached && (!this.preloader || !this.preloader.classList.contains('manual'))) { return; } this.construct?.(); if(this.preloader.parentElement) { this.preloader.classList.remove('manual'); } this.detached = false; if(promise/* && false */) { this.attachPromise(promise); } let useRafs = 0; if(this.detached || this.preloader.parentElement !== elem) { useRafs = isInDOM(this.preloader) ? 1 : 2; if(this.preloader.parentElement !== elem) { elem[this.attachMethod](this.preloader); } } SetTransition({ element: this.preloader, className: 'is-visible', forwards: true, duration: TRANSITION_TIME, useRafs }); if(this.cancelable && reset) { this.setProgress(0); } } public detach() { if(this.detached) { return; } // return; this.detached = true; // return; if(this.preloader?.parentElement) { /* setTimeout(() => */// fastRaf(() => { /* if(!this.detached) return; this.detached = true; */ // fastRaf(() => { // console.log('[PP]: detach after rAF', this.detached, performance.now()); // if(!this.detached || !this.preloader.parentElement) { // return; // } SetTransition({ element: this.preloader, className: 'is-visible', forwards: false, duration: TRANSITION_TIME, onTransitionEnd: () => { this.preloader.remove(); }, useRafs: 1 }); // }); // })/* , 5e3) */; } } public setProgress(percents: number) { if(!this.totalLength && !isInDOM(this.circle)) { return; } if(percents === 0) { this.circle.style.strokeDasharray = ''; return; } try { this.totalLength ||= this.circle.getTotalLength(); // console.log('setProgress', (percents / 100 * totalLength)); this.circle.style.strokeDasharray = '' + Math.max(5, percents / 100 * this.totalLength) + ', ' + this.totalLength; } catch(err) {} } }