Blur on images

Preloader with attach & detach animation
Fixed preloader layout
This commit is contained in:
morethanwords 2021-01-10 20:10:02 +04:00
parent 5b0f0286d9
commit 09ca70ecb7
19 changed files with 628 additions and 342 deletions

View File

@ -880,6 +880,10 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
//const maxWidth = appPhotosManager.windowW - 16; //const maxWidth = appPhotosManager.windowW - 16;
const maxWidth = mediaSizes.isMobile ? this.pageEl.scrollWidth : this.pageEl.scrollWidth - 16; const maxWidth = mediaSizes.isMobile ? this.pageEl.scrollWidth : this.pageEl.scrollWidth - 16;
const maxHeight = appPhotosManager.windowH - 100; const maxHeight = appPhotosManager.windowH - 100;
const gotThumb = appPhotosManager.getStrippedThumbIfNeeded(media);
if(gotThumb) {
container.append(gotThumb.image);
}
const size = appPhotosManager.setAttachmentSize(media, container, maxWidth, maxHeight); const size = appPhotosManager.setAttachmentSize(media, container, maxWidth, maxHeight);
// need after setAttachmentSize // need after setAttachmentSize

View File

@ -20,7 +20,7 @@ import LazyLoadQueue from "./lazyLoadQueue";
import { renderImageFromUrl, putPreloader, formatPhoneNumber } from "./misc"; import { renderImageFromUrl, putPreloader, formatPhoneNumber } from "./misc";
import { ripple } from "./ripple"; import { ripple } from "./ripple";
import Scrollable, { ScrollableX } from "./scrollable"; import Scrollable, { ScrollableX } from "./scrollable";
import { wrapDocument } from "./wrappers"; import { wrapDocument, wrapPhoto, wrapVideo } from "./wrappers";
const testScroll = false; const testScroll = false;
@ -351,99 +351,36 @@ export default class AppSearchSuper {
div.classList.add('grid-item'); div.classList.add('grid-item');
//this.log(message, photo); //this.log(message, photo);
const isPhoto = media._ === 'photo'; let wrapped: ReturnType<typeof wrapPhoto>;
if(media._ !== 'photo') {
const photo = isPhoto ? appPhotosManager.getPhoto(media.id) : null; wrapped = wrapVideo({
let isDownloaded: boolean; doc: media,
if(photo) { message,
isDownloaded = photo.downloaded > 0; container: div,
boxWidth: 0,
boxHeight: 0,
lazyLoadQueue: this.lazyLoadQueue,
middleware,
onlyPreview: true,
withoutPreloader: true
}).thumb;
} else { } else {
const cachedThumb = appPhotosManager.getDocumentCachedThumb(media.id); wrapped = wrapPhoto({
isDownloaded = cachedThumb?.downloaded > 0; photo: media,
} message,
container: div,
//this.log('inputMessagesFilterPhotoVideo', message, media); boxWidth: 0,
boxHeight: 0,
if(!isPhoto) { lazyLoadQueue: this.lazyLoadQueue,
const span = document.createElement('span'); middleware,
span.classList.add('video-time'); withoutPreloader: true
div.append(span);
if(media.type !== 'gif') {
span.innerText = (media.duration + '').toHHMMSS(false);
/* const spanPlay = document.createElement('span');
spanPlay.classList.add('video-play', 'tgico-largeplay', 'btn-circle', 'position-center');
div.append(spanPlay); */
} else {
span.innerText = 'GIF';
}
}
const load = () => appPhotosManager.preloadPhoto(isPhoto ? media.id : media, appPhotosManager.choosePhotoSize(media, 200, 200))
.then(() => {
if(!middleware()) {
//this.log.warn('peer changed');
return;
}
const url = (photo && photo.url) || appPhotosManager.getDocumentCachedThumb(media.id).url;
if(url) {
//if(needBlur) return;
const needBlurCallback = needBlur ? () => {
//void img.offsetLeft; // reflow
img.style.opacity = '';
if(thumb) {
window.setTimeout(() => {
thumb.remove();
}, 200);
}
} : undefined;
renderImageFromUrl(img, url, needBlurCallback);
}
}); });
let thumb: HTMLImageElement;
const sizes = media.sizes || media.thumbs;
const willHaveThumb = !isDownloaded && sizes && sizes[0].bytes;
if(willHaveThumb) {
thumb = new Image();
thumb.classList.add('grid-item-media', 'thumbnail');
thumb.dataset.mid = '' + message.mid;
appPhotosManager.setAttachmentPreview(sizes[0].bytes, thumb, false, false);
div.append(thumb);
} }
const needBlur = (!isDownloaded || !willHaveThumb) && rootScope.settings.animationsEnabled; wrapped.images.thumb && wrapped.images.thumb.classList.add('grid-item-media');
const img = new Image(); wrapped.images.full && wrapped.images.full.classList.add('grid-item-media');
img.dataset.mid = '' + message.mid;
img.classList.add('grid-item-media');
if(needBlur) img.style.opacity = '0';
div.append(img);
if(isDownloaded || willHaveThumb) { promises.push(wrapped.loadPromises.thumb);
const promise = new Promise<void>((resolve, reject) => {
(thumb || img).addEventListener('load', () => {
clearTimeout(timeout);
resolve();
});
const timeout = setTimeout(() => {
//this.log('didn\'t load', thumb, media, isDownloaded, sizes);
reject();
}, 1e3);
});
promises.push(promise);
}
if(sizes?.length) {
if(isDownloaded) load();
else this.lazyLoadQueue.push({div, load});
}
elemsToAppend.push({element: div, message}); elemsToAppend.push({element: div, message});
} }

View File

