import appPhotosManager from '../lib/appManagers/appPhotosManager'; //import CryptoWorker from '../lib/crypto/cryptoworker'; import apiManager from '../lib/mtproto/mtprotoworker'; import LottieLoader from '../lib/lottieLoader'; import appStickersManager from "../lib/appManagers/appStickersManager"; import appDocsManager from "../lib/appManagers/appDocsManager"; import { formatBytes, getEmojiToneIndex } from "../lib/utils"; import ProgressivePreloader from './preloader'; import LazyLoadQueue from './lazyLoadQueue'; import apiFileManager from '../lib/mtproto/apiFileManager'; import VideoPlayer, { MediaProgressLine } from '../lib/mediaPlayer'; import { RichTextProcessor } from '../lib/richtextprocessor'; import { CancellablePromise } from '../lib/polyfill'; import { renderImageFromUrl } from './misc'; import appMessagesManager from '../lib/appManagers/appMessagesManager'; import { Layouter, RectPart } from './groupedLayout'; import PollElement from './poll'; import appWebpManager from '../lib/appManagers/appWebpManager'; export type MTDocument = { _: 'document' | 'documentEmpty', pFlags: any, flags: number, id: string, access_hash: string, file_reference: Uint8Array | number[], date: number, mime_type: string, size: number, thumbs: MTPhotoSize[], dc_id: number, attributes: any[], thumb?: MTPhotoSize, type?: string, h?: number, w?: number, file_name?: string, file?: File, duration?: number, downloaded?: boolean, url?: string, audioTitle?: string, audioPerformer?: string, sticker?: number, stickerEmoji?: string, stickerEmojiRaw?: string, stickerSetInput?: any, stickerThumbConverted?: true, animated?: boolean }; export type MTPhotoSize = { _: string, w?: number, h?: number, size?: number, type?: string, // i, m, x, y, w by asc location?: any, bytes?: Uint8Array // if type == 'i' }; export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue}: { doc: MTDocument, container: HTMLDivElement, message: any, boxWidth: number, boxHeight: number, withTail?: boolean, isOut?: boolean, middleware: () => boolean, lazyLoadQueue: LazyLoadQueue }) { let img: HTMLImageElement; if(withTail) { img = wrapMediaWithTail(doc, message, container, boxWidth, boxHeight, isOut); } else { if(!boxWidth && !boxHeight) { // album let sizes = doc.thumbs; if(!doc.downloaded && sizes && sizes[0].bytes) { appPhotosManager.setAttachmentPreview(sizes[0].bytes, container, false); } } else { if(!container.firstElementChild || (container.firstElementChild.tagName != 'IMG' && container.firstElementChild.tagName != 'VIDEO')) { appPhotosManager.setAttachmentSize(doc, container, boxWidth, boxHeight); } } img = container.lastElementChild as HTMLImageElement; if(!img || img.tagName != 'IMG') { container.append(img = new Image()); } } let video = document.createElement('video'); let source = document.createElement('source'); video.append(source); if(withTail) { let foreignObject = img.parentElement; video.width = +foreignObject.getAttributeNS(null, 'width'); video.height = +foreignObject.getAttributeNS(null, 'height'); foreignObject.append(video); } else { container.append(video); } let span: HTMLSpanElement, spanPlay: HTMLSpanElement; if(doc.type != 'round') { span = document.createElement('span'); span.classList.add('video-time'); container.append(span); if(doc.type != 'gif') { span.innerText = (doc.duration + '').toHHMMSS(false); spanPlay = document.createElement('span'); spanPlay.classList.add('video-play', 'tgico-largeplay', 'btn-circle', 'position-center'); container.append(spanPlay); } else { span.innerText = 'GIF'; } } let loadVideo = async() => { if(message.media.preloader) { // means upload (message.media.preloader as ProgressivePreloader).attach(container, undefined, undefined, false); } else if(!doc.downloaded) { let preloader = new ProgressivePreloader(container, true); let promise = appDocsManager.downloadDoc(doc); preloader.attach(container, true, promise, false); await promise; } if(middleware && !middleware()) { return; } console.log('loaded doc:', doc, doc.url, container); renderImageFromUrl(source, doc.url); source.type = doc.mime_type; video.append(source); if(img && img.parentElement) { img.remove(); } if(doc.type == 'gif') { video.autoplay = true; video.loop = true; video.play(); } else if(doc.type == 'round') { //video.dataset.ckin = doc.type == 'round' ? 'circle' : 'default'; video.dataset.ckin = 'circle'; video.dataset.overlay = '1'; let player = new VideoPlayer(video/* , doc.type != 'round' */); } }; if(doc.size >= 20e6 && !doc.downloaded) { let downloadDiv = document.createElement('div'); downloadDiv.classList.add('download'); let span = document.createElement('span'); span.classList.add('btn-circle', 'tgico-download'); downloadDiv.append(span); downloadDiv.addEventListener('click', () => { downloadDiv.remove(); loadVideo(); }); container.prepend(downloadDiv); return; } return doc.downloaded ? loadVideo() : lazyLoadQueue.push({div: container, load: loadVideo, wasSeen: true}); } let formatDate = (timestamp: number) => { const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const date = new Date(timestamp * 1000); return months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear() + ' at ' + date.getHours() + ':' + ('0' + date.getMinutes()).slice(-2); }; export function wrapDocument(doc: MTDocument, withTime = false, uploading = false): HTMLDivElement { if(doc.type == 'voice') { return wrapVoiceMessage(doc, uploading); } else if(doc.type == 'audio') { return wrapAudio(doc, withTime); } let extSplitted = doc.file_name ? doc.file_name.split('.') : ''; let ext = ''; ext = extSplitted.length > 1 && Array.isArray(extSplitted) ? extSplitted.pop().toLowerCase() : 'file'; let docDiv = document.createElement('div'); docDiv.classList.add('document', `ext-${ext}`); let ext2 = ext; if(doc.type == 'photo') { docDiv.classList.add('photo'); ext2 = ``; } let fileName = doc.file_name || 'Unknown.file'; let size = formatBytes(doc.size); if(withTime) { size += ' · ' + formatDate(doc.date); } docDiv.innerHTML = `
${ext2}
${!uploading ? `
` : ''}
${fileName}
${size}
`; if(!uploading) { let downloadDiv = docDiv.querySelector('.document-download') as HTMLDivElement; let preloader: ProgressivePreloader; let promise: CancellablePromise; docDiv.addEventListener('click', () => { if(!promise) { if(downloadDiv.classList.contains('downloading')) { return; // means not ready yet } if(!preloader) { preloader = new ProgressivePreloader(null, true); } appDocsManager.saveDocFile(doc.id).then(res => { promise = res.promise; preloader.attach(downloadDiv, true, promise); promise.then(() => { downloadDiv.classList.remove('downloading'); downloadDiv.remove(); }); }) downloadDiv.classList.add('downloading'); } else { downloadDiv.classList.remove('downloading'); promise = null; } }); } return docDiv; } export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement { let div = document.createElement('div'); div.classList.add('audio'); console.log('wrapAudio doc:', doc); let durationStr = String(doc.duration | 0).toHHMMSS(true); let title = doc.audioTitle || doc.file_name; let subtitle = doc.audioPerformer ? RichTextProcessor.wrapPlainText(doc.audioPerformer) : ''; /* let durationStr = '3:24'; let title = 'Million Telegrams'; let subtitle = 'Best Artist'; */ if(withTime) { subtitle += (subtitle ? ' · ' : '') + formatDate(doc.date); } else if(!subtitle) { subtitle = 'Unknown Artist'; } div.innerHTML = `
${title}
${subtitle}
${durationStr}
`; /* if(!subtitle) { div.classList.add('audio-no-subtitle'); } */ //////console.log('wrapping audio', doc, doc.attributes[0].waveform); let timeDiv = div.lastElementChild as HTMLDivElement; let downloadDiv = div.querySelector('.audio-download') as HTMLDivElement; let preloader: ProgressivePreloader; let promise: CancellablePromise; let progress: MediaProgressLine; let onClick = () => { if(!promise) { if(!preloader) { preloader = new ProgressivePreloader(null, true); } promise = appDocsManager.downloadDoc(doc.id); preloader.attach(downloadDiv, true, promise); promise.then(blob => { downloadDiv.classList.remove('downloading'); downloadDiv.remove(); let audio = document.createElement('audio'); let source = document.createElement('source'); source.src = doc.url; source.type = doc.mime_type; audio.volume = 1; progress = new MediaProgressLine(audio); div.removeEventListener('click', onClick); let toggle = div.querySelector('.audio-toggle') as HTMLDivElement; let subtitle = div.querySelector('.audio-subtitle') as HTMLDivElement; let launched = false; toggle.addEventListener('click', () => { if(!launched) { div.classList.add('audio-show-progress'); launched = true; } subtitle.innerHTML = ''; subtitle.append(progress.container); if(audio.paused) { if(lastAudioToggle && lastAudioToggle.classList.contains('tgico-largepause')) { lastAudioToggle.click(); } audio.currentTime = 0; audio.play(); lastAudioToggle = toggle; toggle.classList.remove('tgico-largeplay'); toggle.classList.add('tgico-largepause'); } else { audio.pause(); toggle.classList.add('tgico-largeplay'); toggle.classList.remove('tgico-largepause'); } }); audio.addEventListener('ended', () => { toggle.classList.add('tgico-largeplay'); toggle.classList.remove('tgico-largepause'); timeDiv.innerText = String(audio.currentTime | 0).toHHMMSS(true); }); audio.style.display = 'none'; audio.append(source); div.append(audio); }); downloadDiv.classList.add('downloading'); } else { downloadDiv.classList.remove('downloading'); promise.cancel(); promise = null; } }; div.addEventListener('click', onClick); div.click(); return div; } // https://github.com/LonamiWebs/Telethon/blob/4393ec0b83d511b6a20d8a20334138730f084375/telethon/utils.py#L1285 function decodeWaveform(waveform: Uint8Array | number[]) { if(!(waveform instanceof Uint8Array)) { waveform = new Uint8Array(waveform); } var bitCount = waveform.length * 8; var valueCount = bitCount / 5 | 0; if(!valueCount) { return new Uint8Array([]); } var dataView = new DataView(waveform.buffer); var result = new Uint8Array(valueCount); for(var i = 0; i < valueCount; i++) { var byteIndex = i * 5 / 8 | 0; var bitShift = i * 5 % 8; var value = dataView.getUint16(byteIndex, true); result[i] = (value >> bitShift) & 0b00011111; } /* var byteIndex = (valueCount - 1) / 8 | 0; var bitShift = (valueCount - 1) % 8; if(byteIndex == waveform.length - 1) { var value = waveform[byteIndex]; } else { var value = dataView.getUint16(byteIndex, true); } console.log('decoded waveform, setting last value:', value, byteIndex, bitShift); result[valueCount - 1] = (value >> bitShift) & 0b00011111; */ return result; } let lastAudioToggle: HTMLDivElement = null; export function wrapVoiceMessage(doc: MTDocument, uploading = false): HTMLDivElement { let div = document.createElement('div'); div.classList.add('audio', 'is-voice'); let duration = doc.duration; let durationStr = String(duration | 0).toHHMMSS(true); div.innerHTML = `
${uploading ? '' : '
'}
${durationStr}
`; //////console.log('wrapping audio', doc, doc.attributes[0].waveform); let timeDiv = div.lastElementChild as HTMLDivElement; let downloadDiv = div.querySelector('.audio-download') as HTMLDivElement; let preloader: ProgressivePreloader; let promise: CancellablePromise; let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.classList.add('audio-waveform'); svg.setAttributeNS(null, 'width', '190'); svg.setAttributeNS(null, 'height', '23'); svg.setAttributeNS(null, 'viewBox', '0 0 190 23'); div.insertBefore(svg, div.lastElementChild); const barWidth = 2; const barMargin = 1; const barHeightMin = 2; const barHeightMax = 23; let waveform = doc.attributes[0].waveform || []; waveform = decodeWaveform(waveform.slice()); //console.log('decoded waveform:', waveform); const normValue = Math.max(...waveform); const wfSize = waveform.length ? waveform.length : 100; const availW = 190; const barCount = Math.min((availW / (barWidth + barMargin)) | 0, wfSize); let maxValue = 0; let maxDelta = barHeightMax - barHeightMin; let html = ''; for(let i = 0, barX = 0, sumI = 0; i < wfSize; ++i) { const value = waveform[i] || 0; if((sumI + barCount) >= wfSize) { // draw bar sumI = sumI + barCount - wfSize; if(sumI < (barCount + 1) / 2) { if(maxValue < value) maxValue = value; } const bar_value = Math.max(((maxValue * maxDelta) + ((normValue + 1) / 2)) / (normValue + 1), barHeightMin); let h = ` `; html += h; /* if(barX >= activeW) { p.fillRect(nameleft + barX, bottom - bar_value, barWidth, barHeightMin + bar_value, inactive); } else if (barX + barWidth <= activeW) { p.fillRect(nameleft + barX, bottom - bar_value, barWidth, barHeightMin + bar_value, active); } else { p.fillRect(nameleft + barX, bottom - bar_value, activeW - barX, barHeightMin + bar_value, active); p.fillRect(nameleft + activeW, bottom - bar_value, barWidth - (activeW - barX), barHeightMin + bar_value, inactive); } */ barX += barWidth + barMargin; if(sumI < (barCount + 1) / 2) { maxValue = 0; } else { maxValue = value; } } else { if(maxValue < value) maxValue = value; sumI += barCount; } } svg.insertAdjacentHTML('beforeend', html); /* let index = 0; let skipped = 0; let h = ''; for(let uint8 of wave) { if(index > 0 && index % 4 == 0) { ++index; ++skipped; continue; } //let percents = uint8 / 255; let percents = uint8 / 31; let height = 23 * percents; if(height < 2) { height = 2; } h += ` `; ++index; } svg.insertAdjacentHTML('beforeend', h); */ let progress = div.querySelector('.audio-waveform') as HTMLDivElement; let onLoad = () => { let audio = document.createElement('audio'); let source = document.createElement('source'); source.src = doc.url; //source.type = doc.mime_type; source.type = 'audio/wav'; audio.volume = 1; let toggle = div.querySelector('.audio-toggle') as HTMLDivElement; let interval = 0; let lastIndex = 0; toggle.addEventListener('click', () => { if(audio.paused) { if(lastAudioToggle && lastAudioToggle.classList.contains('tgico-largepause')) { lastAudioToggle.click(); } audio.currentTime = 0; audio.play(); lastAudioToggle = toggle; toggle.classList.remove('tgico-largeplay'); toggle.classList.add('tgico-largepause'); (Array.from(svg.children) as HTMLElement[]).forEach(node => node.classList.remove('active')); interval = setInterval(() => { if(lastIndex > svg.childElementCount || isNaN(audio.duration)) { clearInterval(interval); return; } timeDiv.innerText = String(audio.currentTime | 0).toHHMMSS(true); lastIndex = Math.round(audio.currentTime / audio.duration * 47); //svg.children[lastIndex].setAttributeNS(null, 'fill', '#000'); //svg.children[lastIndex].classList.add('active'); #Иногда пропускает полоски.. (Array.from(svg.children) as HTMLElement[]).slice(0,lastIndex+1).forEach(node => node.classList.add('active')); //++lastIndex; //console.log('lastIndex:', lastIndex, audio.currentTime); //}, duration * 1000 / svg.childElementCount | 0/* 63 * duration / 10 */); }, 20); } else { audio.pause(); toggle.classList.add('tgico-largeplay'); toggle.classList.remove('tgico-largepause'); clearInterval(interval); } }); audio.addEventListener('ended', () => { toggle.classList.add('tgico-largeplay'); toggle.classList.remove('tgico-largepause'); clearInterval(interval); (Array.from(svg.children) as HTMLElement[]).forEach(node => node.classList.remove('active')); timeDiv.innerText = String(audio.currentTime | 0).toHHMMSS(true); }); let mousedown = false, mousemove = false; progress.addEventListener('mouseleave', (e) => { if(mousedown) { audio.play(); mousedown = false; } mousemove = false; }) progress.addEventListener('mousemove', (e) => { mousemove = true; if(mousedown) scrub(e, audio, progress); }); progress.addEventListener('mousedown', (e) => { e.preventDefault(); if(!audio.paused) { audio.pause(); scrub(e, audio, progress); mousedown = true; } }); progress.addEventListener('mouseup', (e) => { if (mousemove && mousedown) { audio.play(); mousedown = false; } }); progress.addEventListener('click', (e) => { if(!audio.paused) scrub(e, audio, progress); }); function scrub(e: MouseEvent, audio: HTMLAudioElement, progress: HTMLDivElement) { let scrubTime = e.offsetX / 190 /* width */ * audio.duration; (Array.from(svg.children) as HTMLElement[]).forEach(node => node.classList.remove('active')); lastIndex = Math.round(scrubTime / audio.duration * 47); (Array.from(svg.children) as HTMLElement[]).slice(0,lastIndex+1).forEach(node => node.classList.add('active')); audio.currentTime = scrubTime; } audio.style.display = 'none'; audio.append(source); div.append(audio); }; if(!uploading) { let onClick = () => { if(!promise) { if(!preloader) { preloader = new ProgressivePreloader(null, true); } promise = appDocsManager.downloadDoc(doc.id); preloader.attach(downloadDiv, true, promise); promise.then(() => { downloadDiv.classList.remove('downloading'); downloadDiv.remove(); div.removeEventListener('click', onClick); onLoad(); }); downloadDiv.classList.add('downloading'); } else { downloadDiv.classList.remove('downloading'); promise.cancel(); promise = null; } }; div.addEventListener('click', onClick); div.click(); } else { onLoad(); } return div; } function wrapMediaWithTail(photo: any, message: {mid: number, message: string}, container: HTMLDivElement, boxWidth: number, boxHeight: number, isOut: boolean) { let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.classList.add('bubble__media-container', isOut ? 'is-out' : 'is-in'); let foreignObject = document.createElementNS("http://www.w3.org/2000/svg", 'foreignObject'); appPhotosManager.setAttachmentSize(photo._ == 'document' ? photo : photo.id, foreignObject, boxWidth, boxHeight); let width = +foreignObject.getAttributeNS(null, 'width'); let height = +foreignObject.getAttributeNS(null, 'height'); svg.setAttributeNS(null, 'width', '' + width); svg.setAttributeNS(null, 'height', '' + height); let clipID = 'clip' + message.mid; svg.dataset.clipID = clipID; let defs = document.createElementNS("http://www.w3.org/2000/svg", 'defs'); let clipPathHTML: string = ''; if(message.message) { //clipPathHTML += ``; } else { if(isOut) { clipPathHTML += ` `; } else { clipPathHTML += ` `; } } defs.innerHTML = `${clipPathHTML}`; container.style.width = parseInt(container.style.width) - 9 + 'px'; svg.append(defs, foreignObject); container.append(svg); let img = foreignObject.firstElementChild as HTMLImageElement; if(!img) { foreignObject.append(img = new Image()); } return img; } export function wrapPhoto(photoID: string, message: any, container: HTMLDivElement, boxWidth = 480, boxHeight = 480, withTail = true, isOut = false, lazyLoadQueue: LazyLoadQueue, middleware: () => boolean, size: MTPhotoSize = null) { let photo = appPhotosManager.getPhoto(photoID); let image: HTMLImageElement; if(withTail) { image = wrapMediaWithTail(photo, message, container, boxWidth, boxHeight, isOut); } else { if(size) { // album let sizes = photo.sizes; if(!photo.downloaded && sizes && sizes[0].bytes) { appPhotosManager.setAttachmentPreview(sizes[0].bytes, container, false); } } else if(boxWidth && boxHeight) { // means webpage's preview size = appPhotosManager.setAttachmentSize(photoID, container, boxWidth, boxHeight, false); } image = container.lastElementChild as HTMLImageElement; if(!image || image.tagName != 'IMG') { container.append(image = new Image()); } } //console.log('wrapPhoto downloaded:', photo, photo.downloaded, container); // так нельзя делать, потому что может быть загружен неправильный размер картинки /* if(photo.downloaded && photo.url) { renderImageFromUrl(image, photo.url); return; } */ let preloader: ProgressivePreloader; if(message.media.preloader) { // means upload message.media.preloader.attach(container); } else if(!photo.downloaded) { preloader = new ProgressivePreloader(container, false); } let load = () => { let promise = appPhotosManager.preloadPhoto(photoID, size); if(preloader) { preloader.attach(container, true, promise); } return promise.then(() => { if(middleware && !middleware()) return; renderImageFromUrl(image || container, photo.url); }); }; /////////console.log('wrapPhoto', load, container, image); return photo.downloaded ? load() : lazyLoadQueue.push({div: container, load: load, wasSeen: true}); } export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, onlyThumb, emoji}: { doc: MTDocument, div: HTMLDivElement, middleware?: () => boolean, lazyLoadQueue?: LazyLoadQueue, group?: string, play?: boolean, onlyThumb?: boolean, emoji?: string }) { let stickerType = doc.sticker; if(stickerType == 2 && !LottieLoader.loaded) { //LottieLoader.loadLottie(); LottieLoader.loadLottieWorkers(); } if(!stickerType) { console.error('wrong doc for wrapSticker!', doc); throw new Error('wrong doc for wrapSticker!'); } div.dataset.docID = doc.id; //console.log('wrap sticker', doc, div, onlyThumb); const toneIndex = emoji ? getEmojiToneIndex(emoji) : -1; if(doc.thumbs && !div.firstElementChild && (!doc.downloaded || stickerType == 2)) { let thumb = doc.thumbs[0]; //console.log('wrap sticker', thumb, div); if(thumb.bytes) { let img = new Image(); if(appWebpManager.isSupported() || doc.stickerThumbConverted) { renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true)); div.append(img); } else { appWebpManager.convertToPng(thumb.bytes).then(bytes => { if(middleware && !middleware()) return; thumb.bytes = bytes; doc.stickerThumbConverted = true; if(!div.childElementCount) { renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true)).then(() => { div.append(img); }); } }); } if(onlyThumb) { return Promise.resolve(); } } else if(!onlyThumb && stickerType == 2) { let img = new Image(); let load = () => appDocsManager.downloadDocThumb(doc, thumb.type).then(url => { if(!img.parentElement || (middleware && !middleware())) return; let promise = renderImageFromUrl(img, url); if(!downloaded) { promise.then(() => { div.append(img); }); } }); let downloaded = appDocsManager.hasDownloadedThumb(doc.id, thumb.type); if(downloaded) { div.append(img); } lazyLoadQueue && !downloaded ? lazyLoadQueue.push({div, load, wasSeen: group == 'chat'}) : load(); } } if(onlyThumb && doc.thumbs) { // for sticker panel let thumb = doc.thumbs[0]; let load = () => appDocsManager.downloadDocThumb(doc, thumb.type).then(url => { let img = new Image(); renderImageFromUrl(img, url).then(() => { if(middleware && !middleware()) return; div.append(img); }); }); return lazyLoadQueue ? (lazyLoadQueue.push({div, load}), Promise.resolve()) : load(); } let downloaded = doc.downloaded; let load = () => appDocsManager.downloadDoc(doc.id).then(blob => { //console.log('loaded sticker:', doc, div); if(middleware && !middleware()) return; //return; if(stickerType == 2) { const reader = new FileReader(); reader.addEventListener('loadend', async(e) => { //console.time('decompress sticker' + doc.id); //console.time('render sticker' + doc.id); // @ts-ignore const text = e.srcElement.result; let json = await apiManager.gzipUncompress(text, true); //console.timeEnd('decompress sticker' + doc.id); /* if(doc.id == '1860749763008266301') { console.log('loaded sticker:', doc, div); } */ let animation = await LottieLoader.loadAnimationWorker/* loadAnimation */({ container: div, loop: !emoji, autoplay: true, animationData: JSON.parse(json) }, group, toneIndex); animation.addListener('ready', () => { if(div.firstElementChild && div.firstElementChild.tagName == 'IMG') { div.firstElementChild.remove(); } }); //console.timeEnd('render sticker' + doc.id); /* div.addEventListener('mouseover', (e) => { let animation = LottieLoader.getAnimation(div, group); if(animation) { //console.log('sticker hover', animation, div); // @ts-ignore animation.loop = true; // @ts-ignore if(animation.currentFrame == animation.totalFrames - 1) { animation.goToAndPlay(0, true); } else { animation.play(); } div.addEventListener('mouseout', () => { // @ts-ignore animation.loop = false; }, {once: true}); } }); if(play && false) { animation.play(); } */ }); reader.readAsArrayBuffer(blob); } else if(stickerType == 1) { let img = new Image(); if(!downloaded && (!div.firstElementChild || div.firstElementChild.tagName != 'IMG')) { img.style.opacity = '' + 0; img.addEventListener('load', () => { window.requestAnimationFrame(() => { img.style.opacity = ''; }); }); } renderImageFromUrl(img, doc.url).then(() => { if(div.firstElementChild && div.firstElementChild != img) { div.firstElementChild.remove(); } div.append(img); }); } }); return lazyLoadQueue && (!doc.downloaded || stickerType == 2) ? (lazyLoadQueue.push({div, load, wasSeen: group == 'chat' && stickerType != 2}), Promise.resolve()) : load(); } export function wrapReply(title: string, subtitle: string, message?: any) { let div = document.createElement('div'); div.classList.add('reply'); let replyBorder = document.createElement('div'); replyBorder.classList.add('reply-border'); let replyContent = document.createElement('div'); replyContent.classList.add('reply-content'); let replyTitle = document.createElement('div'); replyTitle.classList.add('reply-title'); let replySubtitle = document.createElement('div'); replySubtitle.classList.add('reply-subtitle'); replyTitle.innerHTML = title ? RichTextProcessor.wrapEmojiText(title) : ''; let media = message && message.media; if(media) { replySubtitle.innerHTML = message.rReply; if(media.photo || (media.document && ['video'].indexOf(media.document.type) !== -1)) { let replyMedia = document.createElement('div'); replyMedia.classList.add('reply-media'); let photo = media.photo || media.document; let sizes = photo.sizes || photo.thumbs; if(sizes && sizes[0].bytes) { appPhotosManager.setAttachmentPreview(sizes[0].bytes, replyMedia, false, true); } appPhotosManager.preloadPhoto(photo, appPhotosManager.choosePhotoSize(photo, 32, 32)) .then(() => { renderImageFromUrl(replyMedia, photo._ == 'photo' ? photo.url : appPhotosManager.getDocumentCachedThumb(photo.id).url); }); replyContent.append(replyMedia); div.classList.add('is-reply-media'); } } else { replySubtitle.innerHTML = subtitle ? RichTextProcessor.wrapEmojiText(subtitle) : ''; } replyContent.append(replyTitle, replySubtitle); div.append(replyBorder, replyContent); /////////console.log('wrapReply', title, subtitle, media); return div; } export function wrapAlbum({groupID, attachmentDiv, middleware, uploading, lazyLoadQueue, isOut}: { groupID: string, attachmentDiv: HTMLElement, middleware?: () => boolean, lazyLoadQueue?: LazyLoadQueue, uploading?: boolean, isOut: boolean }) { let items: {size: MTPhotoSize, media: any, message: any}[] = []; // higher msgID will be the last in album let storage = appMessagesManager.groupedMessagesStorage[groupID]; for(let mid in storage) { let m = appMessagesManager.getMessage(+mid); let media = m.media.photo || m.media.document; let size: any = media._ == 'photo' ? appPhotosManager.choosePhotoSize(media, 480, 480) : {w: media.w, h: media.h}; items.push({size, media, message: m}); } let spacing = 2; let layouter = new Layouter(items.map(i => ({w: i.size.w, h: i.size.h})), 451, 100, spacing); let layout = layouter.layout(); console.log('layout:', layout, items.map(i => ({w: i.size.w, h: i.size.h}))); /* let borderRadius = window.getComputedStyle(realParent).getPropertyValue('border-radius'); let brSplitted = fillPropertyValue(borderRadius); */ for(let {geometry, sides} of layout) { let item = items.shift(); if(!item) { console.error('no item for layout!'); continue; } let {size, media, message} = item; let div = document.createElement('div'); div.classList.add('album-item'); div.dataset.mid = message.mid; div.style.width = geometry.width + 'px'; div.style.height = geometry.height + 'px'; div.style.top = geometry.y + 'px'; div.style.left = geometry.x + 'px'; if(sides & RectPart.Right) { attachmentDiv.style.width = geometry.width + geometry.x + 'px'; } if(sides & RectPart.Bottom) { attachmentDiv.style.height = geometry.height + geometry.y + 'px'; } if(sides & RectPart.Left && sides & RectPart.Top) { div.style.borderTopLeftRadius = 'inherit'; } if(sides & RectPart.Left && sides & RectPart.Bottom) { div.style.borderBottomLeftRadius = 'inherit'; } if(sides & RectPart.Right && sides & RectPart.Top) { div.style.borderTopRightRadius = 'inherit'; } if(sides & RectPart.Right && sides & RectPart.Bottom) { div.style.borderBottomRightRadius = 'inherit'; } if(media._ == 'photo') { wrapPhoto( media.id, message, div, 0, 0, false, isOut, lazyLoadQueue, middleware, size ); } else { wrapVideo({ doc: message.media.document, container: div, message, boxWidth: 0, boxHeight: 0, withTail: false, isOut, lazyLoadQueue, middleware }); } // @ts-ignore //div.style.backgroundColor = '#' + Math.floor(Math.random() * (2 ** 24 - 1)).toString(16).padStart(6, '0'); attachmentDiv.append(div); } } export function wrapPoll(pollID: string, mid: number) { let elem = new PollElement(); elem.setAttribute('poll-id', pollID); elem.setAttribute('message-id', '' + mid); return elem; }