diff --git a/src/components/lazyLoadQueue.ts b/src/components/lazyLoadQueue.ts index 05fb8121..84f7afa1 100644 --- a/src/components/lazyLoadQueue.ts +++ b/src/components/lazyLoadQueue.ts @@ -14,6 +14,8 @@ export default class LazyLoadQueue { private lockPromise: Promise = null; private unlockResolve: () => void = null; + private log = console.log.bind(console, '[LL]:'); + constructor(private parallelLimit = 5) { } @@ -67,13 +69,13 @@ export default class LazyLoadQueue { let tempID = this.tempID; - console.log('lazyLoadQueue: will load media', this.lockPromise); + this.log('will load media', this.lockPromise, item); try { if(this.lockPromise) { let perf = performance.now(); await this.lockPromise; - console.log('lazyLoadQueue: waited lock:', performance.now() - perf); + this.log('waited lock:', performance.now() - perf); } await new Promise((resolve, reject) => window.requestAnimationFrame(() => window.requestAnimationFrame(resolve))); @@ -86,7 +88,7 @@ export default class LazyLoadQueue { this.loadingMedia--; } - console.log('lazyLoadQueue: loaded media'); + this.log('loaded media'); if(this.lazyLoadMedia.length) { this.processQueue(); diff --git a/src/components/misc.ts b/src/components/misc.ts index 83969f67..de32d463 100644 --- a/src/components/misc.ts +++ b/src/components/misc.ts @@ -92,6 +92,16 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise { + if(elem instanceof HTMLImageElement) elem.src = url; + else if(elem instanceof SVGImageElement) elem.setAttributeNS(null, 'href', url); + else elem.style.backgroundImage = 'url(' + url + ')'; + }; +} + export function putPreloader(elem: Element, returnDiv = false) { const html = ` diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index 8f3fddbd..8f859a7c 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -12,9 +12,10 @@ import appWebpManager from '../lib/appManagers/appWebpManager'; import {wrapPlayer} from '../lib/ckin'; import { RichTextProcessor } from '../lib/richtextprocessor'; import { CancellablePromise } from '../lib/polyfill'; +import { renderImageFromUrl } from './misc'; export type MTDocument = { - _: 'document', + _: 'document' | 'documentEmpty', pFlags: any, flags: number, id: string, @@ -27,12 +28,25 @@ export type MTDocument = { dc_id: number, attributes: any[], + thumb?: MTPhotoSize, type?: string, h?: number, w?: number, file_name?: string, file?: File, - duration?: number + duration?: number, + downloaded?: boolean, + version?: any, + + audioTitle?: string, + audioPerformer?: string, + + sticker?: boolean, + stickerEmoji?: string, + stickerEmojiRaw?: string, + stickerSetInput?: any, + + animated?: boolean }; export type MTPhotoSize = { @@ -497,17 +511,13 @@ export async function wrapPhoto(this: AppImManager, photoID: string, message: an preloader.attach(container, true, promise); } - return promise.then((blob) => { + return promise.then(() => { if(this.peerID != peerID) { this.log.warn('peer changed'); return; } - - if(withTail) { - image.setAttributeNS(null, 'href', URL.createObjectURL(blob)); - } else if(image instanceof Image) { - image.src = URL.createObjectURL(blob); - } + + renderImageFromUrl(image, photo.url); }); }; @@ -525,14 +535,15 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: ( if(!stickerType) { console.error('wrong doc for wrapSticker!', doc, div); + return Promise.resolve(); } - //////console.log('wrap sticker', doc, onlyThumb); + console.log('wrap sticker', doc, div, onlyThumb); if(doc.thumbs && !div.firstElementChild) { let thumb = doc.thumbs[0]; - ///////console.log('wrap sticker', thumb); + console.log('wrap sticker', thumb, div); if(thumb.bytes) { apiFileManager.saveSmallFile(thumb.location, thumb.bytes); @@ -566,14 +577,7 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: ( return lazyLoadQueue ? (lazyLoadQueue.push({div, load}), Promise.resolve()) : load(); } - let load = () => apiFileManager.downloadSmallFile({ - _: 'inputDocumentFileLocation', - access_hash: doc.access_hash, - file_reference: doc.file_reference, - thumb_size: ''/* document.thumbs[0].type */, - id: doc.id, - stickerType: stickerType - }, {mimeType: doc.mime_type, dcID: doc.dc_id}).then(blob => { + let load = () => appDocsManager.downloadDoc(doc.id).then(blob => { //console.log('loaded sticker:', blob, div); if(middleware && !middleware()) return; @@ -666,7 +670,7 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: ( appStickersManager.saveSticker(doc); }); - return lazyLoadQueue ? (lazyLoadQueue.push({div, load}), Promise.resolve()) : load(); + return lazyLoadQueue && (!doc.downloaded || stickerType == 2) ? (lazyLoadQueue.push({div, load, wasSeen: group == 'chat'}), Promise.resolve()) : load(); } export function wrapReply(title: string, subtitle: string, media?: any) { @@ -710,8 +714,8 @@ export function wrapReply(title: string, subtitle: string, media?: any) { } appPhotosManager.preloadPhoto(photo, appPhotosManager.choosePhotoSize(photo, 32, 32)) - .then((blob) => { - replyMedia.style.backgroundImage = 'url(' + URL.createObjectURL(blob) + ')'; + .then(blob => { + renderImageFromUrl(replyMedia, photo.url || URL.createObjectURL(blob)); }); replyContent.append(replyMedia); diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index d256d3b0..b04c696b 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -6,7 +6,7 @@ import appPeersManager from './appPeersManager'; import appMessagesManager from "./appMessagesManager"; import appUsersManager from "./appUsersManager"; import { RichTextProcessor } from "../richtextprocessor"; -import { ripple } from "../../components/misc"; +import { ripple, renderImageFromUrl } from "../../components/misc"; import appSidebarLeft from "./appSidebarLeft"; import Scrollable from "../../components/scrollable"; @@ -219,13 +219,14 @@ export class AppDialogsManager { let img = new Image(); img.src = this.savedAvatarURLs[peerID]; + //renderImageFromUrl(img, this.savedAvatarURLs[peerID]); div.innerHTML = ''; //div.style.fontSize = '0'; // need //div.style.backgroundColor = ''; - window.requestAnimationFrame(() => { + //window.requestAnimationFrame(() => { div.append(img); - }); + //}); return true; } diff --git a/src/lib/appManagers/appDocsManager.ts b/src/lib/appManagers/appDocsManager.ts index e943b9b3..5016155e 100644 --- a/src/lib/appManagers/appDocsManager.ts +++ b/src/lib/appManagers/appDocsManager.ts @@ -2,12 +2,12 @@ import apiFileManager from '../mtproto/apiFileManager'; import FileManager from '../filemanager'; import {RichTextProcessor} from '../richtextprocessor'; import { CancellablePromise } from '../polyfill'; -//import { MTDocument } from '../../components/misc'; +import { MTDocument } from '../../components/wrappers'; class AppDocsManager { private docs: any = {}; - public saveDoc(apiDoc: /* MTDocument */any, context?: any) { + public saveDoc(apiDoc: MTDocument/* any */, context?: any) { console.log('saveDoc', apiDoc, this.docs[apiDoc.id]); if(this.docs[apiDoc.id]) return this.docs[apiDoc.id]; @@ -136,15 +136,17 @@ class AppDocsManager { return this.docs[docID] !== undefined; } - public getFileName(doc: any) { - if (doc.file_name) { - return doc.file_name + public getFileName(doc: MTDocument) { + if(doc.file_name) { + return doc.file_name; } - var fileExt = '.' + doc.mime_type.split('/')[1] - if (fileExt == '.octet-stream') { - fileExt = '' + + var fileExt = '.' + doc.mime_type.split('/')[1]; + if(fileExt == '.octet-stream') { + fileExt = ''; } - return 't_' + (doc.type || 'file') + doc.id + fileExt + + return 't_' + (doc.type || 'file') + doc.id + fileExt; } public updateDocDownloaded(docID: string) { @@ -166,8 +168,8 @@ class AppDocsManager { } } - public downloadDoc(docID: any, toFileEntry?: any): CancellablePromise { - let doc: any; + public downloadDoc(docID: string | MTDocument, toFileEntry?: any): CancellablePromise { + let doc: MTDocument; if(typeof(docID) === 'string') { doc = this.docs[docID]; } else { diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 39f35766..d7898ff7 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -1,7 +1,7 @@ import apiManager from '../mtproto/apiManager'; import { $rootScope, isElementInViewport, numberWithCommas, findUpClassName, formatNumber, placeCaretAtEnd, calcImageInBox, findUpTag, langPack } from "../utils"; import appUsersManager from "./appUsersManager"; -import appMessagesManager, { HistoryResult } from "./appMessagesManager"; +import appMessagesManager from "./appMessagesManager"; import appPeersManager from "./appPeersManager"; import appProfileManager from "./appProfileManager"; import appDialogsManager from "./appDialogsManager"; @@ -993,8 +993,11 @@ export class AppImManager { this.firstTopMsgID = dialog ? dialog.top_message : 0; + this.chatInner.style.visibility = 'hidden'; + this.chatInput.style.display = appPeersManager.isChannel(peerID) && !appPeersManager.isMegagroup(peerID) ? 'none' : ''; + this.topbar.style.display = ''; window.requestAnimationFrame(() => { - this.chatInner.style.visibility = 'hidden'; + //this.chatInner.style.visibility = 'hidden'; let title = ''; if(this.peerID == this.myID) title = 'Saved Messages'; @@ -1002,9 +1005,10 @@ export class AppImManager { //this.titleEl.innerHTML = appSidebarRight.profileElements.name.innerHTML = dom.titleSpan.innerHTML; this.titleEl.innerHTML = appSidebarRight.profileElements.name.innerHTML = title; this.pinnedMessageContainer.style.display = 'none'; - this.topbar.style.display = this.goDownBtn.style.display = ''; + this.goDownBtn.style.display = ''; + //this.topbar.style.display = this.goDownBtn.style.display = ''; + //this.chatInput.style.display = appPeersManager.isChannel(peerID) && !appPeersManager.isMegagroup(peerID) ? 'none' : ''; //appSidebarRight.toggleSidebar(true); - this.chatInput.style.display = appPeersManager.isChannel(peerID) && !appPeersManager.isMegagroup(peerID) ? 'none' : ''; if(appPeersManager.isAnyGroup(peerID)) this.chatInner.classList.add('is-chat'); else this.chatInner.classList.remove('is-chat'); @@ -1454,21 +1458,15 @@ export class AppImManager { bubbleContainer.style.height = attachmentDiv.style.height; bubbleContainer.style.width = attachmentDiv.style.width; //appPhotosManager.setAttachmentSize(doc, bubble); - let load = () => wrapSticker(doc, attachmentDiv, () => { + wrapSticker(doc, attachmentDiv, () => { if(this.peerID != peerID) { this.log.warn('peer changed, canceling sticker attach'); return false; } return true; - }, null, 'chat', false, !!message.pending || !multipleRender).then(() => { - //preloader.detach(); - /* attachmentDiv.style.width = ''; - attachmentDiv.style.height = ''; */ - }); - - this.lazyLoadQueue.push({div: bubble, load: load, wasSeen: true}); - + }, this.lazyLoadQueue, 'chat', false, !!message.pending || !multipleRender); + break; } else if(doc.mime_type == 'video/mp4' && doc.size <= 20e6) { this.log('never get free 2', doc); @@ -1703,6 +1701,8 @@ export class AppImManager { } } + //history = history.slice(); // need + if(additionMsgID) { history.unshift(additionMsgID); } @@ -1719,6 +1719,8 @@ export class AppImManager { this.scrollPosition.prepareFor(reverse ? 'up' : 'down'); } + let firstLoad = !!this.setPeerPromise && false; + let peerID = this.peerID; return new Promise((resolve, reject) => { let resolved = false; @@ -1733,6 +1735,8 @@ export class AppImManager { //console.timeEnd('appImManager: pre render start'); + //this.log('start performHistoryResult, scrollTop:', this.scrollable.scrollTop, this.scrollable.scrollHeight, this.scrollable.innerHeight); + let renderedFirstScreen = false; let r = () => { //let bubble = bubbles.shift(); @@ -1771,7 +1775,7 @@ export class AppImManager { if(reverse) { this.scrollable.prepend(bubble); - //console.log('innerHeight', innerHeight, this.scrollable.scrollTop); + //this.log('performHistoryResult scrollTop', this.scrollable.scrollTop, bubble.scrollHeight); /* if(innerHeight >= 0) { let height = bubble.scrollHeight; @@ -1816,10 +1820,10 @@ export class AppImManager { this.scrollable.append(bubble); } */ - window.requestAnimationFrame(r); + firstLoad ? window.requestAnimationFrame(r) : r(); }; - window.requestAnimationFrame(r); + firstLoad ? window.requestAnimationFrame(r) : r(); //r(); /* method((msgID) => { let message = appMessagesManager.getMessage(msgID); @@ -1854,7 +1858,7 @@ export class AppImManager { maxID = dialog.top_message/* + 1 */; } - let loadCount = Object.keys(this.bubbles).length > 0 ? 20 : this.scrollable.container.parentElement.scrollHeight / 30/* * 1.25 */ | 0; + let loadCount = Object.keys(this.bubbles).length > 0 ? 20 : this.scrollable.innerHeight / 38/* * 1.25 */ | 0; /* if(testScroll) { loadCount = 5; @@ -1901,7 +1905,9 @@ export class AppImManager { return (reverse ? this.getHistoryTopPromise = promise : this.getHistoryBottomPromise = promise); } else { - return this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID, true); + let promise = this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID, true); + return (reverse ? this.getHistoryTopPromise = promise : this.getHistoryBottomPromise = promise); + //return this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID, true); } } diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 42aaa3b7..1b2f33be 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -1137,7 +1137,8 @@ export class AppMessagesManager { if(apiMessage.media.ttl_seconds) { apiMessage.media = {_: 'messageMediaUnsupportedWeb'}; } else { - appPhotosManager.savePhoto(apiMessage.media.photo, mediaContext); + apiMessage.media.photo = appPhotosManager.savePhoto(apiMessage.media.photo, mediaContext); + //appPhotosManager.savePhoto(apiMessage.media.photo, mediaContext); } break case 'messageMediaDocument': @@ -1171,7 +1172,8 @@ export class AppMessagesManager { var migrateTo; switch(apiMessage.action._) { case 'messageActionChatEditPhoto': - appPhotosManager.savePhoto(apiMessage.action.photo, mediaContext); + apiMessage.action.photo = appPhotosManager.savePhoto(apiMessage.action.photo, mediaContext); + //appPhotosManager.savePhoto(apiMessage.action.photo, mediaContext); if(isBroadcast) { apiMessage.action._ = 'messageActionChannelEditPhoto'; } diff --git a/src/lib/appManagers/appPhotosManager.ts b/src/lib/appManagers/appPhotosManager.ts index 9754645c..286431d1 100644 --- a/src/lib/appManagers/appPhotosManager.ts +++ b/src/lib/appManagers/appPhotosManager.ts @@ -7,7 +7,7 @@ import apiFileManager from "../mtproto/apiFileManager"; import apiManager from "../mtproto/apiManager"; type MTPhoto = { - _: 'photo', + _: 'photo' | 'photoEmpty' | string, pFlags: any, flags: number, id: string, @@ -18,7 +18,8 @@ type MTPhoto = { dc_id: number, user_id: number, - downloaded?: boolean + downloaded?: boolean | number, + url?: string }; export class AppPhotosManager { @@ -52,7 +53,7 @@ export class AppPhotosManager { console.warn('no apiPhoto.id', photo); } else this.photos[photo.id] = photo; - if(!('sizes' in photo)) return; + /* if(!('sizes' in photo)) return; photo.sizes.forEach((photoSize: any) => { if(photoSize._ == 'photoCachedSize') { @@ -65,7 +66,7 @@ export class AppPhotosManager { delete photoSize.bytes; photoSize._ = 'photoSize'; } - }); + }); */ /* if(!photo.downloaded) { photo.downloaded = apiFileManager.isFileExists({ @@ -141,7 +142,8 @@ export class AppPhotosManager { var photoIDs = []; var context = {user_id: userID}; for(var i = 0; i < photosResult.photos.length; i++) { - this.savePhoto(photosResult.photos[i], context); + //this.savePhoto(photosResult.photos[i], context); + photosResult.photos[i] = this.savePhoto(photosResult.photos[i], context); photoIDs.push(photosResult.photos[i].id); } @@ -237,8 +239,8 @@ export class AppPhotosManager { return photoSize; } - public preloadPhoto(photoID: any, photoSize?: MTPhotoSize): Promise { - let photo: any = null; + public preloadPhoto(photoID: any, photoSize?: MTPhotoSize): Promise { + let photo: MTPhoto = null; if(typeof(photoID) === 'string') { photo = this.photos[photoID]; @@ -267,6 +269,10 @@ export class AppPhotosManager { let promise: Promise; + /* if(photo.downloaded == photoSize.size && photo.url) { + return Promise.resolve(photo.url); + } */ + if(isPhoto/* && photoSize.size >= 1e6 */) { //console.log('Photos downloadFile exec', photo); promise = apiFileManager.downloadFile(photo.dc_id, location, photoSize.size); @@ -276,8 +282,14 @@ export class AppPhotosManager { } if(typeof(photoID) === 'string') { - promise.then(() => { - this.photos[photoID].downloaded = true; + let photo = this.photos[photoID]; + promise.then(blob => { + if(!photo.downloaded || photo.downloaded < blob.size) { + photo.downloaded = blob.size; + photo.url = URL.createObjectURL(blob); + + console.log('wrote photo:', photo, photoSize, blob); + } }); } diff --git a/src/lib/appManagers/appSidebarRight.ts b/src/lib/appManagers/appSidebarRight.ts index 76e27a93..c2d015d8 100644 --- a/src/lib/appManagers/appSidebarRight.ts +++ b/src/lib/appManagers/appSidebarRight.ts @@ -1,4 +1,4 @@ -import { horizontalMenu, formatPhoneNumber, putPreloader } from "../../components/misc"; +import { horizontalMenu, formatPhoneNumber, putPreloader, renderImageFromUrl } from "../../components/misc"; import Scrollable from '../../components/scrollable'; import { $rootScope } from "../utils"; import appMessagesManager from "./appMessagesManager"; @@ -67,6 +67,9 @@ class AppSidebarRight { [type: string]: number[] } } = {}; + public usedFromHistory: { + [type: string]: number + } = {}; private log = logger('SR'); @@ -83,7 +86,7 @@ class AppSidebarRight { private mediaDivsByIDs: { [mid: number]: HTMLDivElement } = {}; - + public urlsToRevoke: string[] = []; constructor() { @@ -93,8 +96,8 @@ class AppSidebarRight { this.scroll = new Scrollable(this.sidebarEl, 'y', 1200, 'SR'); this.scroll.container.addEventListener('scroll', this.onSidebarScroll.bind(this)); this.scroll.onScrolledBottom = () => { - if(this.sharedMediaSelected && !this.scroll.hiddenElements.down.length - && this.sharedMediaSelected.childElementCount/* && false */) { + if(this.sharedMediaSelected && !this.scroll.hiddenElements.down.length && this.sharedMediaSelected.childElementCount/* && false */) { + this.log('onScrolledBottom will load media'); this.loadSidebarMedia(true); } }; @@ -104,7 +107,7 @@ class AppSidebarRight { this.sharedMediaType = this.sharedMediaTypes[id]; this.sharedMediaSelected = tabContent.firstElementChild as HTMLDivElement; - + if(this.prevTabID != -1 && !this.sharedMediaSelected.childElementCount) { // quick brown fix this.loadSidebarMedia(true); } @@ -142,11 +145,10 @@ class AppSidebarRight { let ids = Object.keys(this.mediaDivsByIDs).map(k => +k).sort(); let idx = ids.findIndex(i => i == messageID); - + let targets = ids.map(id => ({element: this.mediaDivsByIDs[id], mid: id})); - appMediaViewer.openMedia(message, target, false, this.sidebarEl, - targets.slice(idx + 1).reverse(), targets.slice(0, idx).reverse(), () => this.loadSidebarMedia(true)); + appMediaViewer.openMedia(message, target, false, this.sidebarEl, targets.slice(idx + 1).reverse(), targets.slice(0, idx).reverse(), () => this.loadSidebarMedia(true)); }); this.profileElements.notificationsCheckbox.addEventListener('change', () => { @@ -154,12 +156,7 @@ class AppSidebarRight { appImManager.mutePeer(); }); - window.addEventListener('resize', () => { - setTimeout(() => { - this.scroll.onScroll(); - this.onSidebarScroll(); - }, 0); - }); + window.addEventListener('resize', this.onSidebarScroll.bind(this)); if(testScroll) { let div = document.createElement('div'); @@ -191,6 +188,7 @@ class AppSidebarRight { setTimeout(() => this.lazyLoadQueueSidebar.check(), 200); this.sidebarEl.classList.add('active'); } else this.sidebarEl.classList.remove('active'); + return; } @@ -201,12 +199,237 @@ class AppSidebarRight { } } + public performSearchResult(ids: number[], type: string) { + let peerID = this.peerID; + + let sharedMediaDiv: HTMLDivElement; + let messages: any[] = []; + for(let mid of ids) { + let message = appMessagesManager.getMessage(mid); + if(message.media) messages.push(message); + } + + let elemsToAppend: HTMLElement[] = []; + + /*'inputMessagesFilterContacts', + 'inputMessagesFilterPhotoVideo', + 'inputMessagesFilterDocument', + 'inputMessagesFilterUrl', + 'inputMessagesFilterVoice'*/ + switch(type) { + case 'inputMessagesFilterPhotoVideo': { + sharedMediaDiv = this.sharedMedia.contentMedia; + + for(let message of messages) { + let media = message.media.photo || message.media.document || (message.media.webpage && message.media.webpage.document); + if(!media) { + //this.log('no media!', message); + continue; + } + + if(media._ == 'document' && media.type != 'video'/* && media.type != 'gif' */) { + //this.log('broken video', media); + continue; + } + + let div = document.createElement('div'); + //console.log(message, photo); + + let isPhoto = media._ == 'photo'; + + let photo = isPhoto ? appPhotosManager.getPhoto(media.id) : null; + if(!photo || !photo.downloaded) { + //this.log('inputMessagesFilterPhotoVideo', message, media, photo, div); + + let sizes = media.sizes || media.thumbs; + if(sizes && sizes[0].bytes) { + appPhotosManager.setAttachmentPreview(sizes[0].bytes, div, false, true); + } /* else { + this.log('no stripped size', message, media); + } */ + } + + //this.log('inputMessagesFilterPhotoVideo', message, media); + + let load = () => appPhotosManager.preloadPhoto(isPhoto ? media.id : media, appPhotosManager.choosePhotoSize(media, 200, 200)) + .then((blob) => { + if($rootScope.selectedPeerID != peerID) { + this.log.warn('peer changed'); + return; + } + + if(photo && photo.url) { + renderImageFromUrl(div, photo.url); + } else { + let url = URL.createObjectURL(blob); + this.urlsToRevoke.push(url); + + let img = new Image(); + img.src = url; + img.onload = () => { + div.style.backgroundImage = 'url(' + url + ')'; + }; + } + + //div.style.backgroundImage = 'url(' + url + ')'; + }); + + div.dataset.mid = '' + message.mid; + + if(photo && photo.downloaded) load(); + else this.lazyLoadQueueSidebar.push({div, load}); + + this.lastSharedMediaDiv.append(div); + if(this.lastSharedMediaDiv.childElementCount == 3) { + if(!this.scroll.contains(this.lastSharedMediaDiv)) { + elemsToAppend.push(this.lastSharedMediaDiv); + } + + this.lastSharedMediaDiv = document.createElement('div'); + } + + this.mediaDivsByIDs[message.mid] = div; + + //sharedMediaDiv.append(div); + } + + break; + } + + case 'inputMessagesFilterDocument': { + sharedMediaDiv = this.sharedMedia.contentDocuments; + + for(let message of messages) { + if(!message.media.document || message.media.document.type == 'voice') { + continue; + } + + let doc = message.media.document; + if(doc.attributes) { + if(doc.attributes.find((a: any) => a._ == "documentAttributeSticker")) { + continue; + } + } + + //this.log('come back down to my knees', message); + + let div = wrapDocument(message.media.document, true); + elemsToAppend.push(div); + } + break; + } + + case 'inputMessagesFilterUrl': { + sharedMediaDiv = this.sharedMedia.contentLinks; + + for(let message of messages) { + if(!message.media.webpage || message.media.webpage._ == 'webPageEmpty') { + continue; + } + + let webpage = message.media.webpage; + let div = document.createElement('div'); + + let previewDiv = document.createElement('div'); + previewDiv.classList.add('preview'); + + //this.log('wrapping webpage', webpage); + + if(webpage.photo) { + let load = () => appPhotosManager.preloadPhoto(webpage.photo.id, appPhotosManager.choosePhotoSize(webpage.photo, 380, 0)) + .then((blob) => { + if($rootScope.selectedPeerID != peerID) { + this.log.warn('peer changed'); + return; + } + + renderImageFromUrl(previewDiv, webpage.photo.url); + + /* let url = URL.createObjectURL(blob); + this.urlsToRevoke.push(url); + + previewDiv.style.backgroundImage = 'url(' + url + ')'; */ + }); + + this.lazyLoadQueueSidebar.push({div: previewDiv, load}); + } else { + previewDiv.innerText = (webpage.title || webpage.description || webpage.url || webpage.display_url).slice(0, 1); + previewDiv.classList.add('empty'); + } + + let title = webpage.rTitle || ''; + let subtitle = webpage.rDescription || ''; + let url = RichTextProcessor.wrapRichText(webpage.url || ''); + + if(!title) { + //title = new URL(webpage.url).hostname; + title = webpage.display_url.split('/', 1)[0]; + } + + div.append(previewDiv); + div.insertAdjacentHTML('beforeend', ` +
${title}
+
${subtitle}
+
${url}
+ `); + + if(div.innerText.trim().length) { + elemsToAppend.push(div); + } + + } + + break; + } + + /* case 'inputMessagesFilterVoice': { + //this.log('wrapping audio', message.media); + if(!message.media || !message.media.document || message.media.document.type != 'voice') { + break; + } + + let doc = message.media.document; + + this.log('wrapping audio', doc); + + let audioDiv = wrapAudio(doc); + + this.sharedMedia.contentAudio.append(audioDiv); + + break; + } */ + + default: + //console.warn('death is my friend', message); + break; + } + + if(this.lastSharedMediaDiv.childElementCount && !this.scroll.contains(this.lastSharedMediaDiv)) { + elemsToAppend.push(this.lastSharedMediaDiv); + } + + if(elemsToAppend.length) { + //window.requestAnimationFrame(() => { + elemsToAppend.forEach(el => this.scroll.append(el)); + //}); + } + + if(sharedMediaDiv) { + let parent = sharedMediaDiv.parentElement; + if(parent.lastElementChild.classList.contains('preloader')) { + parent.lastElementChild.remove(); + } + } + + this.onSidebarScroll(); + } + public loadSidebarMedia(single = false) { if(testScroll/* || 1 == 1 */) { return; } - - //this.log('loadSidebarMedia', single, this.peerID); + + this.log('loadSidebarMedia', single, this.peerID); let peerID = this.peerID; @@ -214,16 +437,25 @@ class AppSidebarRight { typesToLoad = typesToLoad.filter(type => !this.loadedAllMedia[type]); if(!typesToLoad.length) return; - if(!this.historiesStorage[peerID]) this.historiesStorage[peerID] = {}; - let historyStorage = this.historiesStorage[peerID]; - + let historyStorage = this.historiesStorage[peerID] ?? (this.historiesStorage[peerID] = {}); + this.scroll.lock(); let promises = typesToLoad.map(type => { if(this.loadSidebarMediaPromises[type]) return this.loadSidebarMediaPromises[type]; - if(!historyStorage[type]) historyStorage[type] = []; - let history = historyStorage[type]; + let history = historyStorage[type] ?? (historyStorage[type] = []); + + let loadCount = (appPhotosManager.windowH / 130 | 0) * 3; + + // render from cache + if(history.length && this.usedFromHistory[type] < history.length && this.cleared[type]) { + let ids = history.slice(this.usedFromHistory[type], this.usedFromHistory[type] + loadCount); + this.log('will render from cache', this.usedFromHistory[type], history, ids, loadCount); + this.usedFromHistory[type] += ids.length; + this.performSearchResult(ids, type); + return; + } // заливать новую картинку сюда только после полной отправки! //let maxID = this.minMediaID[type] || 0; @@ -233,9 +465,9 @@ class AppSidebarRight { ? appMessagesManager.historiesStorage[peerID].history.slice() : []; maxID = !maxID && ids.length ? ids[ids.length - 1] : maxID; - //this.log('search house of glass pre', type, ids, maxID); + this.log('search house of glass pre', type, ids, maxID); - let loadCount = history.length ? 50 : 15; + //let loadCount = history.length ? 50 : 15; return this.loadSidebarMediaPromises[type] = appMessagesManager.getSearch(peerID, '', {_: type}, maxID, loadCount) .then(value => { ids = ids.concat(value.history); @@ -243,238 +475,26 @@ class AppSidebarRight { this.log('search house of glass', type, value, ids, this.cleared); - if(value.history.length < loadCount) { - this.loadedAllMedia[type] = true; - } - if($rootScope.selectedPeerID != peerID) { this.log.warn('peer changed'); return; } - if(this.cleared[type]) { - ids = history; - delete this.cleared[type]; - } - - let sharedMediaDiv: HTMLDivElement; - let messages: any[] = []; - for(let mid of ids) { - let message = appMessagesManager.getMessage(mid); - if(message.media) messages.push(message); + if(value.history.length < loadCount) { + this.loadedAllMedia[type] = true; } - - let elemsToAppend: HTMLElement[] = []; - /*'inputMessagesFilterContacts', - 'inputMessagesFilterPhotoVideo', - 'inputMessagesFilterDocument', - 'inputMessagesFilterUrl', - 'inputMessagesFilterVoice'*/ - switch(type) { - case 'inputMessagesFilterPhotoVideo': { - sharedMediaDiv = this.sharedMedia.contentMedia; - - for(let message of messages) { - let media = message.media.photo || message.media.document || (message.media.webpage && message.media.webpage.document); - if(!media) { - //this.log('no media!', message); - continue; - } - - if(media._ == 'document' && media.type != 'video'/* && media.type != 'gif' */) { - //this.log('broken video', media); - continue; - } - - let div = document.createElement('div'); - //console.log(message, photo); - - let isPhoto = media._ == 'photo'; - - let photo = isPhoto ? appPhotosManager.getPhoto(media.id) : null; - if(!photo || !photo.downloaded) { - //this.log('inputMessagesFilterPhotoVideo', message, media, photo, div); - - let sizes = media.sizes || media.thumbs; - if(sizes && sizes[0].bytes) { - appPhotosManager.setAttachmentPreview(sizes[0].bytes, div, false, true); - } /* else { - this.log('no stripped size', message, media); - } */ - } - - //this.log('inputMessagesFilterPhotoVideo', message, media); - - let load = () => appPhotosManager.preloadPhoto(isPhoto ? media.id : media, appPhotosManager.choosePhotoSize(media, 200, 200)) - .then((blob) => { - if($rootScope.selectedPeerID != peerID) { - this.log.warn('peer changed'); - return; - } - - let url = URL.createObjectURL(blob); - this.urlsToRevoke.push(url); - - let img = new Image(); - img.src = url; - img.onload = () => { - div.style.backgroundImage = 'url(' + url + ')'; - }; - - //div.style.backgroundImage = 'url(' + url + ')'; - }); - - div.dataset.mid = '' + message.mid; - - if(photo && photo.downloaded) load(); - else this.lazyLoadQueueSidebar.push({div, load}); - - this.lastSharedMediaDiv.append(div); - if(this.lastSharedMediaDiv.childElementCount == 3) { - if(!this.scroll.contains(this.lastSharedMediaDiv)) { - elemsToAppend.push(this.lastSharedMediaDiv); - } - - this.lastSharedMediaDiv = document.createElement('div'); - } - - this.mediaDivsByIDs[message.mid] = div; - - //sharedMediaDiv.append(div); - } - - break; - } - - case 'inputMessagesFilterDocument': { - sharedMediaDiv = this.sharedMedia.contentDocuments; - - for(let message of messages) { - if(!message.media.document || message.media.document.type == 'voice') { - continue; - } - - let doc = message.media.document; - if(doc.attributes) { - if(doc.attributes.find((a: any) => a._ == "documentAttributeSticker")) { - continue; - } - } - - //this.log('come back down to my knees', message); - - let div = wrapDocument(message.media.document, true); - elemsToAppend.push(div); - } - break; - } - - case 'inputMessagesFilterUrl': { - sharedMediaDiv = this.sharedMedia.contentLinks; - - for(let message of messages) { - if(!message.media.webpage || message.media.webpage._ == 'webPageEmpty') { - continue; - } - - let webpage = message.media.webpage; - let div = document.createElement('div'); - - let previewDiv = document.createElement('div'); - previewDiv.classList.add('preview'); - - //this.log('wrapping webpage', webpage); - - if(webpage.photo) { - let load = () => appPhotosManager.preloadPhoto(webpage.photo.id, appPhotosManager.choosePhotoSize(webpage.photo, 380, 0)) - .then((blob) => { - if($rootScope.selectedPeerID != peerID) { - this.log.warn('peer changed'); - return; - } - - let url = URL.createObjectURL(blob); - this.urlsToRevoke.push(url); - - previewDiv.style.backgroundImage = 'url(' + url + ')'; - }); - - this.lazyLoadQueueSidebar.push({div: previewDiv, load}); - } else { - previewDiv.innerText = (webpage.title || webpage.description || webpage.url || webpage.display_url).slice(0, 1); - previewDiv.classList.add('empty'); - } - - let title = webpage.rTitle || ''; - let subtitle = webpage.rDescription || ''; - let url = RichTextProcessor.wrapRichText(webpage.url || ''); - - if(!title) { - //title = new URL(webpage.url).hostname; - title = webpage.display_url.split('/', 1)[0]; - } - - div.append(previewDiv); - div.insertAdjacentHTML('beforeend', ` -
${title}
-
${subtitle}
-
${url}
- `); - - if(div.innerText.trim().length) { - elemsToAppend.push(div); - } - - } - - break; - } - - /* case 'inputMessagesFilterVoice': { - //this.log('wrapping audio', message.media); - if(!message.media || !message.media.document || message.media.document.type != 'voice') { - break; - } - - let doc = message.media.document; - - this.log('wrapping audio', doc); - - let audioDiv = wrapAudio(doc); - - this.sharedMedia.contentAudio.append(audioDiv); - - break; - } */ - - default: - //console.warn('death is my friend', message); - break; - } - - if(this.lastSharedMediaDiv.childElementCount && !this.scroll.contains(this.lastSharedMediaDiv)) { - elemsToAppend.push(this.lastSharedMediaDiv); + if(this.cleared[type]) { + //ids = history; + delete this.cleared[type]; } - if(elemsToAppend.length) { - //window.requestAnimationFrame(() => { - elemsToAppend.forEach(el => this.scroll.append(el)); - //}); + if(ids.length) { + this.performSearchResult(ids, type); } - - if(sharedMediaDiv) { - let parent = sharedMediaDiv.parentElement; - if(parent.lastElementChild.classList.contains('preloader')) { - parent.lastElementChild.remove(); - } - } - - this.onSidebarScroll(); - }).then(() => { - this.loadSidebarMediaPromises[type] = null; }, (err) => { this.log.error('load error:', err); + }).then(() => { this.loadSidebarMediaPromises[type] = null; }); }); @@ -491,7 +511,7 @@ class AppSidebarRight { this.lastSharedMediaDiv = document.createElement('div'); //this.log('fillProfileElements'); - + window.requestAnimationFrame(() => { this.profileContentEl.parentElement.scrollTop = 0; this.profileElements.bio.style.display = 'none'; @@ -500,7 +520,7 @@ class AppSidebarRight { this.profileElements.notificationsRow.style.display = ''; this.profileElements.notificationsCheckbox.checked = true; this.profileElements.notificationsStatus.innerText = 'Enabled'; - + Object.keys(this.sharedMedia).forEach(key => { this.sharedMedia[key].innerHTML = ''; @@ -509,12 +529,14 @@ class AppSidebarRight { putPreloader(parent, true); } }); - + this.savedVirtualStates = {}; this.prevTabID = -1; this.scroll.setVirtualContainer(null); (this.profileTabs.children[1] as HTMLLIElement).click(); // set media + //this.scroll.getScrollTopOffset(); + this.loadSidebarMedia(true); }); @@ -530,6 +552,7 @@ class AppSidebarRight { this.sharedMediaTypes.forEach(type => { //this.minMediaID[type] = 0; this.cleared[type] = true; + this.usedFromHistory[type] = 0; }); let setText = (text: string, el: HTMLDivElement) => { @@ -537,12 +560,14 @@ class AppSidebarRight { if(el.childElementCount > 1) { el.firstElementChild.remove(); } - + let p = document.createElement('p'); p.innerHTML = text; el.prepend(p); - + el.style.display = ''; + + //this.scroll.getScrollTopOffset(); }); }; @@ -564,7 +589,10 @@ class AppSidebarRight { appImManager.setMutedState(muted); } } else { - this.profileElements.notificationsRow.style.display = 'none'; + window.requestAnimationFrame(() => { + this.profileElements.notificationsRow.style.display = 'none'; + //this.scroll.getScrollTopOffset(); + }) } if(peerID > 0) { @@ -590,7 +618,7 @@ class AppSidebarRight { appMessagesManager.wrapSingleMessage(userFull.pinned_msg_id); } - this.scroll.getScrollTopOffset(); + //this.scroll.getScrollTopOffset(); }); } else { let chat = appPeersManager.getPeer(peerID); @@ -607,11 +635,11 @@ class AppSidebarRight { setText(RichTextProcessor.wrapRichText(chatFull.about), this.profileElements.bio); } - this.scroll.getScrollTopOffset(); + //this.scroll.getScrollTopOffset(); }); } - this.scroll.getScrollTopOffset(); + //this.scroll.getScrollTopOffset(); //this.loadSidebarMedia(); } } diff --git a/src/lib/appManagers/appWebPagesManager.ts b/src/lib/appManagers/appWebPagesManager.ts index 181d1ea1..37e5800b 100644 --- a/src/lib/appManagers/appWebPagesManager.ts +++ b/src/lib/appManagers/appWebPagesManager.ts @@ -21,7 +21,8 @@ class AppWebPagesManager { saveWebPage(apiWebPage: any, messageID?: number, mediaContext?: any) { if(apiWebPage.photo && apiWebPage.photo._ === 'photo') { - appPhotosManager.savePhoto(apiWebPage.photo, mediaContext); + //appPhotosManager.savePhoto(apiWebPage.photo, mediaContext); + apiWebPage.photo = appPhotosManager.savePhoto(apiWebPage.photo, mediaContext); } else { delete apiWebPage.photo; } diff --git a/src/lib/appManagers/appWebpManager.ts b/src/lib/appManagers/appWebpManager.ts index 8305ce28..119195ae 100644 --- a/src/lib/appManagers/appWebpManager.ts +++ b/src/lib/appManagers/appWebpManager.ts @@ -65,7 +65,11 @@ class AppWebpManager { await this.loaded; this.busyPromise = this.convert(bytes); - img.src = await this.busyPromise; + let imgTemp = new Image(); + imgTemp.src = await this.busyPromise; + imgTemp.onload = () => { + img.src = imgTemp.src; + }; callback(); this.busyPromise = null; diff --git a/src/lib/lottieLoader.ts b/src/lib/lottieLoader.ts index e4ef2526..fd5a1c37 100644 --- a/src/lib/lottieLoader.ts +++ b/src/lib/lottieLoader.ts @@ -61,6 +61,11 @@ class LottieLoader { if(canvas) { let c = container.firstElementChild as HTMLCanvasElement; + if(!c) { + console.warn('no canvas element for check!', container, animations[i]); + continue; + } + if(!c.height && !c.width && isElementInViewport(container)) { //console.log('lottie need resize'); animation.resize(); diff --git a/src/lib/mtproto/apiFileManager.ts b/src/lib/mtproto/apiFileManager.ts index 202725d0..6c47e347 100644 --- a/src/lib/mtproto/apiFileManager.ts +++ b/src/lib/mtproto/apiFileManager.ts @@ -94,12 +94,6 @@ export class ApiFileManager { var fileName = (location.file_name as string || '').split('.'); var ext: string = fileName[fileName.length - 1] || ''; - if(location.stickerType == 1) { - ext += 'webp'; - } else if(location.stickerType == 2) { - ext += 'tgs'; - } - var versionPart = location.version ? ('v' + location.version) : ''; return (fileName[0] ? fileName[0] + '_' : '') + location.id + versionPart + (ext ? '.' + ext : ext); @@ -108,13 +102,6 @@ export class ApiFileManager { this.log.trace('Empty location', location); } - var ext: string = 'jpg'; - if(location.stickerType == 1) { - ext = 'webp'; - } else if(location.stickerType == 2) { - ext += 'tgs'; - } - if(location.volume_id) { return location.volume_id + '_' + location.local_id + '.' + ext; } else { diff --git a/webpack.common.js b/webpack.common.js index 47e0e799..8d91575e 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -1,6 +1,8 @@ const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); +let allowedIPs = ['195.66.140.39', '192.168.31.144', '127.0.0.1', '192.168.31.1', '192.168.31.192']; + module.exports = { module: { rules: [ @@ -74,7 +76,28 @@ module.exports = { watchContentBase: true, compress: true, http2: true, - port: 9000 + host: '0.0.0.0', + port: 9000, + overlay: true, + useLocalIp: true, + before: function(app, server, compiler) { + app.use((req, res, next) => { + let IP = ''; + if(req.headers['cf-connecting-ip']) { + IP = req.headers['cf-connecting-ip']; + } else { + IP = req.connection.remoteAddress.split(':').pop(); + } + + if(!allowedIPs.includes(IP)) { + console.log('Bad IP connecting: ' + IP, req.url); + res.status(404).send('Nothing interesting here.'); + } else { + console.log(req.url, IP); + next(); + } + }); + } }, plugins: [