@ -44,6 +44,7 @@ import useHeavyAnimationCheck, { getHeavyAnimationPromise, dispatchHeavyAnimatio
import { fastRaf } from "../../helpers/schedulers"; import { fastRaf } from "../../helpers/schedulers";
import { deferredPromise, CancellablePromise } from "../../helpers/cancellablePromise"; import { deferredPromise, CancellablePromise } from "../../helpers/cancellablePromise";
const USE_MEDIA_TAILS = false;
const IGNORE_ACTIONS = ['messageActionHistoryClear']; const IGNORE_ACTIONS = ['messageActionHistoryClear'];
const TEST_SCROLL_TIMES: number = undefined; const TEST_SCROLL_TIMES: number = undefined;
@ -1392,7 +1393,7 @@ export default class ChatBubbles {
this.chatInner.classList.toggle('is-channel', isChannel); this.chatInner.classList.toggle('is-channel', isChannel);
} }
public renderMessagesQueue(message: any, bubble: HTMLDivElement, reverse: boolean) { public renderMessagesQueue(message: any, bubble: HTMLDivElement, reverse: boolean, promises: Promise<any>[]) {
/* let dateMessage = this.getDateContainerByMessage(message, reverse); /* let dateMessage = this.getDateContainerByMessage(message, reverse);
if(reverse) dateMessage.container.insertBefore(bubble, dateMessage.div.nextSibling); if(reverse) dateMessage.container.insertBefore(bubble, dateMessage.div.nextSibling);
else dateMessage.container.append(bubble); else dateMessage.container.append(bubble);
@ -1400,47 +1401,6 @@ export default class ChatBubbles {
//this.log('renderMessagesQueue'); //this.log('renderMessagesQueue');
let promises: Promise<any>[] = [];
(Array.from(bubble.querySelectorAll('img, video')) as HTMLImageElement[]).forEach(el => {
if(el instanceof HTMLVideoElement) {
if(!el.src) {
//this.log.warn('no source', el, source, 'src', source.src);
return;
} else if(el.readyState >= 4) return;
} else if(el.complete || !el.src) return;
let promise = new Promise<void>((resolve, reject) => {
let r: () => boolean;
let onLoad = () => {
clearTimeout(timeout);
resolve();
// lol
el.removeEventListener(el instanceof HTMLVideoElement ? 'canplay' : 'load', onLoad);
};
if(el instanceof HTMLVideoElement) {
el.addEventListener('canplay', onLoad);
r = () => el.readyState >= 1;
} else {
el.addEventListener('load', onLoad);
r = () => el.complete;
}
// for safari
let c = () => r() ? onLoad() : window.requestAnimationFrame(c);
window.requestAnimationFrame(c);
let timeout = setTimeout(() => {
// @ts-ignore
//this.log.error('did not called', el, el.parentElement, el.complete, el.readyState, src);
resolve();
}, 1500);
});
promises.push(promise);
});
this.messagesQueue.push({message, bubble, reverse, promises}); this.messagesQueue.push({message, bubble, reverse, promises});
this.setMessagesQueuePromise(); this.setMessagesQueuePromise();
@ -1624,6 +1584,8 @@ export default class ChatBubbles {
bubble.dataset.mid = message.mid; bubble.dataset.mid = message.mid;
bubble.dataset.timestamp = message.date; bubble.dataset.timestamp = message.date;
const loadPromises: Promise<any>[] = [];
if(message._ === 'messageService') { if(message._ === 'messageService') {
let action = message.action; let action = message.action;
let _ = action._; let _ = action._;
@ -1636,7 +1598,7 @@ export default class ChatBubbles {
bubbleContainer.innerHTML = `<div class="service-msg">${message.rReply}</div>`; bubbleContainer.innerHTML = `<div class="service-msg">${message.rReply}</div>`;
if(updatePosition) { if(updatePosition) {
this.renderMessagesQueue(message, bubble, reverse); this.renderMessagesQueue(message, bubble, reverse, loadPromises);
} }
return bubble; return bubble;
@ -1832,13 +1794,14 @@ export default class ChatBubbles {
middleware: this.getMiddleware(), middleware: this.getMiddleware(),
isOut: our, isOut: our,
lazyLoadQueue: this.lazyLoadQueue, lazyLoadQueue: this.lazyLoadQueue,
chat: this.chat chat: this.chat,
loadPromises
}); });
break; break;
} }
const withTail = !isAndroid && !message.message && !withReplies; const withTail = !isAndroid && !message.message && !withReplies && USE_MEDIA_TAILS;
if(withTail) bubble.classList.add('with-media-tail'); if(withTail) bubble.classList.add('with-media-tail');
wrapPhoto({ wrapPhoto({
photo, photo,
@ -1847,7 +1810,8 @@ export default class ChatBubbles {
withTail, withTail,
isOut, isOut,
lazyLoadQueue: this.lazyLoadQueue, lazyLoadQueue: this.lazyLoadQueue,
middleware: this.getMiddleware() middleware: this.getMiddleware(),
loadPromises
}); });
break; break;
@ -1858,7 +1822,7 @@ export default class ChatBubbles {
let webpage = messageMedia.webpage; let webpage = messageMedia.webpage;
////////this.log('messageMediaWebPage', webpage); ////////this.log('messageMediaWebPage', webpage);
if(webpage._ == 'webPageEmpty') { if(webpage._ === 'webPageEmpty') {
break; break;
} }
@ -1895,7 +1859,8 @@ export default class ChatBubbles {
lazyLoadQueue: this.lazyLoadQueue, lazyLoadQueue: this.lazyLoadQueue,
middleware: this.getMiddleware(), middleware: this.getMiddleware(),
isOut, isOut,
group: CHAT_ANIMATION_GROUP group: CHAT_ANIMATION_GROUP,
loadPromises
}); });
//} //}
} else { } else {
@ -1945,7 +1910,7 @@ export default class ChatBubbles {
bubble.classList.add('photo'); bubble.classList.add('photo');
const size = webpage.photo.sizes[webpage.photo.sizes.length - 1]; const size = webpage.photo.sizes[webpage.photo.sizes.length - 1];
if(size.w == size.h && quoteTextDiv.childElementCount) { if(size.w === size.h && quoteTextDiv.childElementCount) {
bubble.classList.add('is-square-photo'); bubble.classList.add('is-square-photo');
} else if(size.h > size.w) { } else if(size.h > size.w) {
bubble.classList.add('is-vertical-photo'); bubble.classList.add('is-vertical-photo');
@ -1959,7 +1924,8 @@ export default class ChatBubbles {
boxHeight: mediaSizes.active.webpage.height, boxHeight: mediaSizes.active.webpage.height,
isOut, isOut,
lazyLoadQueue: this.lazyLoadQueue, lazyLoadQueue: this.lazyLoadQueue,
middleware: this.getMiddleware() middleware: this.getMiddleware(),
loadPromises
}); });
} }
@ -1986,7 +1952,7 @@ export default class ChatBubbles {
} }
let size = bubble.classList.contains('emoji-big') ? 140 : 200; let size = bubble.classList.contains('emoji-big') ? 140 : 200;
this.appPhotosManager.setAttachmentSize(doc, attachmentDiv, size, size, true); this.appPhotosManager.setAttachmentSize(doc, attachmentDiv, size, size);
//let preloader = new ProgressivePreloader(attachmentDiv, false); //let preloader = new ProgressivePreloader(attachmentDiv, false);
bubbleContainer.style.height = attachmentDiv.style.height; bubbleContainer.style.height = attachmentDiv.style.height;
bubbleContainer.style.width = attachmentDiv.style.width; bubbleContainer.style.width = attachmentDiv.style.width;
@ -2019,10 +1985,11 @@ export default class ChatBubbles {
middleware: this.getMiddleware(), middleware: this.getMiddleware(),
isOut: our, isOut: our,
lazyLoadQueue: this.lazyLoadQueue, lazyLoadQueue: this.lazyLoadQueue,
chat: this.chat chat: this.chat,
loadPromises
}); });
} else { } else {
const withTail = !isAndroid && !isApple && doc.type != 'round' && !message.message && !withReplies; const withTail = !isAndroid && !isApple && doc.type != 'round' && !message.message && !withReplies && USE_MEDIA_TAILS;
if(withTail) bubble.classList.add('with-media-tail'); if(withTail) bubble.classList.add('with-media-tail');
wrapVideo({ wrapVideo({
doc, doc,
@ -2034,7 +2001,8 @@ export default class ChatBubbles {
isOut, isOut,
lazyLoadQueue: this.lazyLoadQueue, lazyLoadQueue: this.lazyLoadQueue,
middleware: this.getMiddleware(), middleware: this.getMiddleware(),
group: CHAT_ANIMATION_GROUP group: CHAT_ANIMATION_GROUP,
loadPromises
}); });
} }
@ -2045,7 +2013,8 @@ export default class ChatBubbles {
message, message,
bubble, bubble,
messageDiv, messageDiv,
chat: this.chat chat: this.chat,
loadPromises
}); });
if(newNameContainer) { if(newNameContainer) {
@ -2260,7 +2229,7 @@ export default class ChatBubbles {
if(updatePosition) { if(updatePosition) {
this.bubbleGroups.addBubble(bubble, message, reverse); this.bubbleGroups.addBubble(bubble, message, reverse);
this.renderMessagesQueue(message, bubble, reverse); this.renderMessagesQueue(message, bubble, reverse, loadPromises);
} else { } else {
this.bubbleGroups.updateGroupByMessageId(message.mid); this.bubbleGroups.updateGroupByMessageId(message.mid);
} }

View File

@ -64,7 +64,7 @@ export default class GifsMasonry {
const doc = appDocsManager.getDoc(docId); const doc = appDocsManager.getDoc(docId);
const promise = this.scrollPromise.then(() => { const promise = this.scrollPromise.then(() => {
const promise = wrapVideo({ const res = wrapVideo({
doc, doc,
container: div as HTMLDivElement, container: div as HTMLDivElement,
lazyLoadQueue: null, lazyLoadQueue: null,
@ -73,6 +73,7 @@ export default class GifsMasonry {
noInfo: true, noInfo: true,
}); });
const promise = res.loadPromise;
promise.finally(() => { promise.finally(() => {
const video = div.querySelector('video'); const video = div.querySelector('video');

View File

@ -1,6 +1,8 @@
import { debounce } from "../helpers/schedulers"; import { debounce } from "../helpers/schedulers";
import { logger, LogLevels } from "../lib/logger"; import { logger, LogLevels } from "../lib/logger";
import VisibilityIntersector, { OnVisibilityChange } from "./visibilityIntersector"; import VisibilityIntersector, { OnVisibilityChange } from "./visibilityIntersector";
import { DEBUG } from "../lib/mtproto/mtproto_config";
import { findAndSpliceAll } from "../helpers/array";
type LazyLoadElementBase = { type LazyLoadElementBase = {
load: () => Promise<any> load: () => Promise<any>
@ -42,14 +44,16 @@ export class LazyLoadQueueBase {
public lock() { public lock() {
if(this.lockPromise) return; if(this.lockPromise) return;
const perf = performance.now(); //const perf = performance.now();
this.lockPromise = new Promise((resolve, reject) => { this.lockPromise = new Promise((resolve, reject) => {
this.unlockResolve = resolve; this.unlockResolve = resolve;
}); });
/* if(DEBUG) {
this.lockPromise.then(() => { this.lockPromise.then(() => {
this.log('was locked for:', performance.now() - perf); this.log('was locked for:', performance.now() - perf);
}); });
} */
} }
public unlock() { public unlock() {
@ -68,7 +72,9 @@ export class LazyLoadQueueBase {
this.inProcess.add(item); this.inProcess.add(item);
/* if(DEBUG) {
this.log('will load media', this.lockPromise, item); this.log('will load media', this.lockPromise, item);
} */
try { try {
//await new Promise((resolve) => setTimeout(resolve, 2e3)); //await new Promise((resolve) => setTimeout(resolve, 2e3));
@ -81,7 +87,9 @@ export class LazyLoadQueueBase {
this.inProcess.delete(item); this.inProcess.delete(item);
/* if(DEBUG) {
this.log('loaded media', item); this.log('loaded media', item);
} */
this.processQueue(); this.processQueue();
} }
@ -104,7 +112,7 @@ export class LazyLoadQueueBase {
do { do {
if(item) { if(item) {
this.queue.findAndSplice(i => i == item); this.queue.findAndSplice(i => i === item);
} else { } else {
item = this.getItem(); item = this.getItem();
} }
@ -168,12 +176,12 @@ export class LazyLoadQueueIntersector extends LazyLoadQueueBase {
} }
protected addElement(method: 'push' | 'unshift', el: LazyLoadElement) { protected addElement(method: 'push' | 'unshift', el: LazyLoadElement) {
const item = this.queue.find(i => i.div == el.div); const item = this.queue.find(i => i.div === el.div && i.load === el.load);
if(item) { if(item) {
return false; return false;
} else { } else {
for(const item of this.inProcess) { for(const item of this.inProcess) {
if(item.div == el.div) { if(item.div === el.div && item.load === el.load) {
return false; return false;
} }
} }
@ -201,7 +209,8 @@ export class LazyLoadQueueIntersector extends LazyLoadQueueBase {
} }
public unobserve(el: HTMLElement) { public unobserve(el: HTMLElement) {
this.queue.findAndSplice(i => i.div === el); findAndSpliceAll(this.queue, (i) => i.div === el);
this.intersector.unobserve(el); this.intersector.unobserve(el);
} }
} }
@ -218,12 +227,11 @@ export default class LazyLoadQueue extends LazyLoadQueueIntersector {
this.log('isIntersecting', target); this.log('isIntersecting', target);
// need for set element first if scrolled // need for set element first if scrolled
const item = this.queue.findAndSplice(i => i.div == target); findAndSpliceAll(this.queue, (i) => i.div === target).forEach(item => {
if(item) {
item.wasSeen = true; item.wasSeen = true;
this.queue.unshift(item); this.queue.unshift(item);
//this.processQueue(item); //this.processQueue(item);
} });
this.setProcessQueueTimeout(); this.setProcessQueueTimeout();
} }
@ -261,11 +269,11 @@ export class LazyLoadQueueRepeat extends LazyLoadQueueIntersector {
super(parallelLimit); super(parallelLimit);
this.intersector = new VisibilityIntersector((target, visible) => { this.intersector = new VisibilityIntersector((target, visible) => {
const spliced = findAndSpliceAll(this.queue, (i) => i.div === target);
if(visible) { if(visible) {
const item = this.queue.findAndSplice(i => i.div == target); spliced.forEach(item => {
this.queue.unshift(item || this._queue.get(target)); this.queue.unshift(item || this._queue.get(target));
} else { });
this.queue.findAndSplice(i => i.div == target);
} }
this.onVisibilityChange && this.onVisibilityChange(target, visible); this.onVisibilityChange && this.onVisibilityChange(target, visible);
@ -298,9 +306,11 @@ export class LazyLoadQueueRepeat2 extends LazyLoadQueueIntersector {
super(parallelLimit); super(parallelLimit);
this.intersector = new VisibilityIntersector((target, visible) => { this.intersector = new VisibilityIntersector((target, visible) => {
const item = this.queue.findAndSplice(i => i.div == target); const spliced = findAndSpliceAll(this.queue, (i) => i.div === target);
if(visible && item) { if(visible && spliced.length) {
spliced.forEach(item => {
this.queue.unshift(item); this.queue.unshift(item);
});
} }
this.onVisibilityChange && this.onVisibilityChange(target, visible); this.onVisibilityChange && this.onVisibilityChange(target, visible);

View File

@ -1,5 +1,8 @@
import { isInDOM, cancelEvent, attachClickEvent } from "../helpers/dom"; import { isInDOM, cancelEvent, attachClickEvent } from "../helpers/dom";
import { CancellablePromise } from "../helpers/cancellablePromise"; import { CancellablePromise } from "../helpers/cancellablePromise";
import SetTransition from "./singleTransition";
const TRANSITION_TIME = 200;
export default class ProgressivePreloader { export default class ProgressivePreloader {
public preloader: HTMLDivElement; public preloader: HTMLDivElement;
@ -20,8 +23,8 @@ export default class ProgressivePreloader {
this.preloader.innerHTML = ` this.preloader.innerHTML = `
<div class="you-spin-me-round"> <div class="you-spin-me-round">
<svg xmlns="http://www.w3.org/2000/svg" class="preloader-circular" viewBox="25 25 50 50"> <svg xmlns="http://www.w3.org/2000/svg" class="preloader-circular" viewBox="${streamable ? '25 25 50 50' : '27 27 54 54'}">
<circle class="preloader-path-new" cx="50" cy="50" r="${streamable ? 19 : 23}" fill="none" stroke-miterlimit="10"/> <circle class="preloader-path-new" cx="${streamable ? '50' : '54'}" cy="${streamable ? '50' : '54'}" r="${streamable ? 19 : 24}" fill="none" stroke-miterlimit="10"/>
</svg> </svg>
</div>`; </div>`;
@ -95,16 +98,20 @@ export default class ProgressivePreloader {
} }
this.detached = false; this.detached = false;
window.requestAnimationFrame(() => { /* window.requestAnimationFrame(() => {
if(this.detached) return; if(this.detached) return;
this.detached = false; this.detached = false; */
elem[this.attachMethod](this.preloader); elem[this.attachMethod](this.preloader);
window.requestAnimationFrame(() => {
SetTransition(this.preloader, 'is-visible', true, TRANSITION_TIME);
});
if(this.cancelable && reset) { if(this.cancelable && reset) {
this.setProgress(0); this.setProgress(0);
} }
}); //});
} }
public detach() { public detach() {
@ -113,14 +120,18 @@ export default class ProgressivePreloader {
//return; //return;
if(this.preloader.parentElement) { if(this.preloader.parentElement) {
/* setTimeout(() => */window.requestAnimationFrame(() => { /* setTimeout(() => *///window.requestAnimationFrame(() => {
if(!this.detached) return; /* if(!this.detached) return;
this.detached = true; this.detached = true; */
if(this.preloader.parentElement) { //if(this.preloader.parentElement) {
window.requestAnimationFrame(() => {
SetTransition(this.preloader, 'is-visible', false, TRANSITION_TIME, () => {
this.preloader.remove(); this.preloader.remove();
} });
})/* , 5e3) */; });
//}
//})/* , 5e3) */;
} }
} }
@ -137,7 +148,7 @@ export default class ProgressivePreloader {
try { try {
const totalLength = this.circle.getTotalLength(); const totalLength = this.circle.getTotalLength();
//console.log('setProgress', (percents / 100 * totalLength)); //console.log('setProgress', (percents / 100 * totalLength));
this.circle.style.strokeDasharray = '' + Math.max(5, percents / 100 * totalLength) + ', 200'; this.circle.style.strokeDasharray = '' + Math.max(5, percents / 100 * totalLength) + ', ' + totalLength;
} catch(err) {} } catch(err) {}
} }
} }

View File

@ -29,10 +29,11 @@ import RichTextProcessor from '../lib/richtextprocessor';
import appImManager from '../lib/appManagers/appImManager'; import appImManager from '../lib/appManagers/appImManager';
import Chat from './chat/chat'; import Chat from './chat/chat';
import { SearchSuperContext } from './appSearchSuper.'; import { SearchSuperContext } from './appSearchSuper.';
import rootScope from '../lib/rootScope';
const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB
export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue, noInfo, group}: { export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue, noInfo, group, onlyPreview, withoutPreloader, loadPromises}: {
doc: MyDocument, doc: MyDocument,
container?: HTMLElement, container?: HTMLElement,
message?: any, message?: any,
@ -43,7 +44,10 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
middleware?: () => boolean, middleware?: () => boolean,
lazyLoadQueue?: LazyLoadQueue, lazyLoadQueue?: LazyLoadQueue,
noInfo?: true, noInfo?: true,
group?: string group?: string,
onlyPreview?: boolean,
withoutPreloader?: boolean,
loadPromises?: Promise<any>[]
}) { }) {
const isAlbumItem = !(boxWidth && boxHeight); const isAlbumItem = !(boxWidth && boxHeight);
const canAutoplay = doc.type != 'video' || (doc.size <= MAX_VIDEO_AUTOPLAY_SIZE && !isAlbumItem); const canAutoplay = doc.type != 'video' || (doc.size <= MAX_VIDEO_AUTOPLAY_SIZE && !isAlbumItem);
@ -71,8 +75,13 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
} }
} }
let res: {
thumb?: typeof photoRes,
loadPromise: Promise<any>
} = {} as any;
if(doc.mime_type == 'image/gif') { if(doc.mime_type == 'image/gif') {
return wrapPhoto({ const photoRes = wrapPhoto({
photo: doc, photo: doc,
message, message,
container, container,
@ -81,8 +90,14 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
withTail, withTail,
isOut, isOut,
lazyLoadQueue, lazyLoadQueue,
middleware middleware,
withoutPreloader,
loadPromises
}); });
res.thumb = photoRes;
res.loadPromise = photoRes.loadPromises.full;
return res;
} }
/* const video = doc.type == 'round' ? appMediaPlaybackController.addMedia(doc, message.mid) as HTMLVideoElement : document.createElement('video'); /* const video = doc.type == 'round' ? appMediaPlaybackController.addMedia(doc, message.mid) as HTMLVideoElement : document.createElement('video');
@ -91,6 +106,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
} */ } */
const video = document.createElement('video'); const video = document.createElement('video');
video.classList.add('media-video');
video.muted = true; video.muted = true;
video.setAttribute('playsinline', 'true'); video.setAttribute('playsinline', 'true');
if(doc.type == 'round') { if(doc.type == 'round') {
@ -156,10 +172,9 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
video.autoplay = true; // для safari video.autoplay = true; // для safari
} }
let img: HTMLImageElement; let photoRes: ReturnType<typeof wrapPhoto>;
if(message) { if(message) {
if(!canAutoplay) { photoRes = wrapPhoto({
return wrapPhoto({
photo: doc, photo: doc,
message, message,
container, container,
@ -168,40 +183,25 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
withTail, withTail,
isOut, isOut,
lazyLoadQueue, lazyLoadQueue,
middleware middleware,
withoutPreloader: true,
loadPromises
}); });
res.thumb = photoRes;
if(!canAutoplay || onlyPreview) {
res.loadPromise = photoRes.loadPromises.full;
return res;
} }
if(withTail) { if(withTail) {
img = wrapMediaWithTail(doc, message, container, boxWidth, boxHeight, isOut); const foreignObject = (photoRes.images.thumb || photoRes.images.full).parentElement;
} else {
if(boxWidth && boxHeight) { // !album
appPhotosManager.setAttachmentSize(doc, container, boxWidth, boxHeight, false, true);
}
if(doc.thumbs?.length && 'bytes' in doc.thumbs[0]) {
appPhotosManager.setAttachmentPreview(doc.thumbs[0].bytes, container, false);
}
img = container.lastElementChild as HTMLImageElement;
if(img?.tagName != 'IMG') {
container.append(img = new Image());
}
}
if(img) {
img.classList.add('thumbnail');
}
if(withTail) {
const foreignObject = img.parentElement;
video.width = +foreignObject.getAttributeNS(null, 'width'); video.width = +foreignObject.getAttributeNS(null, 'width');
video.height = +foreignObject.getAttributeNS(null, 'height'); video.height = +foreignObject.getAttributeNS(null, 'height');
foreignObject.append(video); foreignObject.append(video);
} }
} } else { // * gifs masonry
if(!img?.parentElement) {
const gotThumb = appDocsManager.getThumb(doc, false); const gotThumb = appDocsManager.getThumb(doc, false);
if(gotThumb) { if(gotThumb) {
gotThumb.promise.then(() => { gotThumb.promise.then(() => {
@ -253,14 +253,10 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
//if(doc.type == 'gif'/* || true */) { //if(doc.type == 'gif'/* || true */) {
video.addEventListener(isAppleMobile ? 'loadeddata' : 'canplay', () => { video.addEventListener(isAppleMobile ? 'loadeddata' : 'canplay', () => {
if(img?.parentElement) {
img.remove();
}
/* if(!video.paused) { /* if(!video.paused) {
video.pause(); video.pause();
} */ } */
if(doc.type != 'round' && group) { if(doc.type !== 'round' && group) {
animationIntersector.addAnimation(video, group); animationIntersector.addAnimation(video, group);
} }
@ -327,7 +323,9 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
return; return;
} */ } */
return /* doc.downloaded || */!lazyLoadQueue/* && false */ ? loadVideo() : (lazyLoadQueue.push({div: container, load: loadVideo/* , wasSeen: true */}), Promise.resolve()); res.loadPromise = !lazyLoadQueue ? loadVideo() : (lazyLoadQueue.push({div: container, load: loadVideo}), Promise.resolve());
return res;
} }
export const formatDate = (timestamp: number, monthShort = false, withYear = true) => { export const formatDate = (timestamp: number, monthShort = false, withYear = true) => {
@ -344,13 +342,14 @@ export const formatDate = (timestamp: number, monthShort = false, withYear = tru
return str + ' at ' + date.getHours() + ':' + ('0' + date.getMinutes()).slice(-2); return str + ' at ' + date.getHours() + ':' + ('0' + date.getMinutes()).slice(-2);
}; };
export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showSender, searchContext}: { export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showSender, searchContext, loadPromises}: {
message: any, message: any,
withTime?: boolean, withTime?: boolean,
fontWeight?: number, fontWeight?: number,
voiceAsMusic?: boolean, voiceAsMusic?: boolean,
showSender?: boolean, showSender?: boolean,
searchContext?: SearchSuperContext searchContext?: SearchSuperContext,
loadPromises?: Promise<any>[]
}): HTMLElement { }): HTMLElement {
if(!fontWeight) fontWeight = 500; if(!fontWeight) fontWeight = 500;
@ -394,7 +393,8 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS
message: null, message: null,
container: icoDiv, container: icoDiv,
boxWidth: 54, boxWidth: 54,
boxHeight: 54 boxHeight: 54,
loadPromises
}); });
icoDiv.style.width = icoDiv.style.height = ''; icoDiv.style.width = icoDiv.style.height = '';
} }
@ -476,7 +476,11 @@ function wrapMediaWithTail(photo: MyPhoto | MyDocument, message: {mid: number, m
const foreignObject = document.createElementNS("http://www.w3.org/2000/svg", 'foreignObject'); const foreignObject = document.createElementNS("http://www.w3.org/2000/svg", 'foreignObject');
appPhotosManager.setAttachmentSize(photo, foreignObject, boxWidth, boxHeight/* , false, true */); const gotThumb = appPhotosManager.getStrippedThumbIfNeeded(photo);
if(gotThumb) {
foreignObject.append(gotThumb.image);
}
appPhotosManager.setAttachmentSize(photo, foreignObject, boxWidth, boxHeight);
const width = +foreignObject.getAttributeNS(null, 'width'); const width = +foreignObject.getAttributeNS(null, 'width');
const height = +foreignObject.getAttributeNS(null, 'height'); const height = +foreignObject.getAttributeNS(null, 'height');
@ -525,7 +529,7 @@ function wrapMediaWithTail(photo: MyPhoto | MyDocument, message: {mid: number, m
return img; return img;
} }
export function wrapPhoto({photo, message, container, boxWidth, boxHeight, withTail, isOut, lazyLoadQueue, middleware, size}: { export function wrapPhoto({photo, message, container, boxWidth, boxHeight, withTail, isOut, lazyLoadQueue, middleware, size, withoutPreloader, loadPromises}: {
photo: MyPhoto | MyDocument, photo: MyPhoto | MyDocument,
message: any, message: any,
container: HTMLElement, container: HTMLElement,
@ -535,8 +539,23 @@ export function wrapPhoto({photo, message, container, boxWidth, boxHeight, withT
isOut?: boolean, isOut?: boolean,
lazyLoadQueue?: LazyLoadQueue, lazyLoadQueue?: LazyLoadQueue,
middleware?: () => boolean, middleware?: () => boolean,
size?: PhotoSize size?: PhotoSize,
withoutPreloader?: boolean,
loadPromises?: Promise<any>[]
}) { }) {
if(!((photo as MyPhoto).sizes || (photo as MyDocument).thumbs)) {
return {
loadPromises: {
thumb: Promise.resolve(),
full: Promise.resolve()
},
images: {
thumb: null,
full: null
}
};
}
if(boxWidth === undefined) { if(boxWidth === undefined) {
boxWidth = mediaSizes.active.regular.width; boxWidth = mediaSizes.active.regular.width;
} }
@ -545,44 +564,50 @@ export function wrapPhoto({photo, message, container, boxWidth, boxHeight, withT
boxHeight = mediaSizes.active.regular.height; boxHeight = mediaSizes.active.regular.height;
} }
let loadThumbPromise: Promise<any>;
let thumbImage: HTMLImageElement;
let image: HTMLImageElement; let image: HTMLImageElement;
if(withTail) { if(withTail) {
image = wrapMediaWithTail(photo, message, container, boxWidth, boxHeight, isOut); image = wrapMediaWithTail(photo, message, container, boxWidth, boxHeight, isOut);
} else { } else {
image = new Image();
if(boxWidth && boxHeight) { // !album if(boxWidth && boxHeight) { // !album
size = appPhotosManager.setAttachmentSize(photo, container, boxWidth, boxHeight, false, true); size = appPhotosManager.setAttachmentSize(photo, container, boxWidth, boxHeight);
} }
if(photo._ == 'document' || !photo.downloaded) { const gotThumb = appPhotosManager.getStrippedThumbIfNeeded(photo);
const thumbs = (photo as MyPhoto).sizes || (photo as MyDocument).thumbs; if(gotThumb) {
if(thumbs?.length && 'bytes' in thumbs[0]) { loadThumbPromise = gotThumb.loadPromise;
appPhotosManager.setAttachmentPreview(thumbs[0].bytes, container, false); thumbImage = gotThumb.image;
thumbImage.classList.add('media-photo');
container.append(thumbImage);
} }
} }
image = container.lastElementChild as HTMLImageElement; image.classList.add('media-photo');
if(!image || image.tagName != 'IMG') {
container.append(image = new Image());
}
}
if(!((photo as MyPhoto).sizes || (photo as MyDocument).thumbs)) {
return Promise.resolve();
}
//console.log('wrapPhoto downloaded:', photo, photo.downloaded, container); //console.log('wrapPhoto downloaded:', photo, photo.downloaded, container);
const cacheContext = appPhotosManager.getCacheContext(photo); const cacheContext = appPhotosManager.getCacheContext(photo);
const needFadeIn = (thumbImage || !cacheContext.downloaded) && rootScope.settings.animationsEnabled;
if(needFadeIn) {
image.classList.add('fade-in');
}
let preloader: ProgressivePreloader; let preloader: ProgressivePreloader;
if(message?.media?.preloader) { // means upload if(message?.media?.preloader) { // means upload
message.media.preloader.attach(container, false); message.media.preloader.attach(container, false);
} else if(!cacheContext.downloaded) { } else if(!cacheContext.downloaded && !withoutPreloader) {
preloader = new ProgressivePreloader(null, false, false, photo._ == 'document' ? 'prepend' : 'append'); preloader = new ProgressivePreloader(null, false, false, 'prepend');
} }
let loadPromise: Promise<any>;
const load = () => { const load = () => {
const promise = photo._ == 'document' && photo.animated ? if(loadPromise) return loadPromise;
const promise = photo._ === 'document' && photo.mime_type === 'image/gif' ?
appDocsManager.downloadDoc(photo, undefined, lazyLoadQueue?.queueId) : appDocsManager.downloadDoc(photo, undefined, lazyLoadQueue?.queueId) :
appPhotosManager.preloadPhoto(photo, size, lazyLoadQueue?.queueId); appPhotosManager.preloadPhoto(photo, size, lazyLoadQueue?.queueId);
@ -590,14 +615,56 @@ export function wrapPhoto({photo, message, container, boxWidth, boxHeight, withT
preloader.attach(container, true, promise); preloader.attach(container, true, promise);
} }
return promise.then(() => { return loadPromise = promise.then(() => {
if(middleware && !middleware()) return; if(middleware && !middleware()) return;
renderImageFromUrl(image || container, cacheContext.url || photo.url); return new Promise((resolve) => {
renderImageFromUrl(image, cacheContext.url || photo.url, () => {
container.append(image);
window.requestAnimationFrame(() => {
resolve();
});
//resolve();
if(needFadeIn) {
setTimeout(() => {
image.classList.remove('fade-in');
if(thumbImage) {
thumbImage.remove();
}
}, 200);
}
});
});
}); });
}; };
return cacheContext.downloaded || !lazyLoadQueue ? load() : (lazyLoadQueue.push({div: container, load/* : load, wasSeen: true */}), Promise.resolve()); if(cacheContext.downloaded) {
loadThumbPromise = load();
}
if(!lazyLoadQueue) {
loadPromise = load();
} else {
lazyLoadQueue.push({div: container, load});
}
if(loadPromises) {
loadPromises.push(loadThumbPromise);
}
return {
loadPromises: {
thumb: loadThumbPromise,
full: loadPromise || Promise.resolve()
},
images: {
thumb: thumbImage,
full: image
}
};
} }
export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, onlyThumb, emoji, width, height, withThumb, loop}: { export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, onlyThumb, emoji, width, height, withThumb, loop}: {
@ -923,14 +990,15 @@ export function prepareAlbum(options: {
} */ } */
} }
export function wrapAlbum({groupId, attachmentDiv, middleware, uploading, lazyLoadQueue, isOut, chat}: { export function wrapAlbum({groupId, attachmentDiv, middleware, uploading, lazyLoadQueue, isOut, chat, loadPromises}: {
groupId: string, groupId: string,
attachmentDiv: HTMLElement, attachmentDiv: HTMLElement,
middleware?: () => boolean, middleware?: () => boolean,
lazyLoadQueue?: LazyLoadQueue, lazyLoadQueue?: LazyLoadQueue,
uploading?: boolean, uploading?: boolean,
isOut: boolean, isOut: boolean,
chat: Chat chat: Chat,
loadPromises?: Promise<any>[]
}) { }) {
const items: {size: PhotoSize.photoSize, media: any, message: any}[] = []; const items: {size: PhotoSize.photoSize, media: any, message: any}[] = [];
@ -974,7 +1042,8 @@ export function wrapAlbum({groupId, attachmentDiv, middleware, uploading, lazyLo
isOut, isOut,
lazyLoadQueue, lazyLoadQueue,
middleware, middleware,
size size,
loadPromises
}); });
} else { } else {
wrapVideo({ wrapVideo({
@ -986,19 +1055,21 @@ export function wrapAlbum({groupId, attachmentDiv, middleware, uploading, lazyLo
withTail: false, withTail: false,
isOut, isOut,
lazyLoadQueue, lazyLoadQueue,
middleware middleware,
loadPromises
}); });
} }
}); });
} }
export function wrapGroupedDocuments({albumMustBeRenderedFull, message, bubble, messageDiv, chat}: { export function wrapGroupedDocuments({albumMustBeRenderedFull, message, bubble, messageDiv, chat, loadPromises}: {
albumMustBeRenderedFull: boolean, albumMustBeRenderedFull: boolean,
message: any, message: any,
messageDiv: HTMLElement, messageDiv: HTMLElement,
bubble: HTMLElement, bubble: HTMLElement,
uploading?: boolean, uploading?: boolean,
chat: Chat chat: Chat,
loadPromises?: Promise<any>[]
}) { }) {
let nameContainer: HTMLDivElement; let nameContainer: HTMLDivElement;
const mids = albumMustBeRenderedFull ? chat.getMidsByMid(message.mid) : [message.mid]; const mids = albumMustBeRenderedFull ? chat.getMidsByMid(message.mid) : [message.mid];
@ -1011,7 +1082,8 @@ export function wrapGroupedDocuments({albumMustBeRenderedFull, message, bubble,
const message = chat.getMessage(mid); const message = chat.getMessage(mid);
const doc = message.media.document; const doc = message.media.document;
const div = wrapDocument({ const div = wrapDocument({
message message,
loadPromises
}); });
const container = document.createElement('div'); const container = document.createElement('div');

View File

@ -14,3 +14,13 @@ export function listMergeSorted(list1: any[] = [], list2: any[] = []) {
} }
export const accumulate = (arr: number[], initialValue: number) => arr.reduce((acc, value) => acc + value, initialValue); export const accumulate = (arr: number[], initialValue: number) => arr.reduce((acc, value) => acc + value, initialValue);
export function findAndSpliceAll<T>(array: Array<T>, verify: (value: T, index: number, arr: typeof array) => boolean) {
const out: typeof array = [];
let idx = -1;
while((idx = array.findIndex(verify)) !== -1) {
out.push(array.splice(idx, 1)[0]);
}
return out;
}

34
src/helpers/blur.ts Normal file
View File

@ -0,0 +1,34 @@
import fastBlur from '../vendor/fastBlur';
import { fastRaf } from './schedulers';
const RADIUS = 2;
const ITERATIONS = 2;
export default function blur(dataUri: string, delay?: number) {
return new Promise<string>((resolve) => {
fastRaf(() => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d')!;
ctx.drawImage(img, 0, 0);
fastBlur(ctx, 0, 0, canvas.width, canvas.height, RADIUS, ITERATIONS);
resolve(canvas.toDataURL());
};
if(delay) {
setTimeout(() => {
img.src = dataUri;
}, delay);
} else {
img.src = dataUri;
}
});
});
}

View File

@ -8,6 +8,7 @@ import { RichTextProcessor } from '../richtextprocessor';
import webpWorkerController from '../webp/webpWorkerController'; import webpWorkerController from '../webp/webpWorkerController';
import appDownloadManager, { DownloadBlob } from './appDownloadManager'; import appDownloadManager, { DownloadBlob } from './appDownloadManager';
import appPhotosManager from './appPhotosManager'; import appPhotosManager from './appPhotosManager';
import blur from '../../helpers/blur';
export type MyDocument = Document.document; export type MyDocument = Document.document;
@ -246,7 +247,7 @@ export class AppDocsManager {
if('bytes' in thumb) { if('bytes' in thumb) {
// * exclude from state // * exclude from state
defineNotNumerableProperties(thumb, ['url']); defineNotNumerableProperties(thumb, ['url']);
thumb.url = appPhotosManager.getPreviewURLFromBytes(thumb.bytes, !!doc.sticker); promise = blur(appPhotosManager.getPreviewURLFromBytes(thumb.bytes, !!doc.sticker)).then(url => thumb.url = url);
} else { } else {
//return this.getFileURL(doc, false, thumb); //return this.getFileURL(doc, false, thumb);
promise = this.downloadDoc(doc, thumb); promise = this.downloadDoc(doc, thumb);

View File

@ -19,7 +19,7 @@ import appUsersManager from "./appUsersManager";
#ce671b 5 orange #ce671b 5 orange
*/ */
const DialogColorsFg = ['#c03d33', '#4fad2d', '#d09306', '#168acd', '#8544d6', '#cd4073', '#2996ad', '#ce671b']; const DialogColorsFg = ['#c03d33', '#4fad2d', '#d09306', '#168acd', '#8544d6', '#cd4073', '#2996ad', '#ce671b'];
const DialogColors = ['#e17076', '#7bc862', '#e5ca77', '#65AADD', '#a695e7', '#ee7aae', '#6ec9cb', '#faa774']; const DialogColors = ['red', 'green', 'yellow', 'blue', 'violet', 'pink', 'cyan', 'orange'];
const DialogColorsMap = [0, 7, 4, 1, 6, 3, 5]; const DialogColorsMap = [0, 7, 4, 1, 6, 3, 5];
export type PeerType = 'channel' | 'chat' | 'megagroup' | 'group' | 'saved'; export type PeerType = 'channel' | 'chat' | 'megagroup' | 'group' | 'saved';

View File

@ -12,6 +12,8 @@ import { MyDocument } from "./appDocsManager";
import appDownloadManager from "./appDownloadManager"; import appDownloadManager from "./appDownloadManager";
import appUsersManager from "./appUsersManager"; import appUsersManager from "./appUsersManager";
import { MOUNT_CLASS_TO } from "../mtproto/mtproto_config"; import { MOUNT_CLASS_TO } from "../mtproto/mtproto_config";
import blur from "../../helpers/blur";
import { renderImageFromUrl } from "../../components/misc";
export type MyPhoto = Photo.photo; export type MyPhoto = Photo.photo;
@ -106,7 +108,7 @@ export class AppPhotosManager {
bestPhotoSize = photoSize; bestPhotoSize = photoSize;
const {w, h} = calcImageInBox(photoSize.w, photoSize.h, width, height); const {w, h} = calcImageInBox(photoSize.w, photoSize.h, width, height);
if(w == width || h == height) { if(w === width || h === height) {
break; break;
} }
} }
@ -189,47 +191,28 @@ export class AppPhotosManager {
return thumb.url ?? (defineNotNumerableProperties(thumb, ['url']), thumb.url = this.getPreviewURLFromBytes(thumb.bytes, isSticker)); return thumb.url ?? (defineNotNumerableProperties(thumb, ['url']), thumb.url = this.getPreviewURLFromBytes(thumb.bytes, isSticker));
} }
public setAttachmentPreview(bytes: Uint8Array | number[], element: HTMLElement | SVGForeignObjectElement, isSticker = false, background = false) { public getImageFromStrippedThumb(thumb: PhotoSize.photoCachedSize | PhotoSize.photoStrippedSize) {
let url = this.getPreviewURLFromBytes(bytes, isSticker); const url = this.getPreviewURLFromThumb(thumb, false);
if(background) { const image = new Image();
let img = new Image(); image.classList.add('thumbnail');
img.src = url;
img.addEventListener('load', () => { const loadPromise = blur(url).then(url => {
element.style.backgroundImage = 'url(' + url + ')'; return new Promise<any>((resolve) => {
renderImageFromUrl(image, url, resolve);
});
}); });
return element; return {image, loadPromise};
} else {
if(element instanceof HTMLImageElement) {
element.src = url;
return element;
} else {
let img = new Image();
img.src = url;
element.append(img);
return img;
}
}
} }
public setAttachmentSize(photo: MyPhoto | MyDocument, element: HTMLElement | SVGForeignObjectElement, boxWidth: number, boxHeight: number, isSticker = false, dontRenderPreview = false) { public setAttachmentSize(photo: MyPhoto | MyDocument, element: HTMLElement | SVGForeignObjectElement, boxWidth: number, boxHeight: number) {
const photoSize = this.choosePhotoSize(photo, boxWidth, boxHeight); const photoSize = this.choosePhotoSize(photo, boxWidth, boxHeight);
//console.log('setAttachmentSize', photo, photo.sizes[0].bytes, div); //console.log('setAttachmentSize', photo, photo.sizes[0].bytes, div);
const sizes = (photo as MyPhoto).sizes || (photo as MyDocument).thumbs;
const thumb = sizes?.length ? sizes[0] : null;
if(thumb && ('bytes' in thumb)) {
if((!photo.downloaded || (photo as MyDocument).type == 'video' || (photo as MyDocument).type == 'gif') && !isSticker && !dontRenderPreview) {
this.setAttachmentPreview(thumb.bytes, element, isSticker);
}
}
let width: number; let width: number;
let height: number; let height: number;
if(photo._ == 'document') { if(photo._ === 'document') {
width = photo.w || 512; width = photo.w || 512;
height = photo.h || 512; height = photo.h || 512;
} else { } else {
@ -251,16 +234,35 @@ export class AppPhotosManager {
return photoSize; return photoSize;
} }
public getPhotoDownloadOptions(photo: MyPhoto | MyDocument, photoSize: PhotoSize, queueId?: number) { public getStrippedThumbIfNeeded(photo: MyPhoto | MyDocument): ReturnType<AppPhotosManager['getImageFromStrippedThumb']> {
const isMyDocument = photo._ == 'document'; if(!photo.downloaded || (photo as MyDocument).type === 'video' || (photo as MyDocument).type === 'gif') {
if(photo._ === 'document') {
const cacheContext = this.getCacheContext(photo);
if(cacheContext.downloaded) {
return null;
}
}
if(!photoSize || photoSize._ == 'photoSizeEmpty') { const sizes = (photo as MyPhoto).sizes || (photo as MyDocument).thumbs;
const thumb = sizes?.length ? sizes[0] : null;
if(thumb && ('bytes' in thumb)) {
return appPhotosManager.getImageFromStrippedThumb(thumb as any);
}
}
return null;
}
public getPhotoDownloadOptions(photo: MyPhoto | MyDocument, photoSize: PhotoSize, queueId?: number) {
const isMyDocument = photo._ === 'document';
if(!photoSize || photoSize._ === 'photoSizeEmpty') {
//console.error('no photoSize by photo:', photo); //console.error('no photoSize by photo:', photo);
throw new Error('photoSizeEmpty!'); throw new Error('photoSizeEmpty!');
} }
// maybe it's a thumb // maybe it's a thumb
const isPhoto = (photoSize._ == 'photoSize' || photoSize._ == 'photoSizeProgressive') && photo.access_hash && photo.file_reference; const isPhoto = (photoSize._ === 'photoSize' || photoSize._ === 'photoSizeProgressive') && photo.access_hash && photo.file_reference;
const location: InputFileLocation.inputPhotoFileLocation | InputFileLocation.inputDocumentFileLocation | FileLocation = isPhoto ? { const location: InputFileLocation.inputPhotoFileLocation | InputFileLocation.inputDocumentFileLocation | FileLocation = isPhoto ? {
_: isMyDocument ? 'inputDocumentFileLocation' : 'inputPhotoFileLocation', _: isMyDocument ? 'inputDocumentFileLocation' : 'inputPhotoFileLocation',
id: photo.id, id: photo.id,
@ -278,11 +280,25 @@ export class AppPhotosManager {
return {url: getFileURL('photo', downloadOptions), location: downloadOptions.location}; return {url: getFileURL('photo', downloadOptions), location: downloadOptions.location};
} */ } */
public isDownloaded(media: any) {
const isPhoto = media._ === 'photo';
const photo = isPhoto ? this.getPhoto(media.id) : null;
let isDownloaded: boolean;
if(photo) {
isDownloaded = photo.downloaded > 0;
} else {
const cachedThumb = this.getDocumentCachedThumb(media.id);
isDownloaded = cachedThumb?.downloaded > 0;
}
return isDownloaded;
}
public preloadPhoto(photoId: any, photoSize?: PhotoSize, queueId?: number): CancellablePromise<Blob> { public preloadPhoto(photoId: any, photoSize?: PhotoSize, queueId?: number): CancellablePromise<Blob> {
const photo = this.getPhoto(photoId); const photo = this.getPhoto(photoId);
// @ts-ignore // @ts-ignore
if(!photo || photo._ == 'photoEmpty') { if(!photo || photo._ === 'photoEmpty') {
throw new Error('preloadPhoto photoEmpty!'); throw new Error('preloadPhoto photoEmpty!');
} }
@ -325,7 +341,7 @@ export class AppPhotosManager {
} }
public getCacheContext(photo: any): DocumentCacheThumb { public getCacheContext(photo: any): DocumentCacheThumb {
return photo._ == 'document' ? this.getDocumentCachedThumb(photo.id) : photo; return photo._ === 'document' ? this.getDocumentCachedThumb(photo.id) : photo;
} }
public getDocumentCachedThumb(docId: string) { public getDocumentCachedThumb(docId: string) {
@ -351,7 +367,7 @@ export class AppPhotosManager {
public savePhotoFile(photo: MyPhoto | MyDocument, queueId?: number) { public savePhotoFile(photo: MyPhoto | MyDocument, queueId?: number) {
const fullPhotoSize = this.choosePhotoSize(photo, 0xFFFF, 0xFFFF); const fullPhotoSize = this.choosePhotoSize(photo, 0xFFFF, 0xFFFF);
if(!(fullPhotoSize._ == 'photoSize' || fullPhotoSize._ == 'photoSizeProgressive')) { if(!(fullPhotoSize._ === 'photoSize' || fullPhotoSize._ === 'photoSizeProgressive')) {
return; return;
} }

View File

@ -1,18 +1,51 @@
avatar-element { avatar-element {
--size: 54px; --size: 54px;
--multiplier: 1; --multiplier: 1;
--color-top: var(--peer-avatar-blue-top);
--color-bottom: var(--peer-avatar-blue-bottom);
color: #fff; color: #fff;
width: var(--size); width: var(--size);
height: var(--size); height: var(--size);
line-height: var(--size); line-height: var(--size);
border-radius: 50%; border-radius: 50%;
background-color: $color-blue; background: linear-gradient(var(--color-top), var(--color-bottom));
text-align: center; text-align: center;
font-size: calc(1.25rem / var(--multiplier)); font-size: calc(1.25rem / var(--multiplier));
/* overflow: hidden; */ /* overflow: hidden; */
position: relative; position: relative;
user-select: none; user-select: none;
text-transform: uppercase; text-transform: uppercase;
font-weight: 700;
&[data-color="red"] {
--color-top: var(--peer-avatar-red-top);
--color-bottom: var(--peer-avatar-red-bottom);
}
&[data-color="orange"] {
--color-top: var(--peer-avatar-orange-top);
--color-bottom: var(--peer-avatar-orange-bottom);
}
&[data-color="violet"] {
--color-top: var(--peer-avatar-violet-top);
--color-bottom: var(--peer-avatar-violet-bottom);
}
&[data-color="green"] {
--color-top: var(--peer-avatar-green-top);
--color-bottom: var(--peer-avatar-green-bottom);
}
&[data-color="cyan"] {
--color-top: var(--peer-avatar-cyan-top);
--color-bottom: var(--peer-avatar-cyan-bottom);
}
&[data-color="pink"] {
--color-top: var(--peer-avatar-pink-top);
--color-bottom: var(--peer-avatar-pink-bottom);
}
&.tgico-savedmessages:before { &.tgico-savedmessages:before {
font-size: calc(25px / var(--multiplier)); font-size: calc(25px / var(--multiplier));

View File

@ -1039,7 +1039,7 @@ $chat-helper-size: 39px;
} }
&-container .preloader-circular { &-container .preloader-circular {
background-color: var(--message-time-background); background-color: rgba(0, 0, 0, .3);
} }
} }

View File

@ -597,16 +597,10 @@ $bubble-margin: .25rem;
display: flex; // lol display: flex; // lol
justify-content: center; justify-content: center;
position: relative; position: relative;
cursor: pointer;
img, video { img, video {
max-width: 100%; max-width: 100%;
cursor: pointer;
opacity: 1;
transition: opacity .3s ease;
body.animation-level-0 & {
transition: none;
}
} }
.download { .download {
@ -633,11 +627,11 @@ $bubble-margin: .25rem;
display: none; display: none;
} }
} }
}
.preloader-container { .preloader-container {
z-index: 1; z-index: 1;
} }
}
&:not(.sticker) { &:not(.sticker) {
.attachment { .attachment {

View File

@ -28,10 +28,24 @@
left: 0; left: 0;
right: 0; right: 0;
margin: auto; margin: auto;
width: 50px; width: 54px;
height: 50px; height: 54px;
display: flex; display: flex;
/* cursor: pointer; */ /* cursor: pointer; */
opacity: 0;
transform: scale(0);
body:not(.animation-level-0) & {
transition: opacity .2s ease-in-out, transform .2s ease-in-out;
}
&.is-visible {
&:not(.backwards) {
opacity: 1;
transform: scale(1);
}
}
} }
} }
@ -52,12 +66,12 @@
} }
.preloader-path-new { .preloader-path-new {
stroke-dasharray: 5, 200; stroke-dasharray: 5, 149.82;
stroke-dashoffset: 0; stroke-dashoffset: 0;
transition: stroke-dasharray 400ms ease-in-out; transition: stroke-dasharray 400ms ease-in-out;
stroke-linecap: round; stroke-linecap: round;
stroke: white; stroke: white;
stroke-width: 1.5; stroke-width: 2;
} }
&.preloader-swing { &.preloader-swing {
@ -68,9 +82,7 @@
} }
.preloader-path-new { .preloader-path-new {
stroke-dasharray: 1, 200; animation: dashNew 1.5s ease-in-out infinite;
stroke-dashoffset: 0;
animation: dashNew 1.5s ease-in-out infinite/* , color 6s ease-in-out infinite */;
} }
} }
@ -111,7 +123,7 @@
background-color: #fff; background-color: #fff;
left: 50%; left: 50%;
top: 50%; top: 50%;
transform: translate3d(-50%,-50%,0); transform: translate3d(-50%, -50%, 0);
} }
} }
} }
@ -139,16 +151,16 @@
@keyframes dashNew { @keyframes dashNew {
0% { 0% {
stroke-dasharray: 1, 200; stroke-dasharray: 1, 149.82; // 149.82 = getTotalLength
stroke-dashoffset: 0; stroke-dashoffset: 0;
} }
50% { 50% {
stroke-dasharray: 89, 200; stroke-dasharray: 112.36, 149.82; // 112.36 = 149.82 * .75
stroke-dashoffset: -35px; stroke-dashoffset: -38; // bruted
} }
100% { 100% {
stroke-dasharray: 89, 200; stroke-dasharray: 112.36, 149.82;
stroke-dashoffset: -286%; stroke-dashoffset: -149.82; // totalLength
} }
} }

View File

@ -361,15 +361,6 @@
.grid-item { .grid-item {
overflow: hidden; overflow: hidden;
&-media {
opacity: 1;
transition: opacity .2s ease;
html:not(.is-mac) &.thumbnail {
filter: blur(7px);
}
}
} }
/* span.video-play { /* span.video-play {

View File

@ -96,6 +96,22 @@ $chat-padding-handhelds: .5rem;
--messages-secondary-text-size: calc(var(--messages-text-size) - 1px); --messages-secondary-text-size: calc(var(--messages-text-size) - 1px);
--esg-sticker-size: 80px; --esg-sticker-size: 80px;
// https://github.com/overtake/TelegramSwift/blob/5cc7d2475fe4738a6aa0486c23eaf80a89d33b97/submodules/TGUIKit/TGUIKit/PresentationTheme.swift#L2054
--peer-avatar-red-top: #ff885e;
--peer-avatar-red-bottom: #ff516a;
--peer-avatar-orange-top: #ffcd6a;
--peer-avatar-orange-bottom: #ffa85c;
--peer-avatar-violet-top: #82b1ff;
--peer-avatar-violet-bottom: #665fff;
--peer-avatar-green-top: #a0de7e;
--peer-avatar-green-bottom: #54cb68;
--peer-avatar-cyan-top: #53edd6;
--peer-avatar-cyan-bottom: #28c9b7;
--peer-avatar-blue-top: #72d5fd;
--peer-avatar-blue-bottom: #2a9ef1;
--peer-avatar-pink-top: #e0a2f3;
--peer-avatar-pink-bottom: #d669ed;
@include respond-to(handhelds) { @include respond-to(handhelds) {
--right-column-width: 100vw; --right-column-width: 100vw;
--esg-sticker-size: 68px; --esg-sticker-size: 68px;
@ -859,7 +875,6 @@ img.emoji {
top: 0; top: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
-o-object-fit: cover;
object-fit: cover; object-fit: cover;
} }
} }
@ -1161,3 +1176,19 @@ middle-ellipsis-element {
background-color: var(--color-gray-hover); background-color: var(--color-gray-hover);
} }
} }
.media-photo, .media-video {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
&.fade-in {
animation: fade-in-opacity .2s ease-in-out forwards;
}
}
.media-video {
z-index: 1; // * overflow media-photo
}

160
src/vendor/fastBlur.js vendored Normal file
View File

@ -0,0 +1,160 @@
/*
Superfast Blur - a fast Box Blur For Canvas
Version: 0.5
Author: Mario Klingemann
Contact: mario@quasimondo.com
Website: http://www.quasimondo.com/BoxBlurForCanvas
Twitter: @quasimondo
In case you find this class useful - especially in commercial projects -
I am not totally unhappy for a small donation to my PayPal account
mario@quasimondo.de
Or support me on flattr:
https://flattr.com/thing/140066/Superfast-Blur-a-pretty-fast-Box-Blur-Effect-for-CanvasJavascript
Copyright (c) 2011 Mario Klingemann
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
// eslint-disable-next-line max-len
const mul_table = [1, 57, 41, 21, 203, 34, 97, 73, 227, 91, 149, 62, 105, 45, 39, 137, 241, 107, 3, 173, 39, 71, 65, 238, 219, 101, 187, 87, 81, 151, 141, 133, 249, 117, 221, 209, 197, 187, 177, 169, 5, 153, 73, 139, 133, 127, 243, 233, 223, 107, 103, 99, 191, 23, 177, 171, 165, 159, 77, 149, 9, 139, 135, 131, 253, 245, 119, 231, 224, 109, 211, 103, 25, 195, 189, 23, 45, 175, 171, 83, 81, 79, 155, 151, 147, 9, 141, 137, 67, 131, 129, 251, 123, 30, 235, 115, 113, 221, 217, 53, 13, 51, 50, 49, 193, 189, 185, 91, 179, 175, 43, 169, 83, 163, 5, 79, 155, 19, 75, 147, 145, 143, 35, 69, 17, 67, 33, 65, 255, 251, 247, 243, 239, 59, 29, 229, 113, 111, 219, 27, 213, 105, 207, 51, 201, 199, 49, 193, 191, 47, 93, 183, 181, 179, 11, 87, 43, 85, 167, 165, 163, 161, 159, 157, 155, 77, 19, 75, 37, 73, 145, 143, 141, 35, 138, 137, 135, 67, 33, 131, 129, 255, 63, 250, 247, 61, 121, 239, 237, 117, 29, 229, 227, 225, 111, 55, 109, 216, 213, 211, 209, 207, 205, 203, 201, 199, 197, 195, 193, 48, 190, 47, 93, 185, 183, 181, 179, 178, 176, 175, 173, 171, 85, 21, 167, 165, 41, 163, 161, 5, 79, 157, 78, 154, 153, 19, 75, 149, 74, 147, 73, 144, 143, 71, 141, 140, 139, 137, 17, 135, 134, 133, 66, 131, 65, 129, 1];
// eslint-disable-next-line max-len
const shg_table = [0, 9, 10, 10, 14, 12, 14, 14, 16, 15, 16, 15, 16, 15, 15, 17, 18, 17, 12, 18, 16, 17, 17, 19, 19, 18, 19, 18, 18, 19, 19, 19, 20, 19, 20, 20, 20, 20, 20, 20, 15, 20, 19, 20, 20, 20, 21, 21, 21, 20, 20, 20, 21, 18, 21, 21, 21, 21, 20, 21, 17, 21, 21, 21, 22, 22, 21, 22, 22, 21, 22, 21, 19, 22, 22, 19, 20, 22, 22, 21, 21, 21, 22, 22, 22, 18, 22, 22, 21, 22, 22, 23, 22, 20, 23, 22, 22, 23, 23, 21, 19, 21, 21, 21, 23, 23, 23, 22, 23, 23, 21, 23, 22, 23, 18, 22, 23, 20, 22, 23, 23, 23, 21, 22, 20, 22, 21, 22, 24, 24, 24, 24, 24, 22, 21, 24, 23, 23, 24, 21, 24, 23, 24, 22, 24, 24, 22, 24, 24, 22, 23, 24, 24, 24, 20, 23, 22, 23, 24, 24, 24, 24, 24, 24, 24, 23, 21, 23, 22, 23, 24, 24, 24, 22, 24, 24, 24, 23, 22, 24, 24, 25, 23, 25, 25, 23, 24, 25, 25, 24, 22, 25, 25, 25, 24, 23, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 23, 25, 23, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 24, 22, 25, 25, 23, 25, 25, 20, 24, 25, 24, 25, 25, 22, 24, 25, 24, 25, 24, 25, 25, 24, 25, 25, 25, 25, 22, 25, 25, 25, 24, 25, 24, 25, 18];
export default function boxBlurCanvasRGB(context, top_x, top_y, width, height, radius, iterations) {
if (Number.isNaN(radius) || radius < 1) return;
radius |= 0;
if (Number.isNaN(iterations)) iterations = 1;
iterations |= 0;
if (iterations > 3) iterations = 3;
if (iterations < 1) iterations = 1;
const imageData = context.getImageData(top_x, top_y, width, height);
const pixels = imageData.data;
let rsum;
let gsum;
let bsum;
let x;
let y;
let i;
let p;
let p1;
let p2;
let yp;
let yi;
let yw;
let wm = width - 1;
let hm = height - 1;
let rad1 = radius + 1;
let r = [];
let g = [];
let b = [];
let mul_sum = mul_table[radius];
let shg_sum = shg_table[radius];
let vmin = [];
let vmax = [];
while (iterations-- > 0) {
yw = yi = 0;
for (y = 0; y < height; y++) {
rsum = pixels[yw] * rad1;
gsum = pixels[yw + 1] * rad1;
bsum = pixels[yw + 2] * rad1;
for (i = 1; i <= radius; i++) {
p = yw + (((i > wm ? wm : i)) << 2);
rsum += pixels[p++];
gsum += pixels[p++];
bsum += pixels[p++];
}
for (x = 0; x < width; x++) {
r[yi] = rsum;
g[yi] = gsum;
b[yi] = bsum;
if (y == 0) {
vmin[x] = ((p = x + rad1) < wm ? p : wm) << 2;
vmax[x] = ((p = x - radius) > 0 ? p << 2 : 0);
}
p1 = yw + vmin[x];
p2 = yw + vmax[x];
rsum += pixels[p1++] - pixels[p2++];
gsum += pixels[p1++] - pixels[p2++];
bsum += pixels[p1++] - pixels[p2++];
yi++;
}
yw += (width << 2);
}
for (x = 0; x < width; x++) {
yp = x;
rsum = r[yp] * rad1;
gsum = g[yp] * rad1;
bsum = b[yp] * rad1;
for (i = 1; i <= radius; i++) {
yp += (i > hm ? 0 : width);
rsum += r[yp];
gsum += g[yp];
bsum += b[yp];
}
yi = x << 2;
for (y = 0; y < height; y++) {
pixels[yi] = (rsum * mul_sum) >>> shg_sum;
pixels[yi + 1] = (gsum * mul_sum) >>> shg_sum;
pixels[yi + 2] = (bsum * mul_sum) >>> shg_sum;
if (x == 0) {
vmin[y] = ((p = y + rad1) < hm ? p : hm) * width;
vmax[y] = ((p = y - radius) > 0 ? p * width : 0);
}
p1 = x + vmin[y];
p2 = x + vmax[y];
rsum += r[p1] - r[p2];
gsum += g[p1] - g[p2];
bsum += b[p1] - b[p2];
yi += width << 2;
}
}
}
context.putImageData(imageData, top_x, top_y);
}