Avatar & images fallback to blob due to speed
This commit is contained in:
parent
82043e4c4a
commit
17410a7a59
@ -61,7 +61,7 @@ class AppAudio {
|
||||
|
||||
audio.addEventListener('error', onError);
|
||||
|
||||
const downloadPromise: Promise<any> = !doc.supportsStreaming ? appDocsManager.downloadDocNew(doc.id).promise : Promise.resolve();
|
||||
const downloadPromise: Promise<any> = !doc.supportsStreaming ? appDocsManager.downloadDocNew(doc.id) : Promise.resolve();
|
||||
|
||||
downloadPromise.then(() => {
|
||||
this.container.append(audio);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import appDocsManager from "../lib/appManagers/appDocsManager";
|
||||
import { RichTextProcessor } from "../lib/richtextprocessor";
|
||||
import { formatDate } from "./wrappers";
|
||||
import ProgressivePreloader from "./preloader_new";
|
||||
import ProgressivePreloader from "./preloader";
|
||||
import { MediaProgressLine } from "../lib/mediaPlayer";
|
||||
import appAudio from "./appAudio";
|
||||
import { MTDocument } from "../types";
|
||||
@ -367,9 +367,9 @@ export default class AudioElement extends HTMLElement {
|
||||
}
|
||||
|
||||
download = appDocsManager.downloadDocNew(doc.id);
|
||||
preloader.attach(downloadDiv, true, appDocsManager.getInputFileName(doc));
|
||||
preloader.attach(downloadDiv, true, download);
|
||||
|
||||
download.promise.then(() => {
|
||||
download.then(() => {
|
||||
downloadDiv.remove();
|
||||
this.removeEventListener('click', onClick);
|
||||
onLoad();
|
||||
@ -383,7 +383,7 @@ export default class AudioElement extends HTMLElement {
|
||||
|
||||
downloadDiv.classList.add('downloading');
|
||||
} else {
|
||||
download.controller.abort();
|
||||
download.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -38,8 +38,13 @@ export default class AvatarElement extends HTMLElement {
|
||||
}
|
||||
|
||||
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
|
||||
//console.log('avatar changed attribute:', name, oldValue, newValue);
|
||||
// вызывается при изменении одного из перечисленных выше атрибутов
|
||||
if(name == 'peer') {
|
||||
if(this.peerID == +newValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.peerID = +newValue;
|
||||
this.update();
|
||||
} else if(name == 'peer-title') {
|
||||
|
@ -11,7 +11,7 @@ import apiManager from '../lib/mtproto/mtprotoworker';
|
||||
import LazyLoadQueue from "./lazyLoadQueue";
|
||||
import { wrapSticker, wrapVideo } from "./wrappers";
|
||||
import appDocsManager from "../lib/appManagers/appDocsManager";
|
||||
import ProgressivePreloader from "./preloader_new";
|
||||
import ProgressivePreloader from "./preloader";
|
||||
import Config, { touchSupport } from "../lib/config";
|
||||
import { MTDocument } from "../types";
|
||||
import animationIntersector from "./animationIntersector";
|
||||
@ -377,9 +377,9 @@ class StickersTab implements EmoticonsTab {
|
||||
});
|
||||
} else {
|
||||
const image = new Image();
|
||||
renderImageFromUrl(image, thumbURL).then(() => {
|
||||
renderImageFromUrl(image, thumbURL, () => {
|
||||
li.append(image);
|
||||
})
|
||||
});
|
||||
}
|
||||
} else { // as thumb will be used first sticker
|
||||
wrapSticker({
|
||||
@ -636,7 +636,7 @@ class GifsTab implements EmoticonsTab {
|
||||
}, {once: true});
|
||||
};
|
||||
|
||||
((posterURL ? renderImageFromUrl(img, posterURL) : Promise.resolve()) as Promise<any>).then(() => {
|
||||
const afterRender = () => {
|
||||
if(img) {
|
||||
div.append(img);
|
||||
div.style.opacity = '';
|
||||
@ -658,7 +658,9 @@ class GifsTab implements EmoticonsTab {
|
||||
div.append(img);
|
||||
div.addEventListener('mouseover', onMouseOver, {once: true});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
(posterURL ? renderImageFromUrl(img, posterURL, afterRender) : afterRender());
|
||||
|
||||
/* wrapVideo({
|
||||
doc,
|
||||
@ -742,7 +744,7 @@ class EmoticonsDropdown {
|
||||
if(firstTime) {
|
||||
this.toggleEl.onmouseout = this.element.onmouseout = (e) => {
|
||||
const toElement = (e as any).toElement as Element;
|
||||
if(findUpClassName(toElement, 'emoji-dropdown')) {
|
||||
if(toElement && findUpClassName(toElement, 'emoji-dropdown')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,36 +1,40 @@
|
||||
import Config, { touchSupport, isApple, mediaSizes } from "../lib/config";
|
||||
|
||||
let loadedURLs: {[url: string]: boolean} = {};
|
||||
let set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string) => {
|
||||
export const loadedURLs: {[url: string]: boolean} = {};
|
||||
const set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string) => {
|
||||
if(elem instanceof HTMLImageElement || elem instanceof HTMLVideoElement) elem.src = url;
|
||||
else if(elem instanceof SVGImageElement) elem.setAttributeNS(null, 'href', url);
|
||||
else elem.style.backgroundImage = 'url(' + url + ')';
|
||||
};
|
||||
|
||||
export async function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string): Promise<boolean> {
|
||||
if(loadedURLs[url]) {
|
||||
// проблема функции в том, что она не подходит для ссылок, пригодна только для blob'ов, потому что обычным ссылкам нужен 'load' каждый раз.
|
||||
export function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string, callback?: (err?: Event) => void): boolean {
|
||||
if((loadedURLs[url]/* && false */) || elem instanceof HTMLVideoElement) {
|
||||
set(elem, url);
|
||||
callback && callback();
|
||||
return true;
|
||||
} else {
|
||||
if(elem instanceof HTMLVideoElement) {
|
||||
set(elem, url);
|
||||
} else {
|
||||
await new Promise((resolve, reject) => {
|
||||
let loader = new Image();
|
||||
loader.src = url;
|
||||
//let perf = performance.now();
|
||||
loader.addEventListener('load', () => {
|
||||
set(elem, url);
|
||||
loadedURLs[url] = true;
|
||||
//console.log('onload:', url, performance.now() - perf);
|
||||
resolve(false);
|
||||
});
|
||||
loader.addEventListener('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
const isImage = elem instanceof HTMLImageElement;
|
||||
const loader = isImage ? elem as HTMLImageElement : new Image();
|
||||
//const loader = new Image();
|
||||
loader.src = url;
|
||||
//let perf = performance.now();
|
||||
loader.addEventListener('load', () => {
|
||||
if(!isImage) {
|
||||
set(elem, url);
|
||||
}
|
||||
|
||||
return !!loadedURLs[url];
|
||||
loadedURLs[url] = true;
|
||||
//console.log('onload:', url, performance.now() - perf);
|
||||
callback && callback();
|
||||
});
|
||||
|
||||
if(callback) {
|
||||
loader.addEventListener('error', callback);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function putPreloader(elem: Element, returnDiv = false) {
|
||||
|
@ -1,13 +1,15 @@
|
||||
import { isInDOM } from "../lib/utils";
|
||||
import { isInDOM, cancelEvent } from "../lib/utils";
|
||||
import { CancellablePromise } from "../lib/polyfill";
|
||||
|
||||
export default class ProgressivePreloader {
|
||||
public preloader: HTMLDivElement = null;
|
||||
private circle: SVGCircleElement = null;
|
||||
private promise: CancellablePromise<any> = null;
|
||||
public preloader: HTMLDivElement;
|
||||
private circle: SVGCircleElement;
|
||||
|
||||
private tempID = 0;
|
||||
private detached = true;
|
||||
|
||||
private promise: CancellablePromise<any> = null;
|
||||
|
||||
constructor(elem?: Element, private cancelable = true) {
|
||||
this.preloader = document.createElement('div');
|
||||
this.preloader.classList.add('preloader-container');
|
||||
@ -36,7 +38,9 @@ export default class ProgressivePreloader {
|
||||
}
|
||||
|
||||
if(this.cancelable) {
|
||||
this.preloader.addEventListener('click', () => {
|
||||
this.preloader.addEventListener('click', (e) => {
|
||||
cancelEvent(e);
|
||||
|
||||
if(this.promise && this.promise.cancel) {
|
||||
this.promise.cancel();
|
||||
this.detach();
|
||||
@ -44,13 +48,14 @@ export default class ProgressivePreloader {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public attach(elem: Element, reset = true, promise?: CancellablePromise<any>, append = true) {
|
||||
if(promise) {
|
||||
this.promise = promise;
|
||||
|
||||
let tempID = --this.tempID;
|
||||
let onEnd = () => {
|
||||
const tempID = --this.tempID;
|
||||
|
||||
const onEnd = () => {
|
||||
promise.notify = null;
|
||||
|
||||
if(tempID == this.tempID) {
|
||||
@ -58,37 +63,41 @@ export default class ProgressivePreloader {
|
||||
this.promise = promise = null;
|
||||
}
|
||||
};
|
||||
|
||||
//promise.catch(onEnd);
|
||||
promise.finally(onEnd);
|
||||
|
||||
promise.notify = (details: {done: number, total: number}) => {
|
||||
/* if(details.done >= details.total) {
|
||||
onEnd();
|
||||
} */
|
||||
|
||||
if(tempID != this.tempID) return;
|
||||
|
||||
//console.log('preloader download', promise, details);
|
||||
let percents = details.done / details.total * 100;
|
||||
this.setProgress(percents);
|
||||
};
|
||||
if(promise.addNotifyListener) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(this.cancelable && reset) {
|
||||
this.setProgress(0);
|
||||
}
|
||||
|
||||
this.detached = false;
|
||||
window.requestAnimationFrame(() => {
|
||||
if(this.detached) return;
|
||||
this.detached = false;
|
||||
|
||||
|
||||
elem[append ? 'append' : 'prepend'](this.preloader);
|
||||
|
||||
if(this.cancelable && reset) {
|
||||
this.setProgress(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public detach() {
|
||||
this.detached = true;
|
||||
|
||||
if(this.preloader.parentElement) {
|
||||
window.requestAnimationFrame(() => {
|
||||
if(!this.detached) return;
|
||||
@ -112,7 +121,7 @@ export default class ProgressivePreloader {
|
||||
}
|
||||
|
||||
try {
|
||||
let totalLength = this.circle.getTotalLength();
|
||||
const totalLength = this.circle.getTotalLength();
|
||||
//console.log('setProgress', (percents / 100 * totalLength));
|
||||
this.circle.style.strokeDasharray = '' + Math.max(5, percents / 100 * totalLength) + ', 200';
|
||||
} catch(err) {}
|
||||
|
@ -1,123 +0,0 @@
|
||||
import { isInDOM, $rootScope, cancelEvent } from "../lib/utils";
|
||||
import appDownloadManager, { Progress } from "../lib/appManagers/appDownloadManager";
|
||||
|
||||
export default class ProgressivePreloader {
|
||||
public preloader: HTMLDivElement;
|
||||
private circle: SVGCircleElement;
|
||||
|
||||
//private tempID = 0;
|
||||
private detached = true;
|
||||
|
||||
private fileName: string;
|
||||
public controller: AbortController;
|
||||
|
||||
constructor(elem?: Element, private cancelable = true) {
|
||||
this.preloader = document.createElement('div');
|
||||
this.preloader.classList.add('preloader-container');
|
||||
|
||||
this.preloader.innerHTML = `
|
||||
<div class="you-spin-me-round">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="preloader-circular" viewBox="25 25 50 50">
|
||||
<circle class="preloader-path-new" cx="50" cy="50" r="23" fill="none" stroke-miterlimit="10"/>
|
||||
</svg>
|
||||
</div>`;
|
||||
|
||||
if(cancelable) {
|
||||
this.preloader.innerHTML += `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="preloader-close" viewBox="0 0 20 20">
|
||||
<line x1="0" y1="20" x2="20" y2="0" stroke-width="2" stroke-linecap="round"></line>
|
||||
<line x1="0" y1="0" x2="20" y2="20" stroke-width="2" stroke-linecap="round"></line>
|
||||
</svg>`;
|
||||
} else {
|
||||
this.preloader.classList.add('preloader-swing');
|
||||
}
|
||||
|
||||
this.circle = this.preloader.firstElementChild.firstElementChild.firstElementChild as SVGCircleElement;
|
||||
|
||||
if(elem) {
|
||||
this.attach(elem);
|
||||
}
|
||||
|
||||
if(this.cancelable) {
|
||||
this.preloader.addEventListener('click', (e) => {
|
||||
cancelEvent(e);
|
||||
this.detach();
|
||||
|
||||
if(!this.fileName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const download = appDownloadManager.getDownload(this.fileName);
|
||||
if(download && download.controller && !download.controller.signal.aborted) {
|
||||
download.controller.abort();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
downloadProgressHandler = (details: Progress) => {
|
||||
if(details.done >= details.total) {
|
||||
this.detach();
|
||||
}
|
||||
|
||||
//console.log('preloader download', promise, details);
|
||||
let percents = details.done / details.total * 100;
|
||||
this.setProgress(percents);
|
||||
};
|
||||
|
||||
public attach(elem: Element, reset = true, fileName?: string, append = true) {
|
||||
this.fileName = fileName;
|
||||
if(this.fileName) {
|
||||
const download = appDownloadManager.getDownload(fileName);
|
||||
download.promise.catch(() => {
|
||||
this.detach();
|
||||
});
|
||||
|
||||
appDownloadManager.addProgressCallback(this.fileName, this.downloadProgressHandler);
|
||||
}
|
||||
|
||||
this.detached = false;
|
||||
window.requestAnimationFrame(() => {
|
||||
if(this.detached) return;
|
||||
this.detached = false;
|
||||
|
||||
elem[append ? 'append' : 'prepend'](this.preloader);
|
||||
|
||||
if(this.cancelable && reset) {
|
||||
this.setProgress(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public detach() {
|
||||
this.detached = true;
|
||||
|
||||
if(this.preloader.parentElement) {
|
||||
window.requestAnimationFrame(() => {
|
||||
if(!this.detached) return;
|
||||
this.detached = true;
|
||||
|
||||
if(this.preloader.parentElement) {
|
||||
this.preloader.parentElement.removeChild(this.preloader);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public setProgress(percents: number) {
|
||||
if(!isInDOM(this.circle)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(percents == 0) {
|
||||
this.circle.style.strokeDasharray = '';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let totalLength = this.circle.getTotalLength();
|
||||
//console.log('setProgress', (percents / 100 * totalLength));
|
||||
this.circle.style.strokeDasharray = '' + Math.max(5, percents / 100 * totalLength) + ', 200';
|
||||
} catch(err) {}
|
||||
}
|
||||
}
|
@ -3,11 +3,10 @@ import LottieLoader from '../lib/lottieLoader';
|
||||
import appDocsManager from "../lib/appManagers/appDocsManager";
|
||||
import { formatBytes, getEmojiToneIndex } from "../lib/utils";
|
||||
import ProgressivePreloader from './preloader';
|
||||
import ProgressivePreloaderNew from './preloader_new';
|
||||
import LazyLoadQueue from './lazyLoadQueue';
|
||||
import VideoPlayer from '../lib/mediaPlayer';
|
||||
import { RichTextProcessor } from '../lib/richtextprocessor';
|
||||
import { renderImageFromUrl } from './misc';
|
||||
import { renderImageFromUrl, loadedURLs } from './misc';
|
||||
import appMessagesManager from '../lib/appManagers/appMessagesManager';
|
||||
import { Layouter, RectPart } from './groupedLayout';
|
||||
import PollElement from './poll';
|
||||
@ -15,7 +14,7 @@ import { mediaSizes, isSafari } from '../lib/config';
|
||||
import { MTDocument, MTPhotoSize } from '../types';
|
||||
import animationIntersector from './animationIntersector';
|
||||
import AudioElement from './audio';
|
||||
import { Download } from '../lib/appManagers/appDownloadManager';
|
||||
import appDownloadManager, { Download } from '../lib/appManagers/appDownloadManager';
|
||||
import { webpWorkerController } from '../lib/webp/webpWorkerController';
|
||||
|
||||
export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue, noInfo, group}: {
|
||||
@ -228,7 +227,7 @@ export function wrapDocument(doc: MTDocument, withTime = false, uploading = fals
|
||||
|
||||
if(!uploading) {
|
||||
let downloadDiv = docDiv.querySelector('.document-download') as HTMLDivElement;
|
||||
let preloader: ProgressivePreloaderNew;
|
||||
let preloader: ProgressivePreloader;
|
||||
let download: Download;
|
||||
|
||||
docDiv.addEventListener('click', () => {
|
||||
@ -238,13 +237,13 @@ export function wrapDocument(doc: MTDocument, withTime = false, uploading = fals
|
||||
}
|
||||
|
||||
if(!preloader) {
|
||||
preloader = new ProgressivePreloaderNew(null, true);
|
||||
preloader = new ProgressivePreloader(null, true);
|
||||
}
|
||||
|
||||
download = appDocsManager.saveDocFile(doc);
|
||||
preloader.attach(downloadDiv, true, appDocsManager.getInputFileName(doc));
|
||||
preloader.attach(downloadDiv, true, download);
|
||||
|
||||
download.promise.then(() => {
|
||||
download.then(() => {
|
||||
downloadDiv.remove();
|
||||
}).catch(err => {
|
||||
if(err.name === 'AbortError') {
|
||||
@ -256,7 +255,7 @@ export function wrapDocument(doc: MTDocument, withTime = false, uploading = fals
|
||||
|
||||
downloadDiv.classList.add('downloading');
|
||||
} else {
|
||||
download.controller.abort();
|
||||
download.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -328,7 +327,7 @@ function wrapMediaWithTail(photo: any, message: {mid: number, message: string},
|
||||
}
|
||||
|
||||
export function wrapPhoto(photoID: any, message: any, container: HTMLDivElement, boxWidth = mediaSizes.active.regular.width, boxHeight = mediaSizes.active.regular.height, withTail = true, isOut = false, lazyLoadQueue: LazyLoadQueue, middleware: () => boolean, size: MTPhotoSize = null) {
|
||||
let photo = appPhotosManager.getPhoto(photoID);
|
||||
const photo = appPhotosManager.getPhoto(photoID);
|
||||
|
||||
let image: HTMLImageElement;
|
||||
if(withTail) {
|
||||
@ -351,36 +350,39 @@ export function wrapPhoto(photoID: any, message: any, container: HTMLDivElement,
|
||||
|
||||
//console.log('wrapPhoto downloaded:', photo, photo.downloaded, container);
|
||||
|
||||
// так нельзя делать, потому что может быть загружен неправильный размер картинки
|
||||
/* if(photo.downloaded && photo.url) {
|
||||
renderImageFromUrl(image, photo.url);
|
||||
return;
|
||||
} */
|
||||
const cacheContext = appPhotosManager.getCacheContext(photo);
|
||||
|
||||
let preloader: ProgressivePreloader;
|
||||
if(message.media.preloader) { // means upload
|
||||
message.media.preloader.attach(container);
|
||||
} else if(!photo.downloaded) {
|
||||
} else if(!cacheContext.downloaded) {
|
||||
preloader = new ProgressivePreloader(container, false);
|
||||
}
|
||||
|
||||
let load = () => {
|
||||
let promise = appPhotosManager.preloadPhoto(photoID, size);
|
||||
|
||||
const load = () => {
|
||||
const promise = appPhotosManager.preloadPhoto(photoID, size);
|
||||
|
||||
if(preloader) {
|
||||
preloader.attach(container, true, promise);
|
||||
}
|
||||
|
||||
/* const url = appPhotosManager.getPhotoURL(photoID, size);
|
||||
return renderImageFromUrl(image || container, url).then(() => {
|
||||
photo.downloaded = true;
|
||||
}); */
|
||||
|
||||
/* if(preloader) {
|
||||
preloader.attach(container, true, promise);
|
||||
} */
|
||||
|
||||
return promise.then(() => {
|
||||
if(middleware && !middleware()) return;
|
||||
|
||||
renderImageFromUrl(image || container, photo._ == 'photo' ? photo.url : appPhotosManager.getDocumentCachedThumb(photo.id).url);
|
||||
renderImageFromUrl(image || container, cacheContext.url);
|
||||
});
|
||||
};
|
||||
|
||||
/////////console.log('wrapPhoto', load, container, image);
|
||||
|
||||
return photo.downloaded ? load() : lazyLoadQueue.push({div: container, load: load, wasSeen: true});
|
||||
|
||||
return cacheContext.downloaded ? load() : lazyLoadQueue.push({div: container, load: load, wasSeen: true});
|
||||
}
|
||||
|
||||
export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, onlyThumb, emoji, width, height, withThumb, loop}: {
|
||||
@ -439,7 +441,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
img = new Image();
|
||||
|
||||
if((!isSafari || doc.stickerThumbConverted)/* && false */) {
|
||||
renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true)).then(afterRender);
|
||||
renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true), afterRender);
|
||||
} else {
|
||||
webpWorkerController.convert(doc.id, thumb.bytes).then(bytes => {
|
||||
if(middleware && !middleware()) return;
|
||||
@ -448,7 +450,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
doc.stickerThumbConverted = true;
|
||||
|
||||
if(!div.childElementCount) {
|
||||
renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true)).then(afterRender);
|
||||
renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true), afterRender);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -461,11 +463,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
|
||||
const load = () => {
|
||||
if(div.childElementCount || (middleware && !middleware())) return;
|
||||
const promise = renderImageFromUrl(img, appDocsManager.getFileURL(doc, false, thumb));
|
||||
|
||||
//if(!downloaded) {
|
||||
promise.then(afterRender);
|
||||
//}
|
||||
renderImageFromUrl(img, appDocsManager.getFileURL(doc, false, thumb), afterRender);
|
||||
};
|
||||
|
||||
/* let downloaded = appDocsManager.hasDownloadedThumb(doc.id, thumb.type);
|
||||
@ -483,10 +481,12 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
|
||||
let load = () => {
|
||||
let img = new Image();
|
||||
return renderImageFromUrl(img, appDocsManager.getFileURL(doc, false, thumb)).then(() => {
|
||||
renderImageFromUrl(img, appDocsManager.getFileURL(doc, false, thumb), () => {
|
||||
if(middleware && !middleware()) return;
|
||||
div.append(img);
|
||||
});
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return lazyLoadQueue ? (lazyLoadQueue.push({div, load}), Promise.resolve()) : load();
|
||||
@ -504,11 +504,14 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
//console.time('download sticker' + doc.id);
|
||||
|
||||
//appDocsManager.downloadDocNew(doc.id).promise.then(res => res.json()).then(async(json) => {
|
||||
fetch(doc.url).then(res => res.json()).then(async(json) => {
|
||||
//fetch(doc.url).then(res => res.json()).then(async(json) => {
|
||||
appDownloadManager.download(doc.url, appDocsManager.getInputFileName(doc), 'json').then(async(json) => {
|
||||
//console.timeEnd('download sticker' + doc.id);
|
||||
//console.log('loaded sticker:', doc, div);
|
||||
if(middleware && !middleware()) return;
|
||||
|
||||
//await new Promise((resolve) => setTimeout(resolve, 5e3));
|
||||
|
||||
let animation = await LottieLoader.loadAnimationWorker/* loadAnimation */({
|
||||
container: div,
|
||||
loop: loop && !emoji,
|
||||
@ -554,7 +557,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
});
|
||||
}
|
||||
|
||||
renderImageFromUrl(img, doc.url).then(() => {
|
||||
renderImageFromUrl(img, doc.url, () => {
|
||||
if(div.firstElementChild && div.firstElementChild != img) {
|
||||
div.firstElementChild.remove();
|
||||
}
|
||||
|
@ -12,4 +12,4 @@ export const isAndroid = navigator.userAgent.toLowerCase().indexOf('android') !=
|
||||
*/
|
||||
const ctx = typeof(window) !== 'undefined' ? window : self;
|
||||
|
||||
export const isSafari = !!('safari' in ctx) || !!(userAgent && (/\b(iPad|iPhone|iPod)\b/.test(userAgent) || (!!userAgent.match('Safari') && !userAgent.match('Chrome'))));
|
||||
export const isSafari = !!('safari' in ctx) || !!(userAgent && (/\b(iPad|iPhone|iPod)\b/.test(userAgent) || (!!userAgent.match('Safari') && !userAgent.match('Chrome'))))/* || true */;
|
@ -14,16 +14,21 @@ class AppDocsManager {
|
||||
public saveDoc(apiDoc: MTDocument, context?: any) {
|
||||
//console.log('saveDoc', apiDoc, this.docs[apiDoc.id]);
|
||||
if(this.docs[apiDoc.id]) {
|
||||
let d = this.docs[apiDoc.id];
|
||||
const d = this.docs[apiDoc.id];
|
||||
|
||||
if(apiDoc.thumbs) {
|
||||
if(!d.thumbs) d.thumbs = apiDoc.thumbs;
|
||||
else if(apiDoc.thumbs[0].bytes && !d.thumbs[0].bytes) {
|
||||
/* else if(apiDoc.thumbs[0].bytes && !d.thumbs[0].bytes) {
|
||||
d.thumbs.unshift(apiDoc.thumbs[0]);
|
||||
}
|
||||
} else if(d.thumbs[0].url) { // fix for converted thumb in safari
|
||||
apiDoc.thumbs[0] = d.thumbs[0];
|
||||
} */
|
||||
}
|
||||
|
||||
return Object.assign(d, apiDoc, context);
|
||||
d.file_reference = apiDoc.file_reference;
|
||||
return d;
|
||||
|
||||
//return Object.assign(d, apiDoc, context);
|
||||
//return context ? Object.assign(d, context) : d;
|
||||
}
|
||||
|
||||
@ -33,10 +38,6 @@ class AppDocsManager {
|
||||
|
||||
this.docs[apiDoc.id] = apiDoc;
|
||||
|
||||
if(apiDoc.thumb && apiDoc.thumb._ == 'photoSizeEmpty') {
|
||||
delete apiDoc.thumb;
|
||||
}
|
||||
|
||||
apiDoc.attributes.forEach((attribute: any) => {
|
||||
switch(attribute._) {
|
||||
case 'documentAttributeFilename':
|
||||
@ -216,7 +217,7 @@ class AppDocsManager {
|
||||
|
||||
const thumb = doc.thumbs.find(t => !t.bytes);
|
||||
if(thumb) {
|
||||
const url = appDocsManager.getFileURL(doc, false, thumb);
|
||||
const url = this.getFileURL(doc, false, thumb);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
@ -304,15 +305,15 @@ class AppDocsManager {
|
||||
return download;
|
||||
}
|
||||
|
||||
download = appDownloadManager.download(fileName, doc.url/* , method */);
|
||||
download = appDownloadManager.download(doc.url, fileName, /*method*/);
|
||||
|
||||
const originalPromise = download.promise;
|
||||
const originalPromise = download;
|
||||
originalPromise.then(() => {
|
||||
doc.downloaded = true;
|
||||
});
|
||||
|
||||
if(doc.type == 'voice' && !opusDecodeController.isPlaySupported()) {
|
||||
download.promise = originalPromise.then(async(blob) => {
|
||||
download = originalPromise.then(async(blob) => {
|
||||
let reader = new FileReader();
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
@ -344,7 +345,7 @@ class AppDocsManager {
|
||||
const url = this.getFileURL(doc, true);
|
||||
const fileName = this.getInputFileName(doc);
|
||||
|
||||
return appDownloadManager.downloadToDisc(fileName, url);
|
||||
return appDownloadManager.downloadToDisc(fileName, url, doc.file_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,16 @@
|
||||
import { $rootScope } from "../utils";
|
||||
import apiManager from "../mtproto/mtprotoworker";
|
||||
import { deferredPromise, CancellablePromise } from "../polyfill";
|
||||
|
||||
export type ResponseMethodBlob = 'blob';
|
||||
export type ResponseMethodJson = 'json';
|
||||
export type ResponseMethod = ResponseMethodBlob | ResponseMethodJson;
|
||||
|
||||
export type DownloadBlob = {promise: Promise<Blob>, controller: AbortController};
|
||||
export type DownloadJson = {promise: Promise<any>, controller: AbortController};
|
||||
/* export type DownloadBlob = {promise: Promise<Blob>, controller: AbortController};
|
||||
export type DownloadJson = {promise: Promise<any>, controller: AbortController}; */
|
||||
export type DownloadBlob = CancellablePromise<Blob>;
|
||||
export type DownloadJson = CancellablePromise<any>;
|
||||
//export type Download = DownloadBlob/* | DownloadJson */;
|
||||
export type Download = DownloadBlob/* | DownloadJson */;
|
||||
|
||||
export type Progress = {done: number, fileName: string, total: number, offset: number};
|
||||
@ -26,17 +30,25 @@ export class AppDownloadManager {
|
||||
if(callbacks) {
|
||||
callbacks.forEach(callback => callback(details));
|
||||
}
|
||||
|
||||
const download = this.downloads[details.fileName];
|
||||
if(download) {
|
||||
download.notifyAll(details);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public download(fileName: string, url: string, responseMethod?: ResponseMethodBlob): DownloadBlob;
|
||||
public download(fileName: string, url: string, responseMethod?: ResponseMethodJson): DownloadJson;
|
||||
public download(fileName: string, url: string, responseMethod: ResponseMethod = 'blob'): Download {
|
||||
public download(url: string, fileName: string, responseMethod?: ResponseMethodBlob): DownloadBlob;
|
||||
public download(url: string, fileName: string, responseMethod?: ResponseMethodJson): DownloadJson;
|
||||
public download(url: string, fileName: string, responseMethod: ResponseMethod = 'blob'): Download {
|
||||
if(this.downloads.hasOwnProperty(fileName)) return this.downloads[fileName];
|
||||
|
||||
const deferred = deferredPromise<Blob>();
|
||||
|
||||
const controller = new AbortController();
|
||||
const promise = fetch(url, {signal: controller.signal})
|
||||
.then(res => res[responseMethod]())
|
||||
.then(res => deferred.resolve(res))
|
||||
.catch(err => { // Только потому что event.request.signal не работает в SW, либо я кривой?
|
||||
if(err.name === 'AbortError') {
|
||||
//console.log('Fetch aborted');
|
||||
@ -48,6 +60,7 @@ export class AppDownloadManager {
|
||||
//console.error('Uh oh, an error!', err);
|
||||
}
|
||||
|
||||
deferred.reject(err);
|
||||
throw err;
|
||||
});
|
||||
|
||||
@ -57,7 +70,13 @@ export class AppDownloadManager {
|
||||
delete this.progressCallbacks[fileName];
|
||||
});
|
||||
|
||||
return this.downloads[fileName] = {promise, controller};
|
||||
deferred.cancel = () => {
|
||||
controller.abort();
|
||||
deferred.cancel = () => {};
|
||||
};
|
||||
|
||||
//return this.downloads[fileName] = {promise, controller};
|
||||
return this.downloads[fileName] = deferred;
|
||||
}
|
||||
|
||||
public getDownload(fileName: string) {
|
||||
@ -73,10 +92,12 @@ export class AppDownloadManager {
|
||||
}
|
||||
}
|
||||
|
||||
private createDownloadAnchor(url: string, onRemove?: () => void) {
|
||||
private createDownloadAnchor(url: string, fileName: string, onRemove?: () => void) {
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
|
||||
a.download = fileName;
|
||||
a.target = '_blank';
|
||||
|
||||
a.style.position = 'absolute';
|
||||
a.style.top = '1px';
|
||||
a.style.left = '1px';
|
||||
@ -108,11 +129,11 @@ export class AppDownloadManager {
|
||||
return this.download(fileName, url);
|
||||
} */
|
||||
|
||||
public downloadToDisc(fileName: string, url: string) {
|
||||
const download = this.download(fileName, url);
|
||||
download.promise.then(blob => {
|
||||
public downloadToDisc(fileName: string, url: string, discFileName: string) {
|
||||
const download = this.download(url, fileName);
|
||||
download/* .promise */.then(blob => {
|
||||
const objectURL = URL.createObjectURL(blob);
|
||||
this.createDownloadAnchor(objectURL, () => {
|
||||
this.createDownloadAnchor(objectURL, discFileName, () => {
|
||||
URL.revokeObjectURL(objectURL);
|
||||
});
|
||||
});
|
||||
|
@ -553,7 +553,7 @@ export class AppImManager {
|
||||
private closeBtn = this.topbar.querySelector('.sidebar-close-button') as HTMLButtonElement;
|
||||
|
||||
constructor() {
|
||||
this.log = logger('IM', /* LogLevels.log | LogLevels.warn | LogLevels.debug | */ LogLevels.error);
|
||||
this.log = logger('IM', LogLevels.log | LogLevels.warn | LogLevels.debug | LogLevels.error);
|
||||
this.chatInputC = new ChatInput();
|
||||
this.preloader = new ProgressivePreloader(null, false);
|
||||
this.selectTab(0);
|
||||
@ -1624,6 +1624,7 @@ export class AppImManager {
|
||||
this.peerChanged = true;
|
||||
|
||||
this.avatarEl.setAttribute('peer', '' + this.peerID);
|
||||
this.avatarEl.update();
|
||||
|
||||
const isAnyGroup = appPeersManager.isAnyGroup(peerID);
|
||||
const isChannel = appPeersManager.isChannel(peerID);
|
||||
@ -1822,8 +1823,7 @@ export class AppImManager {
|
||||
resolve();
|
||||
|
||||
// lol
|
||||
el.removeEventListener('canplay', onLoad);
|
||||
el.removeEventListener('load', onLoad);
|
||||
el.removeEventListener(el instanceof HTMLVideoElement ? 'canplay' : 'load', onLoad);
|
||||
};
|
||||
|
||||
if(el instanceof HTMLVideoElement) {
|
||||
@ -2286,20 +2286,26 @@ export class AppImManager {
|
||||
bubble.classList.add('hide-name', 'photo');
|
||||
const tailSupported = !isAndroid;
|
||||
if(tailSupported) bubble.classList.add('with-media-tail');
|
||||
|
||||
if(message.grouped_id) {
|
||||
bubble.classList.add('is-album');
|
||||
|
||||
wrapAlbum({
|
||||
groupID: message.grouped_id,
|
||||
attachmentDiv,
|
||||
middleware: this.getMiddleware(),
|
||||
isOut: our,
|
||||
lazyLoadQueue: this.lazyLoadQueue
|
||||
});
|
||||
} else {
|
||||
wrapPhoto(photo.id, message, attachmentDiv, undefined, undefined, tailSupported, isOut, this.lazyLoadQueue, this.getMiddleware());
|
||||
let storage = appMessagesManager.groupedMessagesStorage[message.grouped_id];
|
||||
if(Object.keys(storage).length != 1) {
|
||||
wrapAlbum({
|
||||
groupID: message.grouped_id,
|
||||
attachmentDiv,
|
||||
middleware: this.getMiddleware(),
|
||||
isOut: our,
|
||||
lazyLoadQueue: this.lazyLoadQueue
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
wrapPhoto(photo.id, message, attachmentDiv, undefined, undefined, tailSupported, isOut, this.lazyLoadQueue, this.getMiddleware());
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@ -2645,6 +2651,7 @@ export class AppImManager {
|
||||
}
|
||||
|
||||
avatarElem.setAttribute('peer', '' + ((message.fwd_from && this.peerID == this.myID ? message.fwdFromID : message.fromID) || 0));
|
||||
avatarElem.update();
|
||||
|
||||
//this.log('exec loadDialogPhoto', message);
|
||||
|
||||
|
@ -132,7 +132,7 @@ export class AppMediaViewer {
|
||||
const download = (e: MouseEvent) => {
|
||||
let message = appMessagesManager.getMessage(this.currentMessageID);
|
||||
if(message.media.photo) {
|
||||
appPhotosManager.downloadPhoto(message.media.photo.id);
|
||||
appPhotosManager.savePhotoFile(message.media.photo.id);
|
||||
} else {
|
||||
let document: any = null;
|
||||
|
||||
@ -895,7 +895,7 @@ export class AppMediaViewer {
|
||||
|
||||
//this.log('will renderImageFromUrl:', image, div, target);
|
||||
|
||||
renderImageFromUrl(image, url).then(() => {
|
||||
renderImageFromUrl(image, url, () => {
|
||||
div.append(image);
|
||||
});
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
import appUsersManager from "./appUsersManager";
|
||||
import { calcImageInBox, isObject, getFileURL } from "../utils";
|
||||
import { bytesFromHex, getFileNameByLocation } from "../bin_utils";
|
||||
//import apiManager from '../mtproto/apiManager';
|
||||
import apiManager from '../mtproto/mtprotoworker';
|
||||
import { MTPhotoSize, inputPhotoFileLocation, inputDocumentFileLocation, FileLocation } from "../../types";
|
||||
import appDownloadManager from "./appDownloadManager";
|
||||
import appDownloadManager, { Download } from "./appDownloadManager";
|
||||
import { deferredPromise, CancellablePromise } from "../polyfill";
|
||||
import { isSafari } from "../../helpers/userAgent";
|
||||
|
||||
export type MTPhoto = {
|
||||
_: 'photo' | 'photoEmpty' | string,
|
||||
@ -98,7 +97,7 @@ export class AppPhotosManager {
|
||||
return bestPhotoSize;
|
||||
}
|
||||
|
||||
public getUserPhotos(userID: number, maxID: number, limit: number) {
|
||||
/* public getUserPhotos(userID: number, maxID: number, limit: number) {
|
||||
var inputUser = appUsersManager.getUserInput(userID);
|
||||
return apiManager.invokeApi('photos.getUserPhotos', {
|
||||
user_id: inputUser,
|
||||
@ -120,7 +119,7 @@ export class AppPhotosManager {
|
||||
photos: photoIDs
|
||||
};
|
||||
});
|
||||
}
|
||||
} */
|
||||
|
||||
public getPreviewURLFromBytes(bytes: Uint8Array | number[], isSticker = false) {
|
||||
let arr: Uint8Array;
|
||||
@ -131,17 +130,15 @@ export class AppPhotosManager {
|
||||
} else {
|
||||
arr = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
||||
}
|
||||
|
||||
//console.log('getPreviewURLFromBytes', bytes, arr, div, isSticker);
|
||||
|
||||
/* let reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
let src = reader.result;
|
||||
};
|
||||
reader.readAsDataURL(blob); */
|
||||
|
||||
let blob = new Blob([arr], {type: "image/jpeg"});
|
||||
let mimeType: string;
|
||||
if(isSticker) {
|
||||
mimeType = isSafari ? 'image/png' : 'image/webp';
|
||||
} else {
|
||||
mimeType = 'image/jpeg';
|
||||
}
|
||||
|
||||
const blob = new Blob([arr], {type: mimeType});
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
@ -215,32 +212,18 @@ export class AppPhotosManager {
|
||||
|
||||
return photoSize;
|
||||
}
|
||||
|
||||
public preloadPhoto(photoID: any, photoSize?: MTPhotoSize): Promise<Blob | void> {
|
||||
const photo = this.getPhoto(photoID);
|
||||
|
||||
if(!photoSize) {
|
||||
const fullWidth = this.windowW;
|
||||
const fullHeight = this.windowH;
|
||||
|
||||
photoSize = this.choosePhotoSize(photo, fullWidth, fullHeight);
|
||||
}
|
||||
|
||||
public getPhotoURL(photo: MTPhoto, photoSize: MTPhotoSize) {
|
||||
const isDocument = photo._ == 'document';
|
||||
const cacheContext = isDocument ? (this.documentThumbsCache[photo.id] ?? (this.documentThumbsCache[photo.id] = {downloaded: -1, url: ''})) : photo;
|
||||
|
||||
if(cacheContext.downloaded >= photoSize.size && cacheContext.url) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if(!photoSize || photoSize._ == 'photoSizeEmpty') {
|
||||
//console.error('no photoSize by photo:', photo);
|
||||
return Promise.reject('no photoSize');
|
||||
throw new Error('photoSizeEmpty!');
|
||||
}
|
||||
|
||||
// maybe it's a thumb
|
||||
let isPhoto = photoSize.size && photo.access_hash && photo.file_reference;
|
||||
let location: inputPhotoFileLocation | inputDocumentFileLocation | FileLocation = isPhoto ? {
|
||||
const isPhoto = photoSize.size && photo.access_hash && photo.file_reference;
|
||||
const location: inputPhotoFileLocation | inputDocumentFileLocation | FileLocation = isPhoto ? {
|
||||
_: isDocument ? 'inputDocumentFileLocation' : 'inputPhotoFileLocation',
|
||||
id: photo.id,
|
||||
access_hash: photo.access_hash,
|
||||
@ -248,30 +231,54 @@ export class AppPhotosManager {
|
||||
thumb_size: photoSize.type
|
||||
} : photoSize.location;
|
||||
|
||||
const url = getFileURL('photo', {dcID: photo.dc_id, location, size: isPhoto ? photoSize.size : undefined});
|
||||
let promise: Promise<Blob>;
|
||||
if(isPhoto/* && photoSize.size >= 1e6 */) {
|
||||
promise = fetch(url).then(res => res.blob());
|
||||
} else {
|
||||
//console.log('Photos downloadSmallFile exec', photo, location);
|
||||
promise = fetch(url).then(res => res.blob());
|
||||
return {url: getFileURL('photo', {dcID: photo.dc_id, location, size: isPhoto ? photoSize.size : undefined}), location};
|
||||
}
|
||||
|
||||
public preloadPhoto(photoID: any, photoSize?: MTPhotoSize): CancellablePromise<Blob> {
|
||||
const photo = this.getPhoto(photoID);
|
||||
|
||||
if(!photoSize) {
|
||||
const fullWidth = this.windowW;
|
||||
const fullHeight = this.windowH;
|
||||
|
||||
photoSize = this.choosePhotoSize(photo, fullWidth, fullHeight);
|
||||
}
|
||||
|
||||
promise.then(blob => {
|
||||
const cacheContext = this.getCacheContext(photo);
|
||||
if(cacheContext.downloaded >= photoSize.size && cacheContext.url) {
|
||||
return Promise.resolve() as any;
|
||||
}
|
||||
|
||||
const {url, location} = this.getPhotoURL(photo, photoSize);
|
||||
const fileName = getFileNameByLocation(location);
|
||||
|
||||
let download = appDownloadManager.getDownload(fileName);
|
||||
if(download) {
|
||||
return download;
|
||||
}
|
||||
|
||||
download = appDownloadManager.download(url, fileName);
|
||||
download.then(blob => {
|
||||
if(!cacheContext.downloaded || cacheContext.downloaded < blob.size) {
|
||||
cacheContext.downloaded = blob.size;
|
||||
//cacheContext.url = URL.createObjectURL(blob);
|
||||
cacheContext.url = url;
|
||||
cacheContext.url = URL.createObjectURL(blob);
|
||||
|
||||
//console.log('wrote photo:', photo, photoSize, cacheContext, blob);
|
||||
}
|
||||
|
||||
return blob;
|
||||
});
|
||||
|
||||
return promise;
|
||||
return download;
|
||||
//return fetch(url).then(res => res.blob());
|
||||
}
|
||||
|
||||
public getCacheContext(photo: any) {
|
||||
return photo._ == 'document' ? this.getDocumentCachedThumb(photo.id) : photo;
|
||||
}
|
||||
|
||||
public getDocumentCachedThumb(docID: string) {
|
||||
return this.documentThumbsCache[docID];
|
||||
return this.documentThumbsCache[docID] ?? (this.documentThumbsCache[docID] = {downloaded: 0, url: ''});
|
||||
}
|
||||
|
||||
public getPhoto(photoID: any): MTPhoto {
|
||||
@ -293,7 +300,7 @@ export class AppPhotosManager {
|
||||
};
|
||||
}
|
||||
|
||||
public downloadPhoto(photoID: string) {
|
||||
public savePhotoFile(photoID: string) {
|
||||
const photo = this.photos[photoID];
|
||||
const fullWidth = this.windowW;
|
||||
const fullHeight = this.windowH;
|
||||
@ -309,7 +316,7 @@ export class AppPhotosManager {
|
||||
const url = getFileURL('download', {dcID: photo.dc_id, location, size: fullPhotoSize.size, fileName: 'photo' + photo.id + '.jpg'});
|
||||
const fileName = getFileNameByLocation(location);
|
||||
|
||||
appDownloadManager.downloadToDisc(fileName, url);
|
||||
appDownloadManager.downloadToDisc(fileName, url, 'photo' + photo.id + '.jpg');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -797,14 +797,12 @@ export class AppSidebarRight extends SidebarSlider {
|
||||
const url = (photo && photo.url) || appPhotosManager.getDocumentCachedThumb(media.id).url;
|
||||
if(url) {
|
||||
//if(needBlur) return;
|
||||
const p = renderImageFromUrl(img, url);
|
||||
|
||||
if(needBlur) {
|
||||
p.then(() => {
|
||||
//void img.offsetLeft; // reflow
|
||||
img.style.opacity = '';
|
||||
});
|
||||
}
|
||||
const needBlurCallback = needBlur ? () => {
|
||||
//void img.offsetLeft; // reflow
|
||||
img.style.opacity = '';
|
||||
} : undefined;
|
||||
renderImageFromUrl(img, url, needBlurCallback);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -8,13 +8,39 @@ export interface CancellablePromise<T> extends Promise<T> {
|
||||
resolve?: (...args: any[]) => void,
|
||||
reject?: (...args: any[]) => void,
|
||||
cancel?: () => void,
|
||||
|
||||
notify?: (...args: any[]) => void,
|
||||
notifyAll?: (...args: any[]) => void,
|
||||
lastNotify?: any,
|
||||
listeners?: Array<(...args: any[]) => void>,
|
||||
addNotifyListener?: (callback: (...args: any[]) => void) => void,
|
||||
|
||||
isFulfilled?: boolean,
|
||||
isRejected?: boolean
|
||||
}
|
||||
|
||||
export function deferredPromise<T>() {
|
||||
let deferredHelper: any = {notify: () => {}, isFulfilled: false, isRejected: false};
|
||||
let deferredHelper: any = {
|
||||
isFulfilled: false,
|
||||
isRejected: false,
|
||||
|
||||
notify: () => {},
|
||||
notifyAll: (...args: any[]) => {
|
||||
deferredHelper.lastNotify = args;
|
||||
deferredHelper.listeners.forEach((callback: any) => callback(...args));
|
||||
},
|
||||
|
||||
lastNotify: undefined,
|
||||
listeners: [],
|
||||
addNotifyListener: (callback: (...args: any[]) => void) => {
|
||||
if(deferredHelper.lastNotify) {
|
||||
callback(...deferredHelper.lastNotify);
|
||||
}
|
||||
|
||||
deferredHelper.listeners.push(callback);
|
||||
}
|
||||
};
|
||||
|
||||
let deferred: CancellablePromise<T> = new Promise<T>((resolve, reject) => {
|
||||
deferredHelper.resolve = (value: T) => {
|
||||
if(deferred.isFulfilled) return;
|
||||
@ -30,6 +56,13 @@ export function deferredPromise<T>() {
|
||||
reject(...args);
|
||||
};
|
||||
});
|
||||
|
||||
deferred.finally(() => {
|
||||
deferred.notify = null;
|
||||
deferred.listeners.length = 0;
|
||||
deferred.lastNotify = null;
|
||||
});
|
||||
|
||||
Object.assign(deferred, deferredHelper);
|
||||
|
||||
return deferred;
|
||||
|
@ -40,6 +40,10 @@ export class WebpWorkerController {
|
||||
}
|
||||
|
||||
convert(fileName: string, bytes: Uint8Array) {
|
||||
if(this.convertPromises.hasOwnProperty(fileName)) {
|
||||
return this.convertPromises[fileName];
|
||||
}
|
||||
|
||||
const convertPromise = deferredPromise<Uint8Array>();
|
||||
|
||||
fileName = 'main-' + fileName;
|
||||
|
@ -114,9 +114,14 @@ div.scrollable::-webkit-scrollbar-thumb {
|
||||
// BROWSER SCROLL
|
||||
div.scrollable-y::-webkit-scrollbar {
|
||||
width: .375rem;
|
||||
opacity: 0; // for safari
|
||||
//height: 200px;
|
||||
}
|
||||
|
||||
div.scrollable:hover::-webkit-scrollbar {
|
||||
opacity: 1; // for safari
|
||||
}
|
||||
|
||||
/* div.scrollable-y::-webkit-scrollbar-thumb {
|
||||
border: 2px solid rgba(0, 0, 0, 0);
|
||||
background-clip: padding-box;
|
||||
|
5
src/types.d.ts
vendored
5
src/types.d.ts
vendored
@ -12,7 +12,6 @@ export type MTDocument = {
|
||||
dc_id: number,
|
||||
attributes: any[],
|
||||
|
||||
thumb?: MTPhotoSize,
|
||||
type?: 'gif' | 'sticker' | 'audio' | 'voice' | 'video' | 'round' | 'photo',
|
||||
h?: number,
|
||||
w?: number,
|
||||
@ -42,7 +41,9 @@ export type MTPhotoSize = {
|
||||
size?: number,
|
||||
type?: string, // i, m, x, y, w by asc
|
||||
location?: FileLocation,
|
||||
bytes?: Uint8Array // if type == 'i'
|
||||
bytes?: Uint8Array, // if type == 'i',
|
||||
|
||||
url?: string
|
||||
};
|
||||
|
||||
export type InvokeApiOptions = Partial<{
|
||||
|
Loading…
x
Reference in New Issue
Block a user