diff --git a/src/components/appSelectPeers.ts b/src/components/appSelectPeers.ts index 537f464c..36e2d8fa 100644 --- a/src/components/appSelectPeers.ts +++ b/src/components/appSelectPeers.ts @@ -5,7 +5,6 @@ import appDialogsManager from "../lib/appManagers/appDialogsManager"; import appChatsManager from "../lib/appManagers/appChatsManager"; import appUsersManager from "../lib/appManagers/appUsersManager"; import { appPeersManager } from "../lib/services"; -import appProfileManager from "../lib/appManagers/appProfileManager"; import appPhotosManager from "../lib/appManagers/appPhotosManager"; export class AppSelectPeers { @@ -116,16 +115,18 @@ export class AppSelectPeers { appMessagesManager.getConversations(this.offsetIndex, 50, 0).then(value => { let dialogs = value.dialogs; - this.offsetIndex = dialogs[value.dialogs.length - 1].index || 0; + let newOffsetIndex = dialogs[value.dialogs.length - 1].index || 0; - if(dialogs[0].peerID != this.myID) { - dialogs.findAndSplice(d => d.peerID == this.myID); + dialogs = dialogs.filter(d => d.peerID != this.myID); + if(!this.offsetIndex) { dialogs.unshift({ peerID: this.myID, pFlags: {} } as any); } + this.offsetIndex = newOffsetIndex; + this.renderResults(dialogs.map(dialog => dialog.peerID)); }); } diff --git a/src/components/bubbleGroups.ts b/src/components/bubbleGroups.ts index 4dbd0223..7925b813 100644 --- a/src/components/bubbleGroups.ts +++ b/src/components/bubbleGroups.ts @@ -12,6 +12,8 @@ export default class BubbleGroups { details.group.findAndSplice(d => d == bubble); if(!details.group.length) { this.groups.findAndSplice(g => g == details.group); + } else { + this.updateGroup(details.group); } } } @@ -49,7 +51,7 @@ export default class BubbleGroups { //console.log('addBubble', bubble, message.mid, fromID, reverse, group); this.bubblesByGroups[reverse ? 'unshift' : 'push']({timestamp, fromID, mid: message.mid, group}); - this.updateGroup(group, reverse); + this.updateGroup(group); } setClipIfNeeded(bubble: HTMLDivElement, remove = false) { @@ -100,7 +102,7 @@ export default class BubbleGroups { } } - updateGroup(group: HTMLDivElement[], reverse = false) { + updateGroup(group: HTMLDivElement[]) { /* if(this.updateRAFs.has(group)) { window.cancelAnimationFrame(this.updateRAFs.get(group)); this.updateRAFs.delete(group); @@ -115,8 +117,6 @@ export default class BubbleGroups { let first = group[0]; - //appImManager.scrollPosition.prepareFor(reverse ? 'up' : 'down'); - //console.log('updateGroup', group, first); if(group.length == 1) { @@ -140,8 +140,6 @@ export default class BubbleGroups { last.classList.remove('is-group-first'); last.classList.add('is-group-last'); this.setClipIfNeeded(last); - - //appImManager.scrollPosition.restore(); //})); } diff --git a/src/components/lazyLoadQueue.ts b/src/components/lazyLoadQueue.ts index bd478e62..8d9b9290 100644 --- a/src/components/lazyLoadQueue.ts +++ b/src/components/lazyLoadQueue.ts @@ -73,7 +73,7 @@ export default class LazyLoadQueue { this.debug && this.log('will load media', this.lockPromise, item); try { - if(this.lockPromise) { + if(this.lockPromise && false) { let perf = performance.now(); await this.lockPromise; diff --git a/src/components/misc.ts b/src/components/misc.ts index 5fabc9af..23e24c7c 100644 --- a/src/components/misc.ts +++ b/src/components/misc.ts @@ -3,6 +3,8 @@ import Config from "../lib/config"; let rippleClickID = 0; export function ripple(elem: HTMLElement, callback: (id: number) => Promise = () => Promise.resolve(), onEnd: (id: number) => void = null) { + if(elem.querySelector('.c-ripple')) return; + let r = document.createElement('div'); r.classList.add('c-ripple'); @@ -99,6 +101,19 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise { + toastEl.remove(); + delete toastEl.dataset.timeout; + }, 3000); +} + let loadedURLs: {[url: string]: boolean} = {}; let set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLSourceElement, url: string) => { if(elem instanceof HTMLImageElement || elem instanceof HTMLSourceElement) elem.src = url; @@ -117,10 +132,12 @@ export function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGIma } else { let loader = new Image(); loader.src = url; - loader.onload = () => { + //let perf = performance.now(); + loader.addEventListener('load', () => { set(elem, url); loadedURLs[url] = true; - }; + //console.log('onload:', url, performance.now() - perf); + }); } return false; @@ -336,7 +353,7 @@ let onMouseMove = (e: MouseEvent) => { }; let onClick = (e: MouseEvent) => { - e.preventDefault(); + //e.preventDefault(); closeBtnMenu(); }; diff --git a/src/components/scrollable_new.ts b/src/components/scrollable_new.ts index f40249c8..8b414635 100644 --- a/src/components/scrollable_new.ts +++ b/src/components/scrollable_new.ts @@ -367,48 +367,39 @@ export default class Scrollable { return !!element.parentElement; } - public scrollIntoView(element: HTMLElement, smooth = true, fromUp = false) { + public scrollIntoView(element: HTMLElement, smooth = true) { if(element.parentElement && !this.scrollLocked) { - let scrollTop = this.scrollTop; + let isFirstUnread = element.classList.contains('is-first-unread'); let offsetTop = element.offsetTop; - let clientHeight = this.container.clientHeight; + if(!smooth && isFirstUnread) { + this.scrollTo(offsetTop, false); + return; + } + let clientHeight = this.container.clientHeight; let height = element.scrollHeight; - let diff = (clientHeight - height) / 2; + offsetTop -= (clientHeight - height) / 2; + + this.scrollTo(offsetTop, smooth); + } + } - /* if(scrollTop < offsetTop) { - offsetTop += diff; - } else { */ - offsetTop -= diff; - //} + public scrollTo(top: number, smooth = true) { + if(this.scrollLocked) return; - if(element.dataset.timeout) { - clearTimeout(+element.dataset.timeout); - element.classList.remove('is-selected'); - void element.offsetWidth; // reflow - } - element.classList.add('is-selected'); - element.dataset.timeout = '' + setTimeout(() => { - element.classList.remove('is-selected'); - delete element.dataset.timeout; - }, 2000); + let scrollTop = this.scrollTop; + if(scrollTop == Math.floor(top)) { + return; + } - if(scrollTop == Math.floor(offsetTop)) { - return; - } + if(this.scrollLocked) clearTimeout(this.scrollLocked); + this.scrollLocked = setTimeout(() => { + this.scrollLocked = 0; + this.onScroll(); + }, 468); - if(this.scrollLocked) clearTimeout(this.scrollLocked); - this.scrollLocked = setTimeout(() => { - this.scrollLocked = 0; - this.onScroll(); - }, 468); - if(fromUp) { - this.container.scrollTo({behavior: 'auto', top: 0}); - } - this.container.scrollTo({behavior: smooth ? 'smooth' : 'auto', top: offsetTop}); - //element.scrollIntoView({behavior: 'smooth', block: 'center'}); - } + this.container.scrollTo({behavior: smooth ? 'smooth' : 'auto', top}); } public removeElement(element: Element) { diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index c8c527c7..1e9fabc2 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -15,7 +15,6 @@ import { CancellablePromise } from '../lib/polyfill'; import { renderImageFromUrl } from './misc'; import appMessagesManager from '../lib/appManagers/appMessagesManager'; import { Layouter, RectPart } from './groupedLayout'; -import { Poll, PollResults } from '../lib/appManagers/appPollsManager'; import PollElement from './poll'; export type MTDocument = { @@ -135,47 +134,43 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai } } - let loadVideo = () => { - let promise = appDocsManager.downloadDoc(doc); - + let loadVideo = async() => { if(message.media.preloader) { // means upload message.media.preloader.attach(container); } else if(!doc.downloaded) { let preloader = new ProgressivePreloader(container, true); + let promise = appDocsManager.downloadDoc(doc); preloader.attach(container, true, promise); + await promise; } - - return promise.then(blob => { - if(middleware && !middleware()) { - return; - } - //return; - - //console.log('loaded doc:', doc, doc.url, blob, container); - - renderImageFromUrl(source, doc.url); - source.type = doc.mime_type; - video.append(source); + if(middleware && !middleware()) { + return; + } - if(!withTail) { - if(img && container.contains(img)) { - container.removeChild(img); - } + console.log('loaded doc:', doc, doc.url, container); + + renderImageFromUrl(source, doc.url); + source.type = doc.mime_type; + video.append(source); - container.append(video); - } - - if(doc.type == 'gif') { - video.autoplay = true; - video.loop = true; - } 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(!withTail) { + if(img && container.contains(img)) { + container.removeChild(img); } - }); + + container.append(video); + } + + if(doc.type == 'gif') { + video.autoplay = true; + video.loop = true; + } 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) { @@ -826,11 +821,11 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: ( if(!downloaded && (!div.firstElementChild || div.firstElementChild.tagName != 'IMG')) { img.style.opacity = '' + 0; - img.onload = () => { + img.addEventListener('load', () => { window.requestAnimationFrame(() => { img.style.opacity = ''; }); - }; + }); } if(!doc.url) { @@ -889,8 +884,8 @@ export function wrapReply(title: string, subtitle: string, message?: any) { } appPhotosManager.preloadPhoto(photo, appPhotosManager.choosePhotoSize(photo, 32, 32)) - .then(blob => { - renderImageFromUrl(replyMedia, photo._ == 'photo' ? photo.url : URL.createObjectURL(blob)); + .then(() => { + renderImageFromUrl(replyMedia, photo._ == 'photo' ? photo.url : appPhotosManager.getDocumentCachedThumb(photo.id).url); }); replyContent.append(replyMedia); @@ -1004,34 +999,6 @@ export function wrapAlbum({groupID, attachmentDiv, middleware, uploading, lazyLo }); } - /* let load = () => appPhotosManager.preloadPhoto(media._ == 'photo' ? media.id : media, size) - .then((blob) => { - if(middleware && !middleware()) { - console.warn('peer changed'); - return; - } - - if(!uploading) { - preloader.detach(); - } - - if(media && media.url) { - renderImageFromUrl(div, media.url); - } else { - let url = URL.createObjectURL(blob); - - let img = new Image(); - img.src = url; - img.onload = () => { - div.style.backgroundImage = 'url(' + url + ')'; - }; - } - - //div.style.backgroundImage = 'url(' + url + ')'; - }); - - load(); */ - // @ts-ignore //div.style.backgroundColor = '#' + Math.floor(Math.random() * (2 ** 24 - 1)).toString(16).padStart(6, '0'); diff --git a/src/lib/appManagers/AppInlineBotsManager.ts b/src/lib/appManagers/AppInlineBotsManager.ts new file mode 100644 index 00000000..576e309d --- /dev/null +++ b/src/lib/appManagers/AppInlineBotsManager.ts @@ -0,0 +1,421 @@ +import appMessagesManager from "./appMessagesManager"; +import apiManagerProxy from "../mtproto/mtprotoworker"; +import { appPeersManager } from "../services"; +import appMessagesIDsManager from "./appMessagesIDsManager"; +import { toast } from "../../components/misc"; +import { RichTextProcessor } from "../richtextprocessor"; + +export class AppInlineBotsManager { + /* private inlineResults: any = {}; + + function getPopularBots () { + return Storage.get('inline_bots_popular').then(function (bots) { + var result = [] + var i, len + var userID + if (bots && bots.length) { + var now = tsNow(true) + for (i = 0, len = bots.length; i < len; i++) { + if ((now - bots[i][3]) > 14 * 86400) { + continue + } + userID = bots[i][0] + if (!AppUsersManager.hasUser(userID)) { + AppUsersManager.saveApiUser(bots[i][1]) + } + result.push({id: userID, rate: bots[i][2], date: bots[i][3]}) + } + } + return result + }) + } + + function pushPopularBot (id) { + getPopularBots().then(function (bots) { + var exists = false + var count = bots.length + var result = [] + for (var i = 0; i < count; i++) { + if (bots[i].id == id) { + exists = true + bots[i].rate++ + bots[i].date = tsNow(true) + } + var user = AppUsersManager.getUser(bots[i].id) + result.push([bots[i].id, user, bots[i].rate, bots[i].date]) + } + if (exists) { + result.sort(function (a, b) { + return b[2] - a[2] + }) + } else { + if (result.length > 15) { + result = result.slice(0, 15) + } + result.push([id, AppUsersManager.getUser(id), 1, tsNow(true)]) + } + ConfigStorage.set({inline_bots_popular: result}) + + $rootScope.$broadcast('inline_bots_popular') + }) + } + + function resolveInlineMention (username) { + return AppPeersManager.resolveUsername(username).then(function (peerID) { + if (peerID > 0) { + var bot = AppUsersManager.getUser(peerID) + if (bot.pFlags.bot && bot.bot_inline_placeholder !== undefined) { + var resolvedBot = { + username: username, + id: peerID, + placeholder: bot.bot_inline_placeholder + } + if (bot.pFlags.bot_inline_geo && + GeoLocationManager.isAvailable()) { + return checkGeoLocationAccess(peerID).then(function () { + return GeoLocationManager.getPosition().then(function (coords) { + resolvedBot.geo = coords + return qSync.when(resolvedBot) + }) + })['catch'](function () { + return qSync.when(resolvedBot) + }) + } + return qSync.when(resolvedBot) + } + } + return $q.reject() + }, function (error) { + error.handled = true + return $q.reject(error) + }) + } + + function getInlineResults (peerID, botID, query, geo, offset) { + return MtpApiManager.invokeApi('messages.getInlineBotResults', { + flags: 0 | (geo ? 1 : 0), + bot: AppUsersManager.getUserInput(botID), + peer: AppPeersManager.getInputPeerByID(peerID), + query: query, + geo_point: geo && {_: 'inputGeoPoint', lat: geo['lat'], long: geo['long']}, + offset: offset + }, {timeout: 1, stopTime: -1, noErrorBox: true}).then(function (botResults) { + var queryID = botResults.query_id + delete botResults._ + delete botResults.flags + delete botResults.query_id + + if (botResults.switch_pm) { + botResults.switch_pm.rText = RichTextProcessor.wrapRichText(botResults.switch_pm.text, {noLinebreaks: true, noLinks: true}) + } + + angular.forEach(botResults.results, function (result) { + var qID = queryID + '_' + result.id + result.qID = qID + result.botID = botID + + result.rTitle = RichTextProcessor.wrapRichText(result.title, {noLinebreaks: true, noLinks: true}) + result.rDescription = RichTextProcessor.wrapRichText(result.description, {noLinebreaks: true, noLinks: true}) + result.initials = (result.url || result.title || result.type || '').substr(0, 1) + + if (result.document) { + AppDocsManager.saveDoc(result.document) + } + if (result.photo) { + AppPhotosManager.savePhoto(result.photo) + } + + inlineResults[qID] = result + }) + return botResults + }) + } + + function regroupWrappedResults (results, rowW, rowH) { + if (!results || + !results[0] || + ['photo', 'gif', 'sticker'].indexOf(results[0].type) == -1) { + return + } + var ratios = [] + angular.forEach(results, function (result) { + var w + var h, doc + var photo + if (result._ == 'botInlineMediaResult') { + if (doc = result.document) { + w = result.document.w + h = result.document.h + } + else if (photo = result.photo) { + var photoSize = (photo.sizes || [])[0] + w = photoSize && photoSize.w + h = photoSize && photoSize.h + } + }else { + w = result.w + h = result.h + } + if (!w || !h) { + w = h = 1 + } + ratios.push(w / h) + }) + + var rows = [] + var curCnt = 0 + var curW = 0 + angular.forEach(ratios, function (ratio) { + var w = ratio * rowH + curW += w + if (!curCnt || curCnt < 4 && curW < (rowW * 1.1)) { + curCnt++ + } else { + rows.push(curCnt) + curCnt = 1 + curW = w + } + }) + if (curCnt) { + rows.push(curCnt) + } + + var i = 0 + var thumbs = [] + var lastRowI = rows.length - 1 + angular.forEach(rows, function (rowCnt, rowI) { + var lastRow = rowI == lastRowI + var curRatios = ratios.slice(i, i + rowCnt) + var sumRatios = 0 + angular.forEach(curRatios, function (ratio) { + sumRatios += ratio + }) + angular.forEach(curRatios, function (ratio, j) { + var thumbH = rowH + var thumbW = rowW * ratio / sumRatios + var realW = thumbH * ratio + if (lastRow && thumbW > realW) { + thumbW = realW + } + var result = results[i + j] + result.thumbW = Math.floor(thumbW) - 2 + result.thumbH = Math.floor(thumbH) - 2 + }) + + i += rowCnt + }) + } + + function switchToPM (fromPeerID, botID, startParam) { + var peerString = AppPeersManager.getPeerString(fromPeerID) + var setHash = {} + setHash['inline_switch_pm' + botID] = {peer: peerString, time: tsNow()} + Storage.set(setHash) + $rootScope.$broadcast('history_focus', {peerString: AppPeersManager.getPeerString(botID)}) + AppMessagesManager.startBot(botID, 0, startParam) + } + + function checkSwitchReturn (botID) { + var bot = AppUsersManager.getUser(botID) + if (!bot || !bot.pFlags.bot || !bot.bot_inline_placeholder) { + return qSync.when(false) + } + var key = 'inline_switch_pm' + botID + return Storage.get(key).then(function (peerData) { + if (peerData) { + Storage.remove(key) + if (tsNow() - peerData.time < 3600000) { + return peerData.peer + } + } + return false + }) + } + + function switchInlineQuery (botID, toPeerString, query) { + $rootScope.$broadcast('history_focus', { + peerString: toPeerString, + attachment: { + _: 'inline_query', + mention: '@' + AppUsersManager.getUser(botID).username, + query: query + } + }) + } + + function switchInlineButtonClick (id, button) { + var message = AppMessagesManager.getMessage(id) + var botID = message.viaBotID || message.fromID + if (button.pFlags && button.pFlags.same_peer) { + var peerID = AppMessagesManager.getMessagePeer(message) + var toPeerString = AppPeersManager.getPeerString(peerID) + switchInlineQuery(botID, toPeerString, button.query) + return + } + return checkSwitchReturn(botID).then(function (retPeerString) { + if (retPeerString) { + return switchInlineQuery(botID, retPeerString, button.query) + } + PeersSelectService.selectPeer({ + canSend: true + }).then(function (toPeerString) { + return switchInlineQuery(botID, toPeerString, button.query) + }) + }) + } */ + + public callbackButtonClick(mid: number, button: any) { + let message = appMessagesManager.getMessage(mid); + let peerID = appMessagesManager.getMessagePeer(message); + + return apiManagerProxy.invokeApi('messages.getBotCallbackAnswer', { + flags: 1, + peer: appPeersManager.getInputPeerByID(peerID), + msg_id: appMessagesIDsManager.getMessageLocalID(mid), + data: button.data + }, {timeout: 1, stopTime: -1, noErrorBox: true}).then((callbackAnswer: any) => { + if(typeof callbackAnswer.message === 'string' && callbackAnswer.message.length) { + toast(RichTextProcessor.wrapRichText(callbackAnswer.message, {noLinks: true, noLinebreaks: true})); + } + + console.log('callbackButtonClick callbackAnswer:', callbackAnswer); + }); + } + + /* function gameButtonClick (id) { + var message = AppMessagesManager.getMessage(id) + var peerID = AppMessagesManager.getMessagePeer(message) + + return MtpApiManager.invokeApi('messages.getBotCallbackAnswer', { + flags: 2, + peer: AppPeersManager.getInputPeerByID(peerID), + msg_id: AppMessagesIDsManager.getMessageLocalID(id) + }, {timeout: 1, stopTime: -1, noErrorBox: true}).then(function (callbackAnswer) { + if (typeof callbackAnswer.message === 'string' && + callbackAnswer.message.length) { + showCallbackMessage(callbackAnswer.message, callbackAnswer.pFlags.alert) + } + else if (typeof callbackAnswer.url === 'string') { + AppGamesManager.openGame(message.media.game.id, id, callbackAnswer.url) + } + }) + } + + function sendInlineResult (peerID, qID, options) { + var inlineResult = inlineResults[qID] + if (inlineResult === undefined) { + return false + } + pushPopularBot(inlineResult.botID) + var splitted = qID.split('_') + var queryID = splitted.shift() + var resultID = splitted.join('_') + options = options || {} + options.viaBotID = inlineResult.botID + options.queryID = queryID + options.resultID = resultID + if (inlineResult.send_message.reply_markup) { + options.reply_markup = inlineResult.send_message.reply_markup + } + + if (inlineResult.send_message._ == 'botInlineMessageText') { + options.entities = inlineResult.send_message.entities + AppMessagesManager.sendText(peerID, inlineResult.send_message.message, options) + } else { + var caption = '' + var inputMedia = false + switch (inlineResult.send_message._) { + case 'botInlineMessageMediaAuto': + caption = inlineResult.send_message.caption + if (inlineResult._ == 'botInlineMediaResult') { + var doc = inlineResult.document + var photo = inlineResult.photo + if (doc) { + inputMedia = { + _: 'inputMediaDocument', + id: {_: 'inputDocument', id: doc.id, access_hash: doc.access_hash}, + caption: caption + } + } else { + inputMedia = { + _: 'inputMediaPhoto', + id: {_: 'inputPhoto', id: photo.id, access_hash: photo.access_hash}, + caption: caption + } + } + } + break + + case 'botInlineMessageMediaGeo': + inputMedia = { + _: 'inputMediaGeoPoint', + geo_point: { + _: 'inputGeoPoint', + 'lat': inlineResult.send_message.geo['lat'], + 'long': inlineResult.send_message.geo['long'] + } + } + break + + case 'botInlineMessageMediaVenue': + inputMedia = { + _: 'inputMediaVenue', + geo_point: { + _: 'inputGeoPoint', + 'lat': inlineResult.send_message.geo['lat'], + 'long': inlineResult.send_message.geo['long'] + }, + title: inlineResult.send_message.title, + address: inlineResult.send_message.address, + provider: inlineResult.send_message.provider, + venue_id: inlineResult.send_message.venue_id + } + break + + case 'botInlineMessageMediaContact': + inputMedia = { + _: 'inputMediaContact', + phone_number: inlineResult.send_message.phone_number, + first_name: inlineResult.send_message.first_name, + last_name: inlineResult.send_message.last_name + } + break + } + if (!inputMedia) { + inputMedia = { + _: 'messageMediaPending', + type: inlineResult.type, + file_name: inlineResult.title || inlineResult.content_url || inlineResult.url, + size: 0, + progress: {percent: 30, total: 0} + } + } + AppMessagesManager.sendOther(peerID, inputMedia, options) + } + } + + function checkGeoLocationAccess (botID) { + var key = 'bot_access_geo' + botID + return Storage.get(key).then(function (geoAccess) { + if (geoAccess && geoAccess.granted) { + return true + } + return ErrorService.confirm({ + type: 'BOT_ACCESS_GEO_INLINE' + }).then(function () { + var setHash = {} + setHash[key] = {granted: true, time: tsNow()} + Storage.set(setHash) + return true + }, function () { + var setHash = {} + setHash[key] = {denied: true, time: tsNow()} + Storage.set(setHash) + return $q.reject() + }) + }) + } */ +} + +const appInlineBotsManager = new AppInlineBotsManager(); +export default appInlineBotsManager; \ No newline at end of file diff --git a/src/lib/appManagers/apiUpdatesManager.ts b/src/lib/appManagers/apiUpdatesManager.ts index 8075b7c7..9694b975 100644 --- a/src/lib/appManagers/apiUpdatesManager.ts +++ b/src/lib/appManagers/apiUpdatesManager.ts @@ -13,9 +13,9 @@ export class ApiUpdatesManager { syncPending: any, syncLoading: any, - seq?: any, - pts?: any, - date?: any + seq?: number, + pts?: number, + date?: number } = { pendingPtsUpdates: [], pendingSeqUpdates: {}, @@ -24,14 +24,7 @@ export class ApiUpdatesManager { }; public channelStates: any = {}; - public myID = 0; private attached = false; - - constructor() { - apiManager.getUserID().then((id) => { - this.myID = id; - }); - } public popPendingSeqUpdate() { var nextSeq = this.updatesState.seq + 1; @@ -141,10 +134,10 @@ export class ApiUpdatesManager { case 'updateShortMessage': case 'updateShortChatMessage': var isOut = updateMessage.flags & 2; - var fromID = updateMessage.from_id || (isOut ? this.myID : updateMessage.user_id); + var fromID = updateMessage.from_id || (isOut ? $rootScope.myID : updateMessage.user_id); var toID = updateMessage.chat_id ? -updateMessage.chat_id - : (isOut ? updateMessage.user_id : this.myID); + : (isOut ? updateMessage.user_id : $rootScope.myID); this.processUpdate({ _: 'updateNewMessage', @@ -500,24 +493,31 @@ export class ApiUpdatesManager { $rootScope.$broadcast('apiUpdate', update); } - public attach() { + public attach(state: Pick) { if(this.attached) return; this.attached = true; apiManager.setUpdatesProcessor(this.processUpdateMessage.bind(this)); - apiManager.invokeApi('updates.getState', {}, {noErrorBox: true}).then((stateResult: any) => { - this.updatesState.seq = stateResult.seq; - this.updatesState.pts = stateResult.pts; - this.updatesState.date = stateResult.date; - setTimeout(() => { - this.updatesState.syncLoading = false; - }, 1000); - - // updatesState.seq = 1 - // updatesState.pts = stateResult.pts - 5000 - // updatesState.date = 1 - // getDifference() - }); + + if(!state) { + apiManager.invokeApi('updates.getState', {}, {noErrorBox: true}).then((stateResult: any) => { + this.updatesState.seq = stateResult.seq; + this.updatesState.pts = stateResult.pts; + this.updatesState.date = stateResult.date; + setTimeout(() => { + this.updatesState.syncLoading = false; + }, 1000); + + // updatesState.seq = 1 + // updatesState.pts = stateResult.pts - 5000 + // updatesState.date = 1 + // getDifference() + }); + } else { + Object.assign(this.updatesState, state); + this.updatesState.syncLoading = false; + this.getDifference(); + } } } diff --git a/src/lib/appManagers/appChatsManager.ts b/src/lib/appManagers/appChatsManager.ts index 9c201a91..989235b6 100644 --- a/src/lib/appManagers/appChatsManager.ts +++ b/src/lib/appManagers/appChatsManager.ts @@ -72,7 +72,7 @@ export class AppChatsManager { let titleWords = SearchIndexManager.cleanSearchText(apiChat.title || '', false).split(' '); let firstWord = titleWords.shift(); let lastWord = titleWords.pop(); - apiChat.initials = firstWord.charAt(0) + (lastWord ? lastWord.charAt(0) : firstWord.charAt(1)); + apiChat.initials = firstWord.charAt(0) + (lastWord ? lastWord.charAt(0) : ''); if(apiChat.pFlags === undefined) { apiChat.pFlags = {}; diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index 198af71e..45be931b 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -384,8 +384,6 @@ export class AppDialogsManager { public chatsArchivedContainer = document.getElementById('chats-archived-container') as HTMLDivElement; public chatsContainer = document.getElementById('chats-container') as HTMLDivElement; - private chatsArchivedOffsetIndex = 0; - private chatsOffsetIndex = 0; private chatsPreloader: HTMLDivElement; //private chatsLoadCount = 0; //private loadDialogsPromise: Promise; @@ -544,10 +542,30 @@ export class AppDialogsManager { } }); - this.loadDialogs().then(result => { - this.setPinnedDelimiter(); - //appSidebarLeft.onChatsScroll(); - this.loadDialogs(true); + $rootScope.$on('peer_changed', (e: CustomEvent) => { + let peerID = e.detail; + + let lastPeerID = this.lastActiveListElement && +this.lastActiveListElement.getAttribute('data-peerID'); + if(this.lastActiveListElement && lastPeerID != peerID) { + this.lastActiveListElement.classList.remove('active'); + this.lastActiveListElement = null; + } + + if(lastPeerID != peerID) { + let dom = this.getDialogDom(peerID); + if(dom) { + this.lastActiveListElement = dom.listEl; + dom.listEl.classList.add('active'); + } + } + }); + + appMessagesManager.loaded.then(() => { + this.loadDialogs().then(result => { + this.setPinnedDelimiter(); + //appSidebarLeft.onChatsScroll(); + this.loadDialogs(true); + }); }); } @@ -559,30 +577,30 @@ export class AppDialogsManager { if(this.loadDialogsPromise/* || 1 == 1 */) return this.loadDialogsPromise; (archived ? this.chatsArchivedContainer : this.chatsContainer).append(this.chatsPreloader); - - //let offset = appMessagesManager.generateDialogIndex();/* appMessagesManager.dialogsNum */; - - let offset = archived ? this.chatsArchivedOffsetIndex : this.chatsOffsetIndex; - //let offset = 0; - let scroll = archived ? this.scrollArchived : this.scroll; + let storage = appMessagesManager.dialogsStorage[+archived] || []; + let offsetIndex = 0; + + for(let i = storage.length - 1; i >= 0; --i) { + let dialog = storage[i]; + if(this.getDialogDom(dialog.peerID)) { + offsetIndex = dialog.index; + break; + } + } + //let offset = storage[storage.length - 1]?.index || 0; try { console.time('getDialogs time'); let loadCount = 50/*this.chatsLoadCount */; - this.loadDialogsPromise = appMessagesManager.getConversations(offset, loadCount, +archived); + this.loadDialogsPromise = appMessagesManager.getConversations(offsetIndex, loadCount, +archived); let result = await this.loadDialogsPromise; console.timeEnd('getDialogs time'); if(result && result.dialogs && result.dialogs.length) { - let index = result.dialogs[result.dialogs.length - 1].index; - - if(archived) this.chatsArchivedOffsetIndex = index; - else this.chatsOffsetIndex = index; - result.dialogs.forEach((dialog: any) => { this.addDialog(dialog); }); @@ -598,7 +616,7 @@ export class AppDialogsManager { this.archivedCount.innerText = '' + count; } */ - this.log('getDialogs ' + loadCount + ' dialogs by offset:', offset, result, this.scroll.length); + this.log('getDialogs ' + loadCount + ' dialogs by offset:', offsetIndex, result, this.scroll.length, archived); this.scroll.onScroll(); } catch(err) { this.log.error(err); @@ -650,14 +668,14 @@ export class AppDialogsManager { if(onFound) onFound(); let peerID = +elem.getAttribute('data-peerID'); - let lastMsgID = +elem.dataset.mid; + let lastMsgID = +elem.dataset.mid || 0; if(!samePeer) { elem.classList.add('active'); this.lastActiveListElement = elem; } - result = appImManager.setPeer(peerID, lastMsgID, true); + result = appImManager.setPeer(peerID, lastMsgID); if(result instanceof Promise) { this.lastGoodClickID = this.lastClickID; @@ -721,7 +739,7 @@ export class AppDialogsManager { public setPinnedDelimiter() { let index = -1; - let dialogs = appMessagesManager.dialogsStorage.dialogs[0]; + let dialogs = appMessagesManager.dialogsStorage[0]; for(let dialog of dialogs) { if(dialog.pFlags?.pinned) { index++; @@ -766,7 +784,7 @@ export class AppDialogsManager { if(lastMessage._ == 'messageEmpty') { dom.lastMessageSpan.innerHTML = ''; dom.lastTimeSpan.innerHTML = ''; - dom.listEl.removeAttribute('data-mid'); + delete dom.listEl.dataset.mid; return; } @@ -853,10 +871,10 @@ export class AppDialogsManager { dom.lastTimeSpan.innerHTML = timeStr; } else dom.lastTimeSpan.innerHTML = ''; - dom.listEl.setAttribute('data-mid', lastMessage.mid); - if(this.doms[peerID] || this.domsArchived[peerID]) { this.setUnreadMessages(dialog); + } else { // means search + dom.listEl.dataset.mid = lastMessage.mid; } } diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 1e7304f6..30cc1e1c 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -1,8 +1,8 @@ //import apiManager from '../mtproto/apiManager'; import apiManager from '../mtproto/mtprotoworker'; -import { $rootScope, numberWithCommas, findUpClassName, formatNumber, placeCaretAtEnd, findUpTag, langPack } from "../utils"; +import { $rootScope, numberWithCommas, findUpClassName, formatNumber, placeCaretAtEnd, findUpTag, langPack, whichChild } from "../utils"; import appUsersManager from "./appUsersManager"; -import appMessagesManager from "./appMessagesManager"; +import appMessagesManager, { Dialog } from "./appMessagesManager"; import appPeersManager from "./appPeersManager"; import appProfileManager from "./appProfileManager"; import appDialogsManager from "./appDialogsManager"; @@ -19,7 +19,7 @@ import appMessagesIDsManager from "./appMessagesIDsManager"; import apiUpdatesManager from './apiUpdatesManager'; import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply, wrapAlbum, wrapPoll } from '../../components/wrappers'; import ProgressivePreloader from '../../components/preloader'; -import { openBtnMenu, formatPhoneNumber, positionMenu } from '../../components/misc'; +import { openBtnMenu, formatPhoneNumber, positionMenu, ripple } from '../../components/misc'; import { ChatInput } from '../../components/chatInput'; //import Scrollable from '../../components/scrollable'; import Scrollable from '../../components/scrollable_new'; @@ -29,6 +29,7 @@ import appDocsManager from './appDocsManager'; import appForward from '../../components/appForward'; import appStickersManager from './appStickersManager'; import AvatarElement from '../../components/avatar'; +import appInlineBotsManager from './AppInlineBotsManager'; console.log('appImManager included!'); @@ -117,7 +118,14 @@ export class AppImManager { private loadedTopTimes = 0; private loadedBottomTimes = 0; - + + private messagesQueuePromise: Promise = null; + private messagesQueue: {message: any, bubble: HTMLDivElement, reverse: boolean, promises: Promise[]}[] = []; + private messagesQueueOnRender: () => void = null; + + private peerChanged: boolean; + private firstUnreadBubble: HTMLDivElement = null; + constructor() { /* if(!lottieLoader.loaded) { lottieLoader.loadLottie(); @@ -332,7 +340,7 @@ export class AppImManager { appSidebarRight.toggleSidebar(true); }); - this.chatInner.addEventListener('click', (e) => { + this.bubblesContainer.addEventListener('click', (e) => { let target = e.target as HTMLElement; let bubble: HTMLDivElement = null; try { @@ -349,7 +357,11 @@ export class AppImManager { //this.log('chatInner click:', target); if(target.tagName == 'SPAN') { - (target.parentElement.querySelector('video') as HTMLElement).click(); // hot-fix for time and play button + let video = (target.parentElement.querySelector('video') as HTMLElement); + if(video) { + video.click(); // hot-fix for time and play button + } + return; } @@ -494,7 +506,7 @@ export class AppImManager { document.body.addEventListener('keydown', onKeyDown); - this.chatInner.addEventListener('contextmenu', e => { + this.bubblesContainer.addEventListener('contextmenu', e => { let bubble: HTMLDivElement = null; try { @@ -629,7 +641,7 @@ export class AppImManager { setInterval(() => this.setPeerStatus(), 60e3); this.setScroll(); - apiUpdatesManager.attach(); + //apiUpdatesManager.attach(); this.datesIntersectionObserver = new IntersectionObserver((entries) => { //this.log('intersection', entries); @@ -774,6 +786,8 @@ export class AppImManager { public onScroll() { if(this.onScrollRAF) window.cancelAnimationFrame(this.onScrollRAF); + //if(this.scrollable.scrollLocked) return; + this.onScrollRAF = window.requestAnimationFrame(() => { lottieLoader.checkAnimations(false, 'chat'); @@ -904,9 +918,8 @@ export class AppImManager { } } - public cleanup() { + public cleanup(bubblesToo = false) { ////console.time('appImManager cleanup'); - this.peerID = $rootScope.selectedPeerID = 0; this.scrolledAll = false; this.scrolledAllDown = false; this.muted = false; @@ -921,16 +934,21 @@ export class AppImManager { // clear input this.chatInputC.messageInput.innerHTML = ''; this.chatInputC.replyElements.cancelBtn.click(); - + // clear messages - this.chatInner.innerHTML = ''; + if(bubblesToo) { + this.scrollable.container.innerHTML = ''; + } + + this.peerChanged = false; + this.firstUnreadBubble = null; + + /* this.messagesQueue.length = 0; + this.messagesQueuePromise = null; */ lottieLoader.checkAnimations(false, 'chat', true); this.getHistoryTopPromise = this.getHistoryBottomPromise = undefined; - - //this.scrollable.setVirtualContainer(this.chatInner); - this.scrollable.setVirtualContainer(null); this.datesIntersectionObserver.disconnect(); this.lastDateMessageDiv = null; @@ -942,18 +960,16 @@ export class AppImManager { ////console.timeEnd('appImManager cleanup'); } - public setPeer(peerID: number, lastMsgID = 0, fromClick = false) { + public setPeer(peerID: number, lastMsgID = 0) { console.time('appImManager setPeer'); console.time('appImManager setPeer pre promise'); ////console.time('appImManager: pre render start'); if(peerID == 0) { appSidebarRight.toggleSidebar(false); this.topbar.style.display = this.chatInput.style.display = this.goDownBtn.style.display = 'none'; - this.cleanup(); - if(appDialogsManager.lastActiveListElement) { - appDialogsManager.lastActiveListElement.classList.remove('active'); - appDialogsManager.lastActiveListElement = null; - } + this.cleanup(true); + this.peerID = $rootScope.selectedPeerID = 0; + $rootScope.$broadcast('peer_changed', this.peerID); return false; } @@ -961,9 +977,9 @@ export class AppImManager { if(this.setPeerPromise && samePeer) return this.setPeerPromise; - if(lastMsgID) { + /* if(lastMsgID) { appMessagesManager.readHistory(peerID, lastMsgID); // lol - } + } */ if(samePeer) { if(!testScroll && !lastMsgID) { @@ -977,8 +993,8 @@ export class AppImManager { this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight); this.scroll.scrollTop = this.scroll.scrollHeight; } else { - //this.bubbles[lastMsgID].scrollIntoView(); this.scrollable.scrollIntoView(this.bubbles[lastMsgID]); + this.highlightBubble(this.bubbles[lastMsgID]); } return true; @@ -986,72 +1002,21 @@ export class AppImManager { } else { appSidebarRight.searchCloseBtn.click(); } - - const maxBubbleID = Math.max(...Object.keys(this.bubbles).map(mid => +mid)); - // clear - this.cleanup(); - // set new this.peerID = $rootScope.selectedPeerID = peerID; - - // no dialog - /* if(!appMessagesManager.getDialogByPeerID(this.peerID).length) { - this.log.error('No dialog by peerID:', this.peerID); - return Promise.reject(); - } */ - - let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0] || null; - //////this.log('setPeer peerID:', this.peerID, dialog, lastMsgID); - - this.avatarEl.setAttribute('peer', '' + this.peerID); - const isChannel = appPeersManager.isChannel(peerID); - const hasRights = isChannel && appChatsManager.hasRights(-peerID, 'send'); - if(hasRights) this.chatInner.classList.add('has-rights'); - else this.chatInner.classList.remove('has-rights'); - this.chatInput.style.display = !isChannel || hasRights ? '' : 'none'; - - this.chatInner.style.visibility = 'hidden'; - this.topbar.style.display = ''; - if(appPeersManager.isAnyGroup(peerID)) this.chatInner.classList.add('is-chat'); - else this.chatInner.classList.remove('is-chat'); - if(isChannel) this.chatInner.classList.add('is-channel'); - else this.chatInner.classList.remove('is-channel'); - this.pinnedMessageContainer.style.display = 'none'; - window.requestAnimationFrame(() => { - //this.chatInner.style.visibility = 'hidden'; - - let title = ''; - if(this.peerID == this.myID) title = 'Saved Messages'; - else title = appPeersManager.getPeerTitle(this.peerID); - //this.titleEl.innerHTML = appSidebarRight.profileElements.name.innerHTML = dom.titleSpan.innerHTML; - this.titleEl.innerHTML = appSidebarRight.profileElements.name.innerHTML = title; - 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); - - //if(appPeersManager.isAnyGroup(peerID)) this.chatInner.classList.add('is-chat'); - //else this.chatInner.classList.remove('is-chat'); - - if(!fromClick) { - if(!samePeer && appDialogsManager.lastActiveListElement) { - appDialogsManager.lastActiveListElement.classList.remove('active'); - } - - let dom = appDialogsManager.getDialogDom(this.peerID); - if(dom) { - appDialogsManager.lastActiveListElement = dom.listEl; - dom.listEl.classList.add('active'); - } + let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0] || null; + if(!lastMsgID && dialog) { + if(dialog.unread_count) { + lastMsgID = dialog.read_inbox_max_id; + } else { + lastMsgID = dialog.top_message; } - - this.setPeerStatus(true); - }); + } + //////this.log('setPeer peerID:', this.peerID, dialog, lastMsgID); const isJump = lastMsgID != dialog?.top_message; - // add last message, bc in getHistory will load < max_id const additionMsgID = isJump ? 0 : dialog.top_message; @@ -1061,55 +1026,103 @@ export class AppImManager { //////appSidebarRight.toggleSidebar(true); + const maxBubbleID = samePeer && Math.max(...Object.keys(this.bubbles).map(mid => +mid)); + + this.cleanup(); + this.chatInner = document.createElement('div'); + this.chatInner.id = 'bubbles-inner'; + this.scrollable.appendTo = this.chatInner; + + let {promise, cached} = this.getHistory(lastMsgID, true, isJump, additionMsgID); + + appSidebarRight.setPeer(this.peerID); + + // clear + if(!cached) { + this.scrollable.container.innerHTML = ''; + this.finishPeerChange(); + this.preloader.attach(this.bubblesContainer); + } + console.timeEnd('appImManager setPeer pre promise'); - this.preloader.attach(this.bubblesContainer); - return this.setPeerPromise = Promise.all([ - this.getHistory(lastMsgID, true, isJump, additionMsgID).then(() => { + + this.setPeerPromise = Promise.all([ + promise.then(() => { ////this.log('setPeer removing preloader'); - if(lastMsgID) { - if(!dialog || lastMsgID != dialog.top_message) { - let bubble = this.bubbles[lastMsgID]; - - let fromUp = maxBubbleID && maxBubbleID < lastMsgID; - if(bubble) { - if(this.scrollable.scrollLocked) { - clearTimeout(this.scrollable.scrollLocked); - this.scrollable.scrollLocked = 0; - } - - this.scrollable.scrollIntoView(bubble, samePeer, fromUp); - } else this.log.warn('no bubble by lastMsgID:', lastMsgID); - } else { - this.log('will scroll down 2'); - this.scroll.scrollTop = this.scroll.scrollHeight; + if(cached) { + this.scrollable.container.innerHTML = ''; + this.finishPeerChange(); + } else { + this.preloader.detach(); + } + + this.scrollable.container.append(this.chatInner); + + if(dialog && lastMsgID && lastMsgID != dialog.top_message && (this.bubbles[lastMsgID] || this.firstUnreadBubble)) { + if(this.scrollable.scrollLocked) { + clearTimeout(this.scrollable.scrollLocked); + this.scrollable.scrollLocked = 0; + } + + let fromUp = maxBubbleID > 0 && maxBubbleID < lastMsgID; + if(!fromUp && samePeer) { + this.scrollable.scrollTop = this.scrollable.scrollHeight; + } + + let forwardingUnread = dialog.read_inbox_max_id == lastMsgID; + let bubble = forwardingUnread ? (this.firstUnreadBubble || this.bubbles[lastMsgID]) : this.bubbles[lastMsgID]; + + this.scrollable.scrollIntoView(bubble, samePeer/* , fromUp */); + if(!forwardingUnread) { + this.highlightBubble(bubble); } + } else { + this.scrollable.scrollTop = this.scrollable.scrollHeight; } - if(!lastMsgID || (dialog && dialog.top_message == lastMsgID)) { + // warning + if(!lastMsgID || (dialog && (this.bubbles[dialog.top_message] || lastMsgID == dialog.top_message))) { this.scrolledAllDown = true; } - /* this.onScroll(); - this.scrollable.onScroll();*/ - - this.preloader.detach(); - this.chatInner.style.visibility = ''; + //this.chatInner.style.visibility = ''; + + /* let promises: Promise[] = []; + for(let i in this.bubbles) { + (Array.from(this.bubbles[i].querySelectorAll('img, image, video')) as HTMLImageElement[]).forEach(el => { + promises.push(new Promise((resolve, reject) => { + if(el.tagName == 'VIDEO') { + el.onloadeddata = () => { + console.log('onloadeddata'); + resolve(); + }; + } else { + el.onload = resolve; + } + })); + }); + } + + Promise.all(promises).then(() => { + window.requestAnimationFrame(() => { + window.requestAnimationFrame(() => { + //this.chatInner.style.visibility = ''; + let parent = oldChatInner.parentElement; + oldChatInner.remove(); + parent.append(this.chatInner); + this.scrollable.scrollTop = this.scrollable.scrollHeight; + }); + }); + }); */ console.timeEnd('appImManager setPeer'); - - //setTimeout(() => { - //appSidebarRight.fillProfileElements(); - //appSidebarRight.loadSidebarMedia(true); - //}, 500); - + return true; - })/* .catch(err => { - this.log.error(err); - }) */, - - appSidebarRight.fillProfileElements()/* , - appSidebarRight.loadSidebarMedia(true) */ + }).catch(err => { + this.log.error('getHistory promise error:', err); + throw err; + }) ]).catch(err => { this.log.error('setPeer promises error:', err); this.preloader.detach(); @@ -1121,6 +1134,55 @@ export class AppImManager { return !!res; }); + + //if(this.messagesQueuePromise) { + //appSidebarRight.setLoadMutex(this.setPeerPromise); + //} + + appSidebarRight.setLoadMutex(this.setPeerPromise); + appSidebarRight.loadSidebarMedia(true); + + return this.setPeerPromise; + } + + public finishPeerChange() { + if(this.peerChanged) return; + + let peerID = this.peerID; + this.peerChanged = true; + + this.avatarEl.setAttribute('peer', '' + this.peerID); + + const isChannel = appPeersManager.isChannel(peerID); + const hasRights = isChannel && appChatsManager.hasRights(-peerID, 'send'); + if(hasRights) this.chatInner.classList.add('has-rights'); + else this.chatInner.classList.remove('has-rights'); + + this.chatInput.style.display = !isChannel || hasRights ? '' : 'none'; + + this.topbar.style.display = ''; + + if(appPeersManager.isAnyGroup(peerID)) this.chatInner.classList.add('is-chat'); + else this.chatInner.classList.remove('is-chat'); + if(isChannel) this.chatInner.classList.add('is-channel'); + else this.chatInner.classList.remove('is-channel'); + + this.pinnedMessageContainer.style.display = 'none'; + + window.requestAnimationFrame(() => { + let title = ''; + if(this.peerID == this.myID) title = 'Saved Messages'; + else title = appPeersManager.getPeerTitle(this.peerID); + this.titleEl.innerHTML = appSidebarRight.profileElements.name.innerHTML = title; + + this.goDownBtn.style.display = ''; + + this.setPeerStatus(true); + }); + + appSidebarRight.fillProfileElements(); + + $rootScope.$broadcast('peer_changed', this.peerID); } public setTyping(action: any): Promise { @@ -1162,6 +1224,10 @@ export class AppImManager { let bubble = this.bubbles[id]; delete this.bubbles[id]; + if(this.firstUnreadBubble == bubble) { + this.firstUnreadBubble = null; + } + this.bubbleGroups.removeBubble(bubble, id); this.unreadedObserver.unobserve(bubble); this.scrollable.removeElement(bubble); @@ -1187,7 +1253,27 @@ export class AppImManager { //this.unreaded.push(msgID); this.renderMessage(message); }); - if(scrolledDown) this.scrollable.scrollTop = this.scrollable.scrollHeight; + + //if(scrolledDown) this.scrollable.scrollTop = this.scrollable.scrollHeight; + if(this.messagesQueuePromise && scrolledDown) { + this.messagesQueuePromise.then(() => { + this.scrollable.scrollTo(this.scrollable.scrollHeight, false); + }); + } + } + + public highlightBubble(element: HTMLDivElement) { + if(element.dataset.timeout) { + clearTimeout(+element.dataset.timeout); + element.classList.remove('is-selected'); + void element.offsetWidth; // reflow + } + + element.classList.add('is-selected'); + element.dataset.timeout = '' + setTimeout(() => { + element.classList.remove('is-selected'); + delete element.dataset.timeout; + }, 2000); } public getDateContainerByMessage(message: any, reverse: boolean) { @@ -1244,6 +1330,81 @@ export class AppImManager { return this.dateMessages[dateTimestamp]; } + + public renderMessagesQueue(message: any, bubble: HTMLDivElement, reverse: boolean) { + let promises: Promise[] = []; + (Array.from(bubble.querySelectorAll('img, image, video')) as HTMLImageElement[]).forEach(el => { + if(el.tagName == 'VIDEO') { + let source = el.firstElementChild as HTMLSourceElement; + if(!source || !source.src) { + this.log.warn('no source', el, source, 'src', source.src); + return; + } + } else if(el.complete || (!el.src && !el.getAttribute('href'))) return; + + let src = el.src || el.getAttributeNS(null, 'href'); + + let promise = new Promise((resolve, reject) => { + if(el.tagName == 'VIDEO') { + el.addEventListener('loadeddata', () => { + clearTimeout(timeout); + resolve(); + }); + } else { + el.addEventListener('load', () => { + clearTimeout(timeout); + resolve(); + }); + } + + let timeout = setTimeout(() => { + console.log('did not called', el, el.parentElement, el.complete, src); + reject(); + }, 5000); + }); + + promises.push(promise); + }); + + this.messagesQueue.push({message, bubble, reverse, promises}); + + if(!this.messagesQueuePromise) { + this.messagesQueuePromise = new Promise((resolve, reject) => { + setTimeout(() => { + let chatInner = this.chatInner; + let queue = this.messagesQueue.slice(); + this.messagesQueue.length = 0; + + let promises = queue.reduce((acc, {promises}) => acc.concat(promises), []); + console.log('promises to call', promises, queue); + Promise.all(promises).then(() => { + if(this.chatInner != chatInner) { + this.log.warn('chatInner changed!', this.chatInner, chatInner); + return reject('chatInner changed!'); + } + + if(this.messagesQueueOnRender) { + this.messagesQueueOnRender(); + } + + queue.forEach(({message, bubble, reverse}) => { + let dateMessage = this.getDateContainerByMessage(message, reverse); + if(reverse) { + dateMessage.container.insertBefore(bubble, dateMessage.div.nextSibling); + //this.scrollable.prepareElement(bubble, false); + } else { + dateMessage.container.append(bubble); + //this.scrollable.prepareElement(bubble, true); + } + }); + + resolve(); + this.messagesQueuePromise = null; + }, reject); + }, 0); + }); + } + } // reverse means top public renderMessage(message: any, reverse = false, multipleRender = false, bubble: HTMLDivElement = null, updatePosition = true) { @@ -1281,6 +1442,10 @@ export class AppImManager { bubble.className = 'bubble'; bubbleContainer = bubble.firstElementChild as HTMLDivElement; bubbleContainer.innerHTML = ''; + + if(bubble == this.firstUnreadBubble) { + bubble.classList.add('is-first-unread'); + } //bubble.innerHTML = ''; } @@ -1311,25 +1476,8 @@ export class AppImManager { let str = l[0].toUpperCase() == l[0] ? l : (name.innerText ? name.outerHTML + ' ' : '') + l; bubbleContainer.innerHTML = `
${str}
`; - /* if(!updatePosition) { - if(!multipleRender) { - this.scrollPosition.restore(); // лагает из-за этого - } - } else if(reverse) { - this.scrollable.prepend(bubble); - } else { - this.scrollable.append(bubble); - } */ - - let dateContainer = this.getDateContainerByMessage(message, reverse); - if(!updatePosition) { - - } else if(reverse) { - dateContainer.container.insertBefore(bubble, dateContainer.div.nextSibling); - //this.scrollable.prepareElement(bubble, false); - } else { - dateContainer.container.append(bubble); - //this.scrollable.prepareElement(bubble, true); + if(updatePosition) { + this.renderMessagesQueue(message, bubble, reverse); } return bubble; @@ -1427,6 +1575,79 @@ export class AppImManager { messageDiv.append(timeSpan); bubbleContainer.prepend(messageDiv); //bubble.prepend(timeSpan, messageDiv); // that's bad + + if(message.reply_markup && message.reply_markup._ == 'replyInlineMarkup' && message.reply_markup.rows && message.reply_markup.rows.length) { + let rows = message.reply_markup.rows; + + let containerDiv = document.createElement('div'); + containerDiv.classList.add('reply-markup'); + rows.forEach((row: any) => { + let buttons = row.buttons; + if(!buttons || !buttons.length) return; + + let rowDiv = document.createElement('div'); + rowDiv.classList.add('reply-markup-row'); + + buttons.forEach((button: any) => { + let text = RichTextProcessor.wrapRichText(button.text, {noLinks: true, noLinebreaks: true}); + + let buttonEl: HTMLButtonElement | HTMLAnchorElement; + + switch(button._) { + case 'keyboardButtonUrl': { + let from = appUsersManager.getUser(message.fromID); + let unsafe = !(from && from.pFlags && from.pFlags.verified); + let url = RichTextProcessor.wrapUrl(button.url, unsafe); + buttonEl = document.createElement('a'); + buttonEl.href = url; + buttonEl.rel = 'noopener noreferrer'; + buttonEl.target = '_blank'; + buttonEl.classList.add('is-link', 'tgico'); + + break; + } + + default: { + buttonEl = document.createElement('button'); + break; + } + } + + buttonEl.classList.add('reply-markup-button', 'rp'); + buttonEl.innerHTML = text; + + ripple(buttonEl); + + rowDiv.append(buttonEl); + }); + + containerDiv.append(rowDiv); + }); + + containerDiv.addEventListener('click', (e) => { + let target = e.target as HTMLElement; + + if(!target.classList.contains('reply-markup-button')) target = findUpClassName(target, 'reply-markup-button'); + if(!target) return; + + let column = whichChild(target); + let row = rows[whichChild(target.parentElement)]; + + if(!row.buttons || !row.buttons[column]) { + this.log.warn('no such button', row, column, message); + return; + } + + let button = row.buttons[column]; + appInlineBotsManager.callbackButtonClick(message.mid, button); + }); + + let offset = rows.length * 45 + 'px'; + bubbleContainer.style.marginBottom = offset; + containerDiv.style.bottom = '-' + offset; + + bubbleContainer.prepend(containerDiv); + } if(our) { if(message.pFlags.unread || message.mid < 0) this.unreadOut.add(message.mid); // message.mid < 0 added 11.02.2020 @@ -1801,8 +2022,13 @@ export class AppImManager { if(message.savedFrom) { let fwd = document.createElement('div'); fwd.classList.add('forward'/* , 'tgico-forward' */); - fwd.innerHTML = ` - `; + fwd.innerHTML = ` + + + + + + `; bubbleContainer.append(fwd); bubble.dataset.savedFrom = message.savedFrom; } @@ -1876,43 +2102,16 @@ export class AppImManager { bubble.classList.add(our ? 'is-out' : 'is-in'); if(updatePosition) { this.bubbleGroups.addBubble(bubble, message, reverse); - - //window.requestAnimationFrame(() => { - /* if(reverse) { - this.scrollable.prependByBatch(bubble); - } else { - this.scrollable.appendByBatch(bubble); - } */ - // раскомментировать ////// если рендер должен быть в другой функции (если хочешь сделать через requestAnimationFrame) - //////if(!multipleRender) { - /* if(reverse) { - this.scrollable.prepend(bubble); - } else { - this.scrollable.append(bubble); - } */ - //////} - //}); - - let dateMessage = this.getDateContainerByMessage(message, reverse); - if(reverse) { - dateMessage.container.insertBefore(bubble, dateMessage.div.nextSibling); - //this.scrollable.prepareElement(bubble, false); - } else { - dateMessage.container.append(bubble); - //this.scrollable.prepareElement(bubble, true); - } + + this.renderMessagesQueue(message, bubble, reverse); } else { this.bubbleGroups.updateGroupByMessageID(message.mid); } - - /* if(bubble.classList.contains('webpage')) { - this.log('night running', bubble, bubble.scrollHeight); - } */ return bubble; } - public performHistoryResult(history: number[], reverse: boolean, isBackLimit: boolean, additionMsgID: number, resetPromises = false) { + public performHistoryResult(history: number[], reverse: boolean, isBackLimit: boolean, additionMsgID: number) { // commented bot getProfile in getHistory! if(!history/* .filter((id: number) => id > 0) */.length) { if(!isBackLimit) { @@ -1922,20 +2121,20 @@ export class AppImManager { } } - //history = history.slice(); // need + history = history.slice(); // need if(additionMsgID) { history.unshift(additionMsgID); } - if(testScroll && additionMsgID) { + /* if(testScroll && additionMsgID) { for(let i = 0; i < 3; ++i) { let _history = history.slice(); setTimeout(() => { this.performHistoryResult(_history, reverse, isBackLimit, 0, resetPromises); - }, 0/* (i + 1) * 2500 */); + }, 0); } - } + } */ console.time('appImManager render history'); @@ -1943,138 +2142,45 @@ export class AppImManager { let peerID = this.peerID; return new Promise((resolve, reject) => { - let resolved = false; - /* let bubbles: HTMLDivElement[] = []; - method((msgID) => { - let message = appMessagesManager.getMessage(msgID); - let bubble = this.renderMessage(message, reverse, true); - bubbles.push(bubble); - }); */ - - let scrollTop = this.scrollable.scrollTop; - let leftHeightToScroll = scrollTop > 0 ? 0 : (Object.keys(this.bubbles).length > 0 ? 1 : this.scrollable.container.clientHeight); - let setScrollRAF = 0; - let previousScrollHeightMinusTop = this.scrollable.scrollHeight - scrollTop; - - //console.timeEnd('appImManager: pre render start'); - - this.log('start performHistoryResult, scrollTop:', scrollTop, leftHeightToScroll); - let method = (reverse ? history.shift : history.pop).bind(history); - let renderedFirstScreen = !!this.scrollable.scrollTop; let r = () => { - //let bubble = bubbles.shift(); - //if(!bubble && !resolved) return resolve(); - //if(!history.length) return resolve(true); - - /* let msgID = result.history.shift(); - if(!msgID && !resolved) return resolve(); - let message = appMessagesManager.getMessage(msgID); */ - if(this.peerID != peerID) { return reject('peer changed'); } - //let startTime = Date.now(); - //let elapsedTime = 0; - //do { - //let msgID = history.shift(); - let msgID = method(); - if(!msgID) { - if(resetPromises) { - (reverse ? this.getHistoryTopPromise = undefined : this.getHistoryBottomPromise = undefined); - } - - if(!resolved) { - resolve(true); - } - - return; - } - - let message = appMessagesManager.getMessage(msgID); - let bubble = this.renderMessage(message, reverse, true); - if(bubble) { - if(reverse) { - ////////this.scrollable.prepend(bubble); - - //this.log('performHistoryResult scrollTop', this.scrollable.scrollTop, bubble.scrollHeight); - - /* if(innerHeight >= 0) { - let height = bubble.scrollHeight; - innerHeight -= height; - this.scrollable.scrollTop += height; - } */ - - if(!renderedFirstScreen) { - /* if(!this.scrollable.scrollTop) { - let height = bubble.scrollHeight; - //let height = Math.ceil(bubble.getBoundingClientRect().height); - this.scrollable.scrollTop += height; - //innerHeight -= height; - } */ - /* if(leftHeightToScroll > 0) { - let height = bubble.scrollHeight; - leftHeightToScroll -= height; - - if(setScrollRAF) window.cancelAnimationFrame(setScrollRAF); - setScrollRAF = window.requestAnimationFrame(() => { - //this.scrollable.scrollTop += height; - this.scrollable.scrollTop = this.scrollable.scrollHeight - previousScrollHeightMinusTop; - }); - //this.scrollable.scrollTop += height; - } else { */ - renderedFirstScreen = true; - resolve(); - resolved = true; - //} - } - } else { - ////////this.scrollable.append(bubble); - } - } - //} while(cached && !this.scrollable.scrollTop); - //} while((elapsedTime = Date.now() - startTime) < 3); - - /* let bubble = this.renderMessage(message, reverse, true); - if(!bubble) return r(); + let msgID = method(); + if(!msgID) { + return; + } - if(reverse) { - this.scrollable.prepend(bubble); - - if(!this.scrollable.scrollTop) { - let height = bubble.scrollHeight; - this.scrollable.scrollTop += height; - //innerHeight -= height; - } else if(!resolved) { - resolve(); - resolved = true; - } - } else { - this.scrollable.append(bubble); - } */ + let message = appMessagesManager.getMessage(msgID); + this.renderMessage(message, reverse, true); firstLoad ? window.requestAnimationFrame(r) : r(); }; firstLoad ? window.requestAnimationFrame(r) : r(); - if(reverse) { - //if(setScrollRAF) window.cancelAnimationFrame(setScrollRAF); - //setScrollRAF = window.requestAnimationFrame(() => { - //this.scrollable.scrollTop += height; - this.scrollable.scrollTop = this.scrollable.scrollHeight - previousScrollHeightMinusTop; - //}); + let realLength = this.scrollable.length; + let previousScrollHeightMinusTop: number; + if(realLength > 0 && reverse) { + this.messagesQueueOnRender = () => { + let scrollTop = realLength ? this.scrollable.scrollTop : 0; + previousScrollHeightMinusTop = realLength ? this.scrollable.scrollHeight - scrollTop : 0; + + this.log('performHistoryResult: messagesQueueOnRender, scrollTop:', scrollTop, previousScrollHeightMinusTop); + }; } - //r(); - /* method((msgID) => { - let message = appMessagesManager.getMessage(msgID); - - window.requestAnimationFrame(() => { - this.renderMessage(message, reverse, true); - }); - }); */ + + (this.messagesQueuePromise || Promise.resolve()).then(() => { + if(realLength > 0 && reverse && previousScrollHeightMinusTop !== undefined) { + this.log('performHistoryResult: will set scrollTop', this.scrollable.scrollHeight, previousScrollHeightMinusTop); + this.scrollable.scrollTop = this.scrollable.scrollHeight - previousScrollHeightMinusTop; + } + + resolve(true); + }, reject); }).then(() => { console.timeEnd('appImManager render history'); @@ -2083,25 +2189,19 @@ export class AppImManager { } // reverse means scroll up - public getHistory(maxID = 0, reverse = false, isBackLimit = false, additionMsgID = 0) { + public getHistory(maxID = 0, reverse = false, isBackLimit = false, additionMsgID = 0): {cached: boolean, promise: Promise} { let peerID = this.peerID; //console.time('appImManager call getHistory'); - - let dialog = appMessagesManager.getDialogByPeerID(peerID)[0]; - if(!maxID && dialog && dialog.top_message) { - maxID = dialog.top_message/* + 1 */; - } - let pageCount = this.bubblesContainer.clientHeight / 38/* * 1.25 */ | 0; //let loadCount = Object.keys(this.bubbles).length > 0 ? 50 : pageCount; let realLoadCount = Object.keys(this.bubbles).length > 0 ? Math.max(40, pageCount) : pageCount;//let realLoadCount = 50; let loadCount = realLoadCount; if(testScroll) { - //loadCount = 1; + loadCount = 1; if(Object.keys(this.bubbles).length > 0) - return Promise.resolve(true); + return {cached: false, promise: Promise.resolve(true)}; } ////console.time('render history total'); @@ -2123,8 +2223,9 @@ export class AppImManager { result = new Promise((resolve, reject) => setTimeout(() => resolve(_result), 150)); } */ - let promise: Promise; + let promise: Promise, cached: boolean; if(result instanceof Promise) { + cached = false; promise = result.then((result) => { this.log('getHistory result by maxID:', maxID, reverse, isBackLimit, result); @@ -2138,20 +2239,21 @@ export class AppImManager { ////console.timeEnd('render history total'); - return this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID, true); + return this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID); }, (err) => { this.log.error('getHistory error:', err); (reverse ? this.getHistoryTopPromise = undefined : this.getHistoryBottomPromise = undefined); return false; }); - - (reverse ? this.getHistoryTopPromise = promise : this.getHistoryBottomPromise = promise); } else { - promise = this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID, true); + cached = true; + promise = this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID); //return (reverse ? this.getHistoryTopPromise = promise : this.getHistoryBottomPromise = promise); //return this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID, true); } + (reverse ? this.getHistoryTopPromise = promise : this.getHistoryBottomPromise = promise); + /* false && */promise.then(() => { if(reverse) { this.loadedTopTimes++; @@ -2186,16 +2288,36 @@ export class AppImManager { this.log('getHistory: will slice ids:', ids, reverse); this.deleteMessagesByIDs(ids); - /* ids.forEach(id => { - this.bubbles[id].remove(); - delete this.bubbles[id]; - }); - - this.deleteEmptyDateGroups(); */ } + + (reverse ? this.getHistoryTopPromise = undefined : this.getHistoryBottomPromise = undefined); + + this.setUnreadDelimiter(); // не нашёл места лучше }); - return promise; + return {cached, promise}; + } + + public setUnreadDelimiter() { + let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0]; + if(!dialog?.unread_count) return; + + let maxID = dialog.read_inbox_max_id; + maxID = Object.keys(this.bubbles).map(i => +i).sort((a, b) => a - b).find(i => i > maxID); + + if(maxID && this.bubbles[maxID]) { + let bubble = this.bubbles[maxID]; + if(this.firstUnreadBubble && this.firstUnreadBubble != bubble) { + this.firstUnreadBubble.classList.remove('is-first-unread'); + this.firstUnreadBubble = null; + } + + if(maxID != dialog.top_message) { + bubble.classList.add('is-first-unread'); + } + + this.firstUnreadBubble = bubble; + } } public deleteEmptyDateGroups() { diff --git a/src/lib/appManagers/appMediaViewer.ts b/src/lib/appManagers/appMediaViewer.ts index 52e5d62d..d3de98e6 100644 --- a/src/lib/appManagers/appMediaViewer.ts +++ b/src/lib/appManagers/appMediaViewer.ts @@ -171,7 +171,7 @@ export class AppMediaViewer { mover = this.setNewMover(); } */ - ///////this.log('setMoverToTarget', target, closing, wasActive, fromRight); + this.log('setMoverToTarget', target, closing, wasActive, fromRight); let realParent: HTMLDivElement; @@ -200,13 +200,38 @@ export class AppMediaViewer { top = rect.top; } + let aspecter: HTMLDivElement; + if(target instanceof HTMLImageElement || target instanceof HTMLVideoElement) { + if(mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter')) { + aspecter = mover.firstElementChild as HTMLDivElement; + + let player = aspecter.querySelector('.ckin__player'); + if(player) { + let video = player.firstElementChild as HTMLVideoElement; + aspecter.append(video); + player.remove(); + } + + if(!aspecter.style.cssText) { // всё из-за видео, элементы управления скейлятся, так бы можно было этого не делать + mover.classList.remove('active'); + this.setFullAspect(aspecter, containerRect, rect); + void mover.offsetLeft; // reflow + mover.classList.add('active'); + } + } else { + aspecter = document.createElement('div'); + aspecter.classList.add('media-viewer-aspecter'); + mover.prepend(aspecter); + } + + aspecter.style.cssText = `width: ${rect.width}px; height: ${rect.height}px; transform: scale(${containerRect.width / rect.width}, ${containerRect.height / rect.height});`; + } + transform += `translate(${left}px,${top}px) `; mover.style.width = containerRect.width + 'px'; mover.style.height = containerRect.height + 'px'; - mover.classList.remove('cover'); - let scaleX = rect.width / containerRect.width; let scaleY = rect.height / containerRect.height; if(!wasActive) { @@ -234,19 +259,15 @@ export class AppMediaViewer { let video: HTMLVideoElement; if(target.tagName == 'DIV') { // means backgrounded with cover - //img.style.objectFit = 'cover'; img = new Image(); img.src = target.style.backgroundImage.slice(5, -2); - //mover.classList.add('cover'); - //mover.style.backgroundImage = target.style.backgroundImage; - } else if(target.tagName == 'IMG' || target.tagName == 'image') { + } else if(target instanceof HTMLImageElement) { img = new Image(); - img.src = target instanceof SVGImageElement ? target.getAttributeNS(null, 'href') : (target as HTMLImageElement).src; - img.style.objectFit = 'contain'; - } else if(target.tagName == 'VIDEO') { + img.src = target.src; + } else if(target instanceof HTMLVideoElement) { video = document.createElement('video'); let source = document.createElement('source'); - source.src = target.querySelector('source').src; + source.src = target.querySelector('source')?.src; video.append(source); } else if(target instanceof SVGSVGElement) { let clipID = target.dataset.clipID; @@ -298,12 +319,9 @@ export class AppMediaViewer { mover.prepend(newSvg); } - if(img) { - img.style.borderRadius = borderRadius; - mover.prepend(img); - } else if(video) { - video.style.borderRadius = borderRadius; - mover.prepend(video); + if(aspecter) { + aspecter.style.borderRadius = borderRadius; + aspecter.append(img || video); } mover.style.display = ''; @@ -330,15 +348,11 @@ export class AppMediaViewer { if(mover.firstElementChild) { (mover.firstElementChild as HTMLElement).style.borderRadius = borderRadius; } - - if(target.tagName == 'DIV') { - mover.classList.add('cover'); - } }, delay / 2); setTimeout(() => { mover.innerHTML = ''; - mover.classList.remove('moving', 'active', 'cover'); + mover.classList.remove('moving', 'active'); mover.style.display = 'none'; }, delay); } @@ -346,22 +360,60 @@ export class AppMediaViewer { return () => { mover.style.transform = `translate(${containerRect.left}px,${containerRect.top}px) scale(1,1)`; + if(aspecter) { + this.setFullAspect(aspecter, containerRect, rect); + } + setTimeout(() => { mover.style.borderRadius = ''; if(mover.firstElementChild) { (mover.firstElementChild as HTMLElement).style.borderRadius = ''; } - - mover.classList.remove('cover'); }, delay / 2); + mover.dataset.timeout = '' + setTimeout(() => { + mover.classList.remove('moving'); + + if(aspecter) { // всё из-за видео, элементы управления скейлятся, так бы можно было этого не делать + mover.classList.remove('active'); + aspecter.style.cssText = ''; + void mover.offsetLeft; // reflow + } + + mover.classList.add('active'); + delete mover.dataset.timeout; + }, delay); + if(path) { this.sizeTailPath(path, containerRect, scaleX, delay, true, isOut, borderRadius); } }; } + private setFullAspect(aspecter: HTMLDivElement, containerRect: DOMRect, rect: DOMRect) { + let media = aspecter.firstElementChild; + let proportion: number; + if(media instanceof HTMLImageElement) { + proportion = media.naturalWidth / media.naturalHeight; + } else if(media instanceof HTMLVideoElement) { + proportion = media.videoWidth / media.videoHeight; + } + + let {width, height} = rect; + if(proportion == 1) { + aspecter.style.cssText = ''; + } else { + if(proportion > 0) { + width = height * proportion; + } else { + height = width * proportion; + } + + aspecter.style.cssText = `width: ${width}px; height: ${height}px; transform: scale(${containerRect.width / width}, ${containerRect.height / height});`; + } + } + public sizeTailPath(path: SVGPathElement, rect: DOMRect, scaleX: number, delay: number, upscale: boolean, isOut: boolean, borderRadius: string) { let start = Date.now(); let {width, height} = rect; @@ -393,8 +445,13 @@ export class AppMediaViewer { public moveTheMover(mover: HTMLDivElement, toLeft = true) { let windowW = appPhotosManager.windowW; + //mover.classList.remove('active'); mover.classList.add('moving'); + if(mover.dataset.timeout) { // и это тоже всё из-за скейла видео, так бы это не нужно было + clearTimeout(+mover.dataset.timeout); + } + let rect = mover.getBoundingClientRect(); let newTransform = mover.style.transform.replace(/translate\((.+?),/, (match, p1) => { @@ -617,30 +674,35 @@ export class AppMediaViewer { if(useContainerAsTarget) target = target.querySelector('img, video') || target; let afterTimeout = this.setMoverToTarget(target, false, fromRight); + //return; // set and don't move //if(wasActive) return; - //return; setTimeout(() => { afterTimeout(); //return; let video = mover.querySelector('video') || document.createElement('video'); - let source: HTMLSourceElement; - if(video.firstElementChild) { - source = video.firstElementChild as HTMLSourceElement; - } + let source = video.firstElementChild as HTMLSourceElement || document.createElement('source'); if(media.type == 'gif') { video.autoplay = true; } - video.dataset.ckin = 'default'; - video.dataset.overlay = '1'; + let createPlayer = () => { + if(media.type != 'gif') { + video.dataset.ckin = 'default'; + video.dataset.overlay = '1'; + + let player = new VideoPlayer(video, true); + /* player.wrapper.parentElement.append(video); + mover.append(player.wrapper); */ + } + }; if(!source || !source.src) { let promise = appDocsManager.downloadDoc(media); this.preloader.attach(mover, true, promise); - promise.then(blob => { + promise.then(() => { if(this.currentMessageID != message.mid) { this.log.warn('media viewer changed video'); return; @@ -651,36 +713,27 @@ export class AppMediaViewer { this.updateMediaSource(mover, url, 'source'); this.updateMediaSource(target, url, 'source'); } else { - let img = mover.firstElementChild; - if(img instanceof Image) { - mover.removeChild(img); + let aspecter = mover.firstElementChild; + let img = aspecter.firstElementChild; + if(img instanceof HTMLImageElement) { + img.remove(); } - source = document.createElement('source'); renderImageFromUrl(source, url); source.type = media.mime_type; - mover.prepend(video); - - video.append(source); - } + if(!source.parentElement) { + video.append(source); + } - if(media.type != 'gif') { - let player = new VideoPlayer(video, true); + if(!video.parentElement) { + aspecter.prepend(video); + } } - }); - } else if(media.type != 'gif') { - let player = new VideoPlayer(video, true); - } - - - /* wrapVideo.call(this, media, mover, message, false, this.preloader).then(() => { - if(this.currentMessageID != message.mid) { - this.log.warn('media viewer changed video'); - return; - } - }); */ + createPlayer(); + }); + } else createPlayer(); }, 0); } else { let size = appPhotosManager.setAttachmentSize(media.id, container, maxWidth, maxHeight); @@ -688,7 +741,7 @@ export class AppMediaViewer { if(useContainerAsTarget) target = target.querySelector('img, video') || target; let afterTimeout = this.setMoverToTarget(target, false, fromRight); - //return; + //return; // set and don't move //if(wasActive) return; setTimeout(() => { afterTimeout(); @@ -709,10 +762,14 @@ export class AppMediaViewer { this.updateMediaSource(target, url, 'image'); this.updateMediaSource(mover, url, 'image'); } else { - let image = mover.firstElementChild as HTMLImageElement || new Image(); - //image.src = url; + let aspecter = mover.firstElementChild; + let image = aspecter.firstElementChild as HTMLImageElement; + if(!image) { + image = new Image(); + aspecter.append(image); + } + renderImageFromUrl(image, url); - mover.prepend(image); } this.preloader.detach(); diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index db0e2d58..a532e409 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -1,4 +1,4 @@ -import { SearchIndexManager, $rootScope, copy, tsNow, safeReplaceObject, dT, _, listMergeSorted, deepEqual, langPack } from "../utils"; +import { $rootScope, copy, tsNow, safeReplaceObject, dT, _, listMergeSorted, deepEqual, langPack } from "../utils"; import appMessagesIDsManager from "./appMessagesIDsManager"; import appChatsManager from "./appChatsManager"; import appUsersManager from "./appUsersManager"; @@ -9,7 +9,7 @@ import apiUpdatesManager from "./apiUpdatesManager"; import appPhotosManager from "./appPhotosManager"; import AppStorage from '../storage'; -import AppPeersManager from "./appPeersManager"; +import appPeersManager from "./appPeersManager"; import ServerTimeManager from "../mtproto/serverTimeManager"; import apiFileManager from "../mtproto/apiFileManager"; import appDocsManager from "./appDocsManager"; @@ -64,20 +64,13 @@ export type Dialog = { export class AppMessagesManager { public messagesStorage: any = {}; - public messagesForDialogs: any = {}; public groupedMessagesStorage: {[groupID: string]: any} = {}; // will be used for albums public historiesStorage: { [peerID: string]: HistoryStorage } = {}; public dialogsStorage: { - count: number, - dialogs: { - [folderID: number]: Dialog[] - } - } = { - count: null, - dialogs: {} - }; + [folderID: number]: Dialog[] + } = {}; public pendingByRandomID: {[randomID: string]: [number, number]} = {}; public pendingByMessageID: any = {}; public pendingAfterMsgs: any = {}; @@ -86,9 +79,6 @@ export class AppMessagesManager { public tempID = -1; public tempFinalizeCallbacks: any = {}; - public dialogsIndex: any = SearchIndexManager.createIndex(); - public cachedResults: any = {query: false}; - public lastSearchFilter: any = {}; public lastSearchResults: any = []; @@ -96,10 +86,6 @@ export class AppMessagesManager { public fetchSingleMessagesTimeout = 0; private fetchSingleMessagesPromise: Promise = null; - public incrementedMessageViews: any = {}; - public needIncrementMessageViews: any = []; - public incrementMessageViewsTimeout: any = false; - public maxSeenID = 0; public allDialogsLoaded: {[folder_id: number]: boolean} = {}; @@ -116,16 +102,9 @@ export class AppMessagesManager { public newDialogsToHandle: {[peerID: string]: {reload: true} | Dialog} = {}; public newUpdatesAfterReloadToHandle: any = {}; - public fwdMessagesPluralize = _('conversation_forwarded_X_messages'); - public gameScorePluralize = _('conversation_scored_X'); + public loaded: Promise = null; constructor() { - AppStorage.get('max_seen_msg').then((maxID) => { - if(maxID && !appMessagesIDsManager.getMessageIDInfo(maxID)[1]) { - this.maxSeenID = maxID; - } - }); - $rootScope.$on('apiUpdate', (e: CustomEvent) => { let update: any = e.detail; // if(update._ != 'updateUserStatus') { @@ -159,7 +138,7 @@ export class AppMessagesManager { if(draft && draft.date) { topDate = draft.date; } else { - var channelID = AppPeersManager.isChannel(peerID) ? -peerID : 0 + var channelID = appPeersManager.isChannel(peerID) ? -peerID : 0 var topDate = this.getMessage(dialog.top_message).date; if(channelID) { @@ -183,6 +162,94 @@ export class AppMessagesManager { }); } }); + + this.loaded = new Promise((resolve, reject) => { + AppStorage.get<{ + dialogs: Dialog[], + allDialogsLoaded: AppMessagesManager['allDialogsLoaded'], + peers: any[], + messages: any[], + updates: any, + maxSeenMsgID: number + }>('state').then(({dialogs, allDialogsLoaded, peers, messages, maxSeenMsgID, updates}) => { + console.log('state res', dialogs, messages); + + if(maxSeenMsgID && !appMessagesIDsManager.getMessageIDInfo(maxSeenMsgID)[1]) { + this.maxSeenID = maxSeenMsgID; + } + + //return resolve(); + + if(peers) { + for(let peerID in peers) { + let peer = peers[peerID]; + if(+peerID < 0) appChatsManager.saveApiChat(peer); + else appUsersManager.saveApiUser(peer); + } + } + + if(messages) { + this.saveMessages(messages); + } + + if(allDialogsLoaded) { + this.allDialogsLoaded = allDialogsLoaded; + } + + if(dialogs) { + dialogs.forEachReverse(dialog => { + this.saveConversation(dialog); + }); + } + + apiUpdatesManager.attach(updates ?? null); + + resolve(); + }).catch(resolve); + }); + + setInterval(() => this.saveState(), 10000); + } + + public saveState() { + let messages: any[] = []; + let dialogs: Dialog[] = []; + let peers: {[peerID: number]: any} = {}; + + for(let folderID in this.dialogsStorage) { + for(let dialog of this.dialogsStorage[folderID]) { + let message = this.getMessage(dialog.top_message); + if(message._ != 'messageEmpty' && message.id > 0) { + messages.push(message); + + if(message.fromID != dialog.peerID) { + peers[message.fromID] = appPeersManager.getPeer(message.fromID); + } + } + + dialogs.push(dialog); + + peers[dialog.peerID] = appPeersManager.getPeer(dialog.peerID); + } + } + + let us = apiUpdatesManager.updatesState; + let updates = { + seq: us.seq, + pts: us.pts, + date: us.date + }; + + AppStorage.set({ + state: { + dialogs, + messages, + allDialogsLoaded: this.allDialogsLoaded, + peers, + updates, + maxSeenMsgID: this.maxSeenID + } + }); } public getInputEntities(entities: any) { @@ -240,7 +307,7 @@ export class AppMessagesManager { return apiManager.invokeApi('messages.editMessage', { flags: flags, - peer: AppPeersManager.getInputPeerByID(peerID), + peer: appPeersManager.getInputPeerByID(peerID), id: appMessagesIDsManager.getMessageLocalID(messageID), message: text, media: message.media, @@ -260,22 +327,22 @@ export class AppMessagesManager { }); } - public sendText(peerID: number, text: string, options: { - entities?: any[], - replyToMsgID?: number, - viaBotID?: number, - queryID?: number, - resultID?: number, - noWebPage?: boolean, - reply_markup?: any, - clearDraft?: boolean, - webPage?: any - } = {}) { + public sendText(peerID: number, text: string, options: Partial<{ + entities: any[], + replyToMsgID: number, + viaBotID: number, + queryID: number, + resultID: number, + noWebPage: boolean, + reply_markup: any, + clearDraft: boolean, + webPage: any + }> = {}) { if(typeof(text) != 'string') { return; } - peerID = AppPeersManager.getPeerMigratedTo(peerID) || peerID; + peerID = appPeersManager.getPeerMigratedTo(peerID) || peerID; var entities = options.entities || []; if(!options.viaBotID) { @@ -293,8 +360,8 @@ export class AppMessagesManager { var flags = 0; var pFlags: any = {}; var replyToMsgID = options.replyToMsgID; - var isChannel = AppPeersManager.isChannel(peerID); - var isMegagroup = isChannel && AppPeersManager.isMegagroup(peerID); + var isChannel = appPeersManager.isChannel(peerID); + var isMegagroup = isChannel && appPeersManager.isMegagroup(peerID); var asChannel = isChannel && !isMegagroup ? true : false; var message: any; let noWebPage = options.noWebPage || false; @@ -329,7 +396,7 @@ export class AppMessagesManager { _: 'message', id: messageID, from_id: fromID, - to_id: AppPeersManager.getOutputPeer(peerID), + to_id: appPeersManager.getOutputPeer(peerID), flags: flags, pFlags: pFlags, date: tsNow(true) + serverTimeManager.serverTimeOffset, @@ -387,7 +454,7 @@ export class AppMessagesManager { if(options.viaBotID) { apiPromise = apiManager.invokeApi('messages.sendInlineBotResult', { flags: flags, - peer: AppPeersManager.getInputPeerByID(peerID), + peer: appPeersManager.getInputPeerByID(peerID), random_id: randomID, reply_to_msg_id: appMessagesIDsManager.getMessageLocalID(replyToMsgID), query_id: options.queryID, @@ -401,7 +468,7 @@ export class AppMessagesManager { apiPromise = apiManager.invokeApi('messages.sendMessage', { flags: flags, no_webpage: noWebPage, - peer: AppPeersManager.getInputPeerByID(peerID), + peer: appPeersManager.getInputPeerByID(peerID), message: text, random_id: randomID, reply_to_msg_id: appMessagesIDsManager.getMessageLocalID(replyToMsgID), @@ -490,7 +557,7 @@ export class AppMessagesManager { duration: number, background: boolean }> = {}) { - peerID = AppPeersManager.getPeerMigratedTo(peerID) || peerID; + peerID = appPeersManager.getPeerMigratedTo(peerID) || peerID; var messageID = this.tempID--; var randomID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)]; var randomIDS = bigint(randomID[0]).shiftLeft(32).add(bigint(randomID[1])).toString(); @@ -498,8 +565,8 @@ export class AppMessagesManager { var flags = 0; var pFlags: any = {}; var replyToMsgID = options.replyToMsgID; - var isChannel = AppPeersManager.isChannel(peerID); - var isMegagroup = isChannel && AppPeersManager.isMegagroup(peerID); + var isChannel = appPeersManager.isChannel(peerID); + var isMegagroup = isChannel && appPeersManager.isMegagroup(peerID); var asChannel = isChannel && !isMegagroup ? true : false; var attachType: string, apiFileName: string; @@ -651,7 +718,7 @@ export class AppMessagesManager { _: 'message', id: messageID, from_id: fromID, - to_id: AppPeersManager.getOutputPeer(peerID), + to_id: appPeersManager.getOutputPeer(peerID), flags: flags, pFlags: pFlags, date: date, @@ -688,7 +755,7 @@ export class AppMessagesManager { flags: flags, background: options.background, clear_draft: true, - peer: AppPeersManager.getInputPeerByID(peerID), + peer: appPeersManager.getInputPeerByID(peerID), media: inputMedia, message: caption, random_id: randomID, @@ -821,14 +888,14 @@ export class AppMessagesManager { objectURL: string, }>[] }> = {}) { - peerID = AppPeersManager.getPeerMigratedTo(peerID) || peerID; + peerID = appPeersManager.getPeerMigratedTo(peerID) || peerID; let groupID: number; let historyStorage = this.historiesStorage[peerID] ?? (this.historiesStorage[peerID] = {count: null, history: [], pending: []}); let flags = 0; let pFlags: any = {}; let replyToMsgID = options.replyToMsgID; - let isChannel = AppPeersManager.isChannel(peerID); - let isMegagroup = isChannel && AppPeersManager.isMegagroup(peerID); + let isChannel = appPeersManager.isChannel(peerID); + let isMegagroup = isChannel && appPeersManager.isMegagroup(peerID); let asChannel = isChannel && !isMegagroup ? true : false; let caption = options.caption || ''; @@ -949,7 +1016,7 @@ export class AppMessagesManager { id: messageID, from_id: fromID, grouped_id: groupID, - to_id: AppPeersManager.getOutputPeer(peerID), + to_id: appPeersManager.getOutputPeer(peerID), flags: flags, pFlags: pFlags, date: date, @@ -987,7 +1054,7 @@ export class AppMessagesManager { let uploaded = false, uploadPromise: ReturnType = null; - let inputPeer = AppPeersManager.getInputPeerByID(peerID); + let inputPeer = appPeersManager.getInputPeerByID(peerID); let invoke = (multiMedia: any[]) => { appImManager.setTyping('sendMessageCancelAction'); @@ -1129,9 +1196,7 @@ export class AppMessagesManager { } public getConversations(offsetIndex?: number, limit = 20, folderID = 0) { - let curDialogStorage = this.dialogsStorage.dialogs[folderID] ?? (this.dialogsStorage.dialogs[folderID] = []); - - this.cachedResults.query = false; + let curDialogStorage = this.dialogsStorage[folderID] ?? (this.dialogsStorage[folderID] = []); let offset = 0; if(offsetIndex > 0) { @@ -1150,7 +1215,7 @@ export class AppMessagesManager { } return this.getTopMessages(limit, folderID).then(count => { - let curDialogStorage = this.dialogsStorage.dialogs[folderID]; + let curDialogStorage = this.dialogsStorage[folderID]; offset = 0; if(offsetIndex > 0) { @@ -1171,12 +1236,12 @@ export class AppMessagesManager { } public getTopMessages(limit: number, folderID: number): Promise { - var dialogs = this.dialogsStorage.dialogs[folderID]; - var offsetDate = 0; - var offsetID = 0; - var offsetPeerID = 0; - var offsetIndex = 0; - var flags = 0; + const dialogs = this.dialogsStorage[folderID]; + let offsetID = 0; + let offsetDate = 0; + let offsetPeerID = 0; + let offsetIndex = 0; + let flags = 0; if(this.dialogsOffsetDate[folderID]) { offsetDate = this.dialogsOffsetDate[folderID] + serverTimeManager.serverTimeOffset; @@ -1196,7 +1261,7 @@ export class AppMessagesManager { folder_id: folderID, offset_date: offsetDate, offset_id: appMessagesIDsManager.getMessageLocalID(offsetID), - offset_peer: AppPeersManager.getInputPeerByID(offsetPeerID), + offset_peer: appPeersManager.getInputPeerByID(offsetPeerID), limit: limit, hash: hash }, { @@ -1230,7 +1295,7 @@ export class AppMessagesManager { } if(!maxSeenIdIncremented && - !AppPeersManager.isChannel(AppPeersManager.getPeerID(dialog.peer))) { + !appPeersManager.isChannel(appPeersManager.getPeerID(dialog.peer))) { this.incrementMaxSeenID(dialog.top_message); maxSeenIdIncremented = true; } @@ -1267,7 +1332,7 @@ export class AppMessagesManager { public forwardMessages(peerID: number, mids: number[], options: Partial<{ withMyScore: boolean }> = {}) { - peerID = AppPeersManager.getPeerMigratedTo(peerID) || peerID; + peerID = appPeersManager.getPeerMigratedTo(peerID) || peerID; mids = mids.sort((a, b) => a - b); var flags = 0; @@ -1293,10 +1358,10 @@ export class AppMessagesManager { let promise = apiManager.invokeApi('messages.forwardMessages', { flags: flags, - from_peer: AppPeersManager.getInputPeerByID(-channelID), + from_peer: appPeersManager.getInputPeerByID(-channelID), id: msgIDs, random_id: randomIDs, - to_peer: AppPeersManager.getInputPeerByID(peerID) + to_peer: appPeersManager.getInputPeerByID(peerID) }, sentRequestOptions).then((updates) => { apiUpdatesManager.processUpdateMessage(updates); }, () => {}).then(() => { @@ -1329,7 +1394,7 @@ export class AppMessagesManager { } public generateIndexForDialog(dialog: Dialog) { - let channelID = AppPeersManager.isChannel(dialog.peerID) ? -dialog.peerID : 0; + let channelID = appPeersManager.isChannel(dialog.peerID) ? -dialog.peerID : 0; let mid = appMessagesIDsManager.getFullMessageID(dialog.top_message, channelID); let message = this.getMessage(mid); @@ -1354,15 +1419,15 @@ export class AppMessagesManager { } public pushDialogToStorage(dialog: Dialog, offsetDate?: number) { - let dialogs = this.dialogsStorage.dialogs[dialog.folder_id] ?? (this.dialogsStorage.dialogs[dialog.folder_id] = []); + let dialogs = this.dialogsStorage[dialog.folder_id] ?? (this.dialogsStorage[dialog.folder_id] = []); let pos = dialogs.findIndex(d => d.peerID == dialog.peerID); if(pos !== -1) { dialogs.splice(pos, 1); } if(offsetDate && - !dialog.pFlags.pinned && - (!this.dialogsOffsetDate[dialog.folder_id] || offsetDate < this.dialogsOffsetDate[dialog.folder_id])) { + !dialog.pFlags.pinned && + (!this.dialogsOffsetDate[dialog.folder_id] || offsetDate < this.dialogsOffsetDate[dialog.folder_id])) { if(pos !== -1) { // So the dialog jumped to the last position return false; @@ -1395,7 +1460,7 @@ export class AppMessagesManager { } public getMessagePeer(message: any): number { - var toID = message.to_id && AppPeersManager.getPeerID(message.to_id) || 0; + var toID = message.to_id && appPeersManager.getPeerID(message.to_id) || 0; if(toID < 0) { return toID; @@ -1406,7 +1471,7 @@ export class AppMessagesManager { } public getDialogByPeerID(peerID: number): [Dialog, number] | [] { - let dialogs = this.dialogsStorage.dialogs; + let dialogs = this.dialogsStorage; for(let folderID in dialogs) { let index = dialogs[folderID].findIndex(dialog => dialog.peerID == peerID); if(index !== -1) { @@ -1418,7 +1483,7 @@ export class AppMessagesManager { } public reloadConversation(peerID: number | number[]) { - let peers = [].concat(peerID).map(peerID => AppPeersManager.getInputPeerByID(peerID)); + let peers = [].concat(peerID).map(peerID => appPeersManager.getInputPeerByID(peerID)); console.log('will reloadConversation', peerID); @@ -1456,7 +1521,7 @@ export class AppMessagesManager { } public async flushHistory(peerID: number, justClear: boolean) { - if(AppPeersManager.isChannel(peerID)) { + if(appPeersManager.isChannel(peerID)) { let promise = this.getHistory(peerID, 0, 1); let historyResult = promise instanceof Promise ? await promise : promise; @@ -1480,7 +1545,7 @@ export class AppMessagesManager { }); } - return this.doFlushHistory(AppPeersManager.getInputPeerByID(peerID), justClear).then(() => { + return this.doFlushHistory(appPeersManager.getInputPeerByID(peerID), justClear).then(() => { delete this.historiesStorage[peerID]; for(let mid in this.messagesStorage) { let message = this.messagesStorage[mid]; @@ -1494,7 +1559,7 @@ export class AppMessagesManager { } else { let foundDialog = this.getDialogByPeerID(peerID); if(foundDialog[0]) { - this.dialogsStorage.dialogs[foundDialog[0].folder_id].splice(foundDialog[1], 1); + this.dialogsStorage[foundDialog[0].folder_id].splice(foundDialog[1], 1); } $rootScope.$broadcast('dialog_drop', {peerID: peerID}); @@ -1559,9 +1624,9 @@ export class AppMessagesManager { if(fwdHeader) { if(peerID == appUsersManager.getSelf().id) { if(fwdHeader.saved_from_peer && fwdHeader.saved_from_msg_id) { - var savedFromPeerID = AppPeersManager.getPeerID(fwdHeader.saved_from_peer); + var savedFromPeerID = appPeersManager.getPeerID(fwdHeader.saved_from_peer); var savedFromMid = appMessagesIDsManager.getFullMessageID(fwdHeader.saved_from_msg_id, - AppPeersManager.isChannel(savedFromPeerID) ? -savedFromPeerID : 0); + appPeersManager.isChannel(savedFromPeerID) ? -savedFromPeerID : 0); apiMessage.savedFrom = savedFromPeerID + '_' + savedFromMid; } @@ -1834,7 +1899,7 @@ export class AppMessagesManager { folder_peers: peerIDs.map(peerID => { return { _: 'inputFolderPeer', - peer: AppPeersManager.getInputPeerByID(peerID), + peer: appPeersManager.getInputPeerByID(peerID), folder_id: folderID }; }) @@ -1850,7 +1915,7 @@ export class AppMessagesManager { let peer = { _: 'inputDialogPeer', - peer: AppPeersManager.getInputPeerByID(peerID) + peer: appPeersManager.getInputPeerByID(peerID) }; let flags = dialog.pFlags?.pinned ? 0 : 1; @@ -1874,7 +1939,7 @@ export class AppMessagesManager { let peer = { _: 'inputDialogPeer', - peer: AppPeersManager.getInputPeerByID(peerID) + peer: appPeersManager.getInputPeerByID(peerID) }; let flags = dialog.pFlags?.unread_mark ? 0 : 1; @@ -1906,7 +1971,7 @@ export class AppMessagesManager { setTimeout(() => { var foundDialog = this.getDialogByPeerID(migrateFrom); if(foundDialog.length) { - this.dialogsStorage.dialogs[foundDialog[0].folder_id].splice(foundDialog[1], 1); + this.dialogsStorage[foundDialog[0].folder_id].splice(foundDialog[1], 1); $rootScope.$broadcast('dialog_drop', {peerID: migrateFrom, dialog: foundDialog[0]}); } @@ -1969,7 +2034,7 @@ export class AppMessagesManager { var updatedDialogs: {[peerID: number]: Dialog} = {}; var hasUpdated = false; dialogsResult.dialogs.forEach((dialog: any) => { - var peerID = AppPeersManager.getPeerID(dialog.peer); + var peerID = appPeersManager.getPeerID(dialog.peer); var topMessage = dialog.top_message; var topPendingMesage = this.pendingTopMsgs[peerID]; if(topPendingMesage) { @@ -1991,7 +2056,6 @@ export class AppMessagesManager { this.saveConversation(dialog); if(wasDialogBefore) { - this.clearDialogCache(topMessage); $rootScope.$broadcast('dialog_top', dialog); } else { updatedDialogs[peerID] = dialog; @@ -2000,7 +2064,7 @@ export class AppMessagesManager { } else { var foundDialog = this.getDialogByPeerID(peerID); if(foundDialog.length) { - this.dialogsStorage.dialogs[foundDialog[0].folder_id].splice(foundDialog[1], 1); + this.dialogsStorage[foundDialog[0].folder_id].splice(foundDialog[1], 1); $rootScope.$broadcast('dialog_drop', {peerID: peerID, dialog: foundDialog[0]}); } } @@ -2020,20 +2084,13 @@ export class AppMessagesManager { } } - public clearDialogCache(msgID: number) { - delete this.messagesForDialogs[msgID]; - } - public saveConversation(dialog: Dialog) { - var peerID = AppPeersManager.getPeerID(dialog.peer); + var peerID = appPeersManager.getPeerID(dialog.peer); if(!peerID) { return false; } - var channelID = AppPeersManager.isChannel(peerID) ? -peerID : 0; - var peerText = AppPeersManager.getPeerSearchText(peerID); - SearchIndexManager.indexObject(peerID, peerText, this.dialogsIndex); + var channelID = appPeersManager.isChannel(peerID) ? -peerID : 0; - //var isMegagroup = AppPeersManager.isMegagroup(channelID); if(dialog.top_message) { var mid = appMessagesIDsManager.getFullMessageID(dialog.top_message, channelID); var message = this.getMessage(mid); @@ -2044,7 +2101,7 @@ export class AppMessagesManager { id: mid, mid: mid, from_id: appUsersManager.getSelf().id, - to_id: AppPeersManager.getOutputPeer(peerID), + to_id: appPeersManager.getOutputPeer(peerID), deleted: true, flags: 0, pFlags: {unread: false, out: true}, @@ -2053,12 +2110,11 @@ export class AppMessagesManager { } this.saveMessages([message]); } - var offsetDate = message.date; if(!channelID && peerID < 0) { var chat = appChatsManager.getChat(-peerID) if(chat && chat.migrated_to && chat.pFlags.deactivated) { - var migratedToPeer = AppPeersManager.getPeerID(chat.migrated_to) + var migratedToPeer = appPeersManager.getPeerID(chat.migrated_to) this.migratedFromTo[peerID] = migratedToPeer; this.migratedToFrom[migratedToPeer] = peerID; return; @@ -2073,7 +2129,7 @@ export class AppMessagesManager { dialog.peerID = peerID; this.generateIndexForDialog(dialog); - this.pushDialogToStorage(dialog, offsetDate); + this.pushDialogToStorage(dialog, message.date); // Because we saved message without dialog present if(message.mid > 0) { @@ -2093,12 +2149,6 @@ export class AppMessagesManager { } } - //NotificationsManager.savePeerSettings(peerID, dialog.notify_settings); // warning - - /* if(dialog.pts || dialog.pFlags.pts) { - console.warn('dialog pts!', dialog, dialog.pts); - } */ - if(channelID && dialog.pts) { apiUpdatesManager.addChannelState(channelID, dialog.pts); } @@ -2190,8 +2240,7 @@ export class AppMessagesManager { var foundMsgs: number[] = []; var useSearchCache = !query; var newSearchFilter = {peer: peerID, filter: inputFilter}; - var sameSearchCache = useSearchCache - && deepEqual(this.lastSearchFilter, newSearchFilter); //angular.equals(this.lastSearchFilter, newSearchFilter); + var sameSearchCache = useSearchCache && deepEqual(this.lastSearchFilter, newSearchFilter); if(useSearchCache && !sameSearchCache) { // console.warn(dT(), 'new search filter', lastSearchFilter, newSearchFilter) @@ -2313,7 +2362,7 @@ export class AppMessagesManager { if(peerID || !query) { apiPromise = apiManager.invokeApi('messages.search', { flags: 0, - peer: AppPeersManager.getInputPeerByID(peerID), + peer: appPeersManager.getInputPeerByID(peerID), q: query || '', filter: inputFilter || {_: 'inputMessagesFilterEmpty'}, min_date: 0, @@ -2342,7 +2391,7 @@ export class AppMessagesManager { apiPromise = apiManager.invokeApi('messages.searchGlobal', { q: query, offset_rate: offsetRate, - offset_peer: AppPeersManager.getInputPeerByID(offsetPeerID), + offset_peer: appPeersManager.getInputPeerByID(offsetPeerID), offset_id: appMessagesIDsManager.getMessageLocalID(offsetID), limit: limit || 20 }, { @@ -2396,7 +2445,7 @@ export class AppMessagesManager { let pinnedIndex: number; if(dialog) { - if(dialog.pinnedIndex) { + if(dialog.hasOwnProperty('pinnedIndex')) { pinnedIndex = dialog.pinnedIndex; } else { dialog.pinnedIndex = pinnedIndex = this.pinnedIndex++; @@ -2405,6 +2454,10 @@ export class AppMessagesManager { pinnedIndex = this.pinnedIndex++; } + if(pinnedIndex > this.pinnedIndex) { + this.pinnedIndex = pinnedIndex; + } + return 0x7fffff00 + (pinnedIndex & 0xff); } @@ -2428,13 +2481,13 @@ export class AppMessagesManager { delete this.newDialogsToHandle[peerID]; } else { this.pushDialogToStorage(dialog); - if(!AppPeersManager.isChannel(+peerID)) { + if(!appPeersManager.isChannel(+peerID)) { newMaxSeenID = Math.max(newMaxSeenID, dialog.top_message || 0); } } } - console.log('after order:', this.dialogsStorage.dialogs[0].map(d => d.peerID)); + //console.log('after order:', this.dialogsStorage[0].map(d => d.peerID)); if(newMaxSeenID != 0) { this.incrementMaxSeenID(newMaxSeenID); @@ -2452,7 +2505,7 @@ export class AppMessagesManager { public readHistory(peerID: number, maxID = 0, minID = 0): Promise { // console.trace('start read') - var isChannel = AppPeersManager.isChannel(peerID); + var isChannel = appPeersManager.isChannel(peerID); var historyStorage = this.historiesStorage[peerID]; var foundDialog = this.getDialogByPeerID(peerID)[0]; @@ -2489,7 +2542,7 @@ export class AppMessagesManager { }); } else { apiPromise = apiManager.invokeApi('messages.readHistory', { - peer: AppPeersManager.getInputPeerByID(peerID), + peer: appPeersManager.getInputPeerByID(peerID), max_id: maxID }).then((affectedMessages: any) => { apiUpdatesManager.processUpdateMessage({ @@ -2539,9 +2592,6 @@ export class AppMessagesManager { if(message && !message.pFlags.out) { message.pFlags.unread = false; - if(this.messagesForDialogs[messageID]) { - this.messagesForDialogs[messageID].pFlags.unread = false; - } //NotificationsManager.cancel('msg' + messageID); // warning } @@ -2549,7 +2599,7 @@ export class AppMessagesManager { } } - // NotificationsManager.soundReset(AppPeersManager.getPeerString(peerID)) // warning + // NotificationsManager.soundReset(appPeersManager.getPeerString(peerID)) // warning return historyStorage.readPromise; } @@ -2602,7 +2652,7 @@ export class AppMessagesManager { if(pendingData) { var peerID: number = pendingData[0]; var tempID = pendingData[1]; - var channelID = AppPeersManager.isChannel(peerID) ? -peerID : 0; + var channelID = appPeersManager.isChannel(peerID) ? -peerID : 0; var mid = appMessagesIDsManager.getFullMessageID(update.id, channelID); var message = this.messagesStorage[mid]; if(message) { @@ -2721,7 +2771,7 @@ export class AppMessagesManager { case 'updateDialogUnreadMark': { console.log('updateDialogUnreadMark', update); - let peerID = AppPeersManager.getPeerID(update.peer.peer); + let peerID = appPeersManager.getPeerID(update.peer.peer); let foundDialog = this.getDialogByPeerID(peerID); if(!foundDialog.length) { @@ -2750,7 +2800,7 @@ export class AppMessagesManager { peers.forEach((folderPeer: any) => { let {folder_id, peer} = folderPeer; - let peerID = AppPeersManager.getPeerID(peer); + let peerID = appPeersManager.getPeerID(peer); let foundDialog = this.getDialogByPeerID(peerID); if(!foundDialog.length) { this.newDialogsToHandle[peerID] = {reload: true}; @@ -2758,7 +2808,7 @@ export class AppMessagesManager { let dialog = foundDialog[0]; this.newDialogsToHandle[peerID] = dialog; - this.dialogsStorage.dialogs[dialog.folder_id].splice(foundDialog[1], 1); + this.dialogsStorage[dialog.folder_id].splice(foundDialog[1], 1); dialog.folder_id = folder_id; this.generateIndexForDialog(dialog); @@ -2770,7 +2820,7 @@ export class AppMessagesManager { case 'updateDialogPinned': { console.log('updateDialogPinned', update); - let peerID = AppPeersManager.getPeerID(update.peer.peer); + let peerID = appPeersManager.getPeerID(update.peer.peer); let foundDialog = this.getDialogByPeerID(peerID); this.scheduleHandleNewDialogs(); @@ -2806,7 +2856,7 @@ export class AppMessagesManager { newPinned[dialog.peerID] = true; }); - this.dialogsStorage.dialogs[0].forEach((dialog: any) => { + this.dialogsStorage[0].forEach((dialog: any) => { let peerID = dialog.peerID; if(dialog.pFlags.pinned && !newPinned[peerID]) { this.newDialogsToHandle[peerID] = {reload: true}; @@ -2817,13 +2867,13 @@ export class AppMessagesManager { break; } - console.log('before order:', this.dialogsStorage.dialogs[0].map(d => d.peerID)); + //console.log('before order:', this.dialogsStorage[0].map(d => d.peerID)); this.pinnedIndex = 0; let willHandle = false; update.order.reverse(); // index must be higher update.order.forEach((peer: any) => { - let peerID = AppPeersManager.getPeerID(peer.peer); + let peerID = appPeersManager.getPeerID(peer.peer); newPinned[peerID] = true; let foundDialog = this.getDialogByPeerID(peerID); @@ -2842,7 +2892,7 @@ export class AppMessagesManager { willHandle = true; }); - this.dialogsStorage.dialogs[0].forEach(dialog => { + this.dialogsStorage[0].forEach(dialog => { let peerID = dialog.peerID; if(dialog.pFlags.pinned && !newPinned[peerID]) { this.newDialogsToHandle[peerID] = {reload: true}; @@ -2901,7 +2951,7 @@ export class AppMessagesManager { var isOut = update._ == 'updateReadHistoryOutbox' || update._ == 'updateReadChannelOutbox'; var channelID: number = update.channel_id; var maxID = appMessagesIDsManager.getFullMessageID(update.max_id, channelID); - var peerID = channelID ? -channelID : AppPeersManager.getPeerID(update.peer); + var peerID = channelID ? -channelID : appPeersManager.getPeerID(update.peer); var foundDialog = this.getDialogByPeerID(peerID); var history = (this.historiesStorage[peerID] || {}).history || []; var newUnreadCount = 0; @@ -2935,9 +2985,7 @@ export class AppMessagesManager { if(!foundAffected) { foundAffected = true; } - if(this.messagesForDialogs[messageID]) { - this.messagesForDialogs[messageID].pFlags.unread = false; - } + if(!message.pFlags.out) { if(foundDialog[0]) { newUnreadCount = --foundDialog[0].unread_count; @@ -3007,33 +3055,22 @@ export class AppMessagesManager { case 'updateDeleteMessages': case 'updateDeleteChannelMessages': { - var historiesUpdated: any = {}; - var channelID: number = update.channel_id; - var messageID: number; - var message, i; - var peerID: number, foundDialog: ReturnType; - let history: any; - var peerMessagesToHandle; - var peerMessagesHandlePos; - - for (i = 0; i < update.messages.length; i++) { - messageID = appMessagesIDsManager.getFullMessageID(update.messages[i], channelID); - message = this.messagesStorage[messageID]; + let historiesUpdated: {[peerID: number]: {count: number, unread: number, msgs: {[mid: number]: true}}} = {}; + let channelID: number = update.channel_id; + + for(let i = 0; i < update.messages.length; i++) { + let messageID = appMessagesIDsManager.getFullMessageID(update.messages[i], channelID); + let message = this.messagesStorage[messageID]; if(message) { - peerID = this.getMessagePeer(message); - history = historiesUpdated[peerID] || (historiesUpdated[peerID] = {count: 0, unread: 0, msgs: {}}); + let peerID = this.getMessagePeer(message); + let history = historiesUpdated[peerID] || (historiesUpdated[peerID] = {count: 0, unread: 0, msgs: {}}); if(!message.pFlags.out && message.pFlags.unread) { history.unread++; - // NotificationsManager.cancel('msg' + messageID); // warning } history.count++; history.msgs[messageID] = true; - if(this.messagesForDialogs[messageID]) { - this.messagesForDialogs[messageID].deleted = true; - delete this.messagesForDialogs[messageID]; - } message.deleted = true this.messagesStorage[messageID] = { deleted: true, @@ -3045,9 +3082,9 @@ export class AppMessagesManager { date: message.date }; - peerMessagesToHandle = this.newMessagesToHandle[peerID]; + let peerMessagesToHandle = this.newMessagesToHandle[peerID]; if(peerMessagesToHandle && peerMessagesToHandle.length) { - peerMessagesHandlePos = peerMessagesToHandle.indexOf(messageID); + let peerMessagesHandlePos = peerMessagesToHandle.indexOf(messageID); if(peerMessagesHandlePos != -1) { peerMessagesToHandle.splice(peerMessagesHandlePos); } @@ -3055,13 +3092,13 @@ export class AppMessagesManager { } } - Object.keys(historiesUpdated).forEach((peerID: string) => { - let updatedData = historiesUpdated[peerID]; - var historyStorage = this.historiesStorage[peerID]; + Object.keys(historiesUpdated).forEach(peerID => { + let updatedData = historiesUpdated[+peerID]; + let historyStorage = this.historiesStorage[peerID]; if(historyStorage !== undefined) { - var newHistory = [] - var newPending = [] - for(var i = 0; i < historyStorage.history.length; i++) { + let newHistory: number[] = []; + let newPending: number[] = []; + for(let i = 0; i < historyStorage.history.length; i++) { if(!updatedData.msgs[historyStorage.history[i]]) { newHistory.push(historyStorage.history[i]); } @@ -3070,13 +3107,13 @@ export class AppMessagesManager { if(updatedData.count && historyStorage.count !== null && historyStorage.count > 0) { - historyStorage.count -= updatedData.count + historyStorage.count -= updatedData.count; if(historyStorage.count < 0) { historyStorage.count = 0; } } - for(var i = 0; i < historyStorage.pending.length; i++) { + for(let i = 0; i < historyStorage.pending.length; i++) { if(!updatedData.msgs[historyStorage.pending[i]]) { newPending.push(historyStorage.pending[i]); } @@ -3086,7 +3123,7 @@ export class AppMessagesManager { $rootScope.$broadcast('history_delete', {peerID: peerID, msgs: updatedData.msgs}); } - var foundDialog = this.getDialogByPeerID(+peerID)[0]; + let foundDialog = this.getDialogByPeerID(+peerID)[0]; if(foundDialog) { if(updatedData.unread) { foundDialog.unread_count -= updatedData.unread; @@ -3101,7 +3138,7 @@ export class AppMessagesManager { this.reloadConversation(+peerID); } } - }) + }); break; } @@ -3127,7 +3164,7 @@ export class AppMessagesManager { this.reloadConversation(-channelID); } else { if(foundDialog[0]) { - this.dialogsStorage.dialogs[foundDialog[0].folder_id].splice(foundDialog[1], 1); + this.dialogsStorage[foundDialog[0].folder_id].splice(foundDialog[1], 1); $rootScope.$broadcast('dialog_drop', {peerID: peerID, dialog: foundDialog[0]}); } } @@ -3141,7 +3178,7 @@ export class AppMessagesManager { let peerID = -channelID; let foundDialog = this.getDialogByPeerID(peerID); if(foundDialog[0]) { - this.dialogsStorage.dialogs[foundDialog[0].folder_id].splice(foundDialog[1], 1); + this.dialogsStorage[foundDialog[0].folder_id].splice(foundDialog[1], 1); } delete this.historiesStorage[peerID]; @@ -3167,8 +3204,6 @@ export class AppMessagesManager { } case 'updateServiceNotification': { - // update.inbox_date = tsNow(true) - // update.pFlags = {popup: true} var fromID = 777000; var peerID = fromID; var messageID = this.tempID--; @@ -3176,7 +3211,7 @@ export class AppMessagesManager { _: 'message', id: messageID, from_id: fromID, - to_id: AppPeersManager.getOutputPeer(peerID), + to_id: appPeersManager.getOutputPeer(peerID), flags: 0, pFlags: {unread: true}, date: (update.inbox_date || tsNow(true)) + serverTimeManager.serverTimeOffset, @@ -3203,9 +3238,7 @@ export class AppMessagesManager { message: message }); } - if(update.pFlags.popup && update.message) { - //ErrorService.show({error: {code: 400, type: 'UPDATE_SERVICE_NOTIFICATION'}, historyMessage: historyMessage}); // warning - } + break; } } @@ -3464,14 +3497,14 @@ export class AppMessagesManager { } public requestHistory(peerID: number, maxID: number, limit: number, offset = 0): Promise { - var isChannel = AppPeersManager.isChannel(peerID); + var isChannel = appPeersManager.isChannel(peerID); //console.trace('requestHistory', peerID, maxID, limit, offset); $rootScope.$broadcast('history_request'); return apiManager.invokeApi('messages.getHistory', { - peer: AppPeersManager.getInputPeerByID(peerID), + peer: appPeersManager.getInputPeerByID(peerID), offset_id: maxID ? appMessagesIDsManager.getMessageLocalID(maxID) : 0, offset_date: 0, add_offset: offset || 0, @@ -3523,7 +3556,7 @@ export class AppMessagesManager { _: 'messageService', id: messageID, from_id: peerID, - to_id: AppPeersManager.getOutputPeer(peerID), + to_id: appPeersManager.getOutputPeer(peerID), flags: 0, pFlags: {}, date: tsNow(true) + serverTimeManager.serverTimeOffset, @@ -3617,16 +3650,14 @@ export class AppMessagesManager { public wrapSingleMessage(msgID: number) { if(this.messagesStorage[msgID]) { - //let ret = this.wrapForDialog(msgID); // hm $rootScope.$broadcast('messages_downloaded', [msgID]); - //return ret; return {mid: msgID, loading: false}; } if(this.needSingleMessages.indexOf(msgID) == -1) { this.needSingleMessages.push(msgID); if(this.fetchSingleMessagesTimeout == 0) { - this.fetchSingleMessagesTimeout = window.setTimeout(this.fetchSingleMessages.bind(this), 25); + this.fetchSingleMessagesTimeout = window.setTimeout(this.fetchSingleMessages.bind(this), 10); } return {mid: msgID, loading: true}; diff --git a/src/lib/appManagers/appPhotosManager.ts b/src/lib/appManagers/appPhotosManager.ts index ec90604c..b384bfca 100644 --- a/src/lib/appManagers/appPhotosManager.ts +++ b/src/lib/appManagers/appPhotosManager.ts @@ -184,34 +184,47 @@ export class AppPhotosManager { arr[164] = bytes[1]; arr[166] = bytes[2]; } else { - arr = bytes; + arr = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes); } //console.log('setAttachmentPreview', bytes, arr, div, isSticker); let blob = new Blob([arr], {type: "image/jpeg"}); + + /* let reader = new FileReader(); + reader.onloadend = () => { + let src = reader.result; + }; + reader.readAsDataURL(blob); */ if(background) { let url = URL.createObjectURL(blob); let img = new Image(); img.src = url; - img.onload = () => { + img.addEventListener('load', () => { element.style.backgroundImage = 'url(' + url + ')'; - }; + }); + return element; //element.style.backgroundImage = 'url(' + url + ')'; } else { if(element instanceof SVGSVGElement) { let image = element.firstElementChild as SVGImageElement || document.createElementNS("http://www.w3.org/2000/svg", "image"); image.setAttributeNS(null, 'href', URL.createObjectURL(blob)); element.append(image); + + return image; + } else if(element instanceof HTMLImageElement) { + element.src = URL.createObjectURL(blob); + return element; } else { - let image = new Image(); - image.src = URL.createObjectURL(blob); + let img = new Image(); + img.style.width = '100%'; + img.style.height = '100%'; - image.style.width = '100%'; - image.style.height = '100%'; - element.append(image); + img.src = URL.createObjectURL(blob); + element.append(img); + return img; } } } diff --git a/src/lib/appManagers/appSidebarRight.ts b/src/lib/appManagers/appSidebarRight.ts index 2e92cccf..1a739150 100644 --- a/src/lib/appManagers/appSidebarRight.ts +++ b/src/lib/appManagers/appSidebarRight.ts @@ -1,4 +1,4 @@ -import { horizontalMenu, formatPhoneNumber, putPreloader, renderImageFromUrl } from "../../components/misc"; +import { horizontalMenu, putPreloader, renderImageFromUrl } from "../../components/misc"; //import Scrollable from '../../components/scrollable'; import Scrollable from '../../components/scrollable_new'; import { $rootScope } from "../utils"; @@ -18,6 +18,22 @@ import AvatarElement from "../../components/avatar"; const testScroll = false; +let setText = (text: string, el: HTMLDivElement) => { + window.requestAnimationFrame(() => { + if(el.childElementCount > 1) { + el.firstElementChild.remove(); + } + + let p = document.createElement('p'); + p.innerHTML = text; + el.prepend(p); + + el.style.display = ''; + + //this.scroll.getScrollTopOffset(); + }); +}; + class AppSidebarRight { public sidebarEl = document.getElementById('column-right') as HTMLDivElement; public profileContainer = this.sidebarEl.querySelector('.profile-container') as HTMLDivElement; @@ -56,7 +72,7 @@ class AppSidebarRight { 'inputMessagesFilterUrl', 'inputMessagesFilterMusic' ]; - public sharedMediaType: string = ''; + public sharedMediaType: AppSidebarRight['sharedMediaTypes'][number] = ''; private sharedMediaSelected: HTMLDivElement = null; private lazyLoadQueueSidebar = new LazyLoadQueue(5); @@ -91,6 +107,8 @@ class AppSidebarRight { public privateSearch = new AppSearch(this.searchContainer.querySelector('.chats-container') as HTMLDivElement, this.searchInput, { messages: new SearchGroup('Private Search', 'messages') }); + + private loadMutex: Promise = Promise.resolve(); constructor() { let container = this.profileContentEl.querySelector('.content-container .tabs-container') as HTMLDivElement; @@ -156,7 +174,7 @@ class AppSidebarRight { let ids = Object.keys(this.mediaDivsByIDs).map(k => +k).sort((a, b) => a - b); let idx = ids.findIndex(i => i == messageID); - let targets = ids.map(id => ({element: this.mediaDivsByIDs[id], mid: id})); + let targets = ids.map(id => ({element: this.mediaDivsByIDs[id].firstElementChild as HTMLElement, mid: id})); appMediaViewer.openMedia(message, target, false, this.sidebarEl, targets.slice(idx + 1).reverse(), targets.slice(0, idx).reverse(), true); }); @@ -289,12 +307,11 @@ class AppSidebarRight { return filtered; } - public performSearchResult(messages: any[], type: string) { + public async performSearchResult(messages: any[], type: string) { let peerID = this.peerID; - let sharedMediaDiv: HTMLDivElement; - let elemsToAppend: HTMLElement[] = []; + let promises: Promise[] = []; // https://core.telegram.org/type/MessagesFilter switch(type) { @@ -305,22 +322,23 @@ class AppSidebarRight { let media = message.media.photo || message.media.document || (message.media.webpage && message.media.webpage.document); let div = document.createElement('div'); + div.classList.add('media-item'); //console.log(message, photo); let isPhoto = media._ == 'photo'; let photo = isPhoto ? appPhotosManager.getPhoto(media.id) : null; - let isDownloaded = (photo && photo.downloaded) || appPhotosManager.getDocumentCachedThumb(media.id); - if(!isDownloaded) { - //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); - } */ + let isDownloaded: boolean; + if(photo) { + isDownloaded = photo.downloaded > 0; + } else { + let cachedThumb = appPhotosManager.getDocumentCachedThumb(media.id); + isDownloaded = cachedThumb?.downloaded > 0; } + + let img = new Image(); + img.classList.add('media-image'); + div.append(img); //this.log('inputMessagesFilterPhotoVideo', message, media); @@ -349,14 +367,37 @@ class AppSidebarRight { let url = (photo && photo.url) || appPhotosManager.getDocumentCachedThumb(media.id).url; if(url) { - renderImageFromUrl(div, url); + renderImageFromUrl(img, url); } }); - div.dataset.mid = '' + message.mid; + img.dataset.mid = '' + message.mid; + + let sizes = media.sizes || media.thumbs; + if(isDownloaded || (sizes && sizes[0].bytes)) { + let promise = new Promise((resolve, reject) => { + img.addEventListener('load', () => { + clearTimeout(timeout); + resolve(); + }); + + let timeout = setTimeout(() => { + this.log('did not loaded', img, media, isDownloaded, sizes); + reject(); + }, 1e3); + }); + + promises.push(promise); + } if(isDownloaded) load(); - else this.lazyLoadQueueSidebar.push({div, load}); + else { + if(sizes && sizes[0].bytes) { + appPhotosManager.setAttachmentPreview(sizes[0].bytes, img, false, false); + } + + this.lazyLoadQueueSidebar.push({div, load}); + } this.lastSharedMediaDiv.append(div); if(this.lastSharedMediaDiv.childElementCount == 3) { @@ -365,6 +406,7 @@ class AppSidebarRight { } this.lastSharedMediaDiv = document.createElement('div'); + this.lastSharedMediaDiv.classList.add('media-row'); } this.mediaDivsByIDs[message.mid] = div; @@ -397,6 +439,8 @@ class AppSidebarRight { //this.log('wrapping webpage', webpage); + previewDiv.innerText = (webpage.title || webpage.description || webpage.url || webpage.display_url).slice(0, 1); + previewDiv.classList.add('empty'); if(webpage.photo) { let load = () => appPhotosManager.preloadPhoto(webpage.photo.id, appPhotosManager.choosePhotoSize(webpage.photo, 60, 60)) .then(() => { @@ -405,13 +449,12 @@ class AppSidebarRight { return; } + previewDiv.classList.remove('empty'); + renderImageFromUrl(previewDiv, webpage.photo.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 || ''; @@ -457,11 +500,19 @@ class AppSidebarRight { if(this.lastSharedMediaDiv.childElementCount && !this.scroll.contains(this.lastSharedMediaDiv)) { elemsToAppend.push(this.lastSharedMediaDiv); } + + if(this.loadMutex) { + promises.push(this.loadMutex); + } if(elemsToAppend.length) { - //window.requestAnimationFrame(() => { - //elemsToAppend.forEach(el => this.scroll.append(el, false)); - //}); + if(promises.length) { + await Promise.all(promises); + if(this.peerID != peerID) { + this.log.warn('peer changed'); + return; + } + } sharedMediaDiv.append(...elemsToAppend); } @@ -521,7 +572,7 @@ class AppSidebarRight { this.usedFromHistory[type] = used; if(messages.length) { - this.performSearchResult(messages, type); + return this.performSearchResult(messages, type); } return Promise.resolve(); @@ -556,7 +607,7 @@ class AppSidebarRight { this.usedFromHistory[type] = history.length; if(ids.length) { - this.performSearchResult(this.filterMessagesByType(ids, type), type); + return this.performSearchResult(this.filterMessagesByType(ids, type), type); } }, (err) => { this.log.error('load error:', err); @@ -567,77 +618,77 @@ class AppSidebarRight { return Promise.all(promises); } - - public fillProfileElements() { - let peerID = this.peerID = $rootScope.selectedPeerID; + + public cleanup() { this.loadSidebarMediaPromises = {}; this.loadedAllMedia = {}; this.lastSharedMediaDiv = document.createElement('div'); + this.lastSharedMediaDiv.classList.add('media-row'); + + this.prevTabID = -1; + this.scroll.setVirtualContainer(null); + + this.mediaDivsByIDs = {}; - //this.log('fillProfileElements'); + this.lazyLoadQueueSidebar.clear(); + this.sharedMediaTypes.forEach(type => { + this.usedFromHistory[type] = 0; + }); + + this.sharedMediaType = 'inputMessagesFilterPhotoVideo'; + } + + public cleanupHTML() { this.contentContainer.classList.remove('loaded'); - this.profileElements.avatar.setAttribute('peer', '' + peerID); - - window.requestAnimationFrame(() => { - this.profileContentEl.parentElement.scrollTop = 0; - this.profileElements.bio.style.display = 'none'; - this.profileElements.phone.style.display = 'none'; - this.profileElements.username.style.display = 'none'; - this.profileElements.notificationsRow.style.display = ''; - this.profileElements.notificationsCheckbox.checked = true; - this.profileElements.notificationsStatus.innerText = 'Enabled'; + this.profileContentEl.parentElement.scrollTop = 0; + this.profileElements.bio.style.display = 'none'; + this.profileElements.phone.style.display = 'none'; + this.profileElements.username.style.display = 'none'; + this.profileElements.notificationsRow.style.display = ''; + this.profileElements.notificationsCheckbox.checked = true; + this.profileElements.notificationsStatus.innerText = 'Enabled'; + + if(this.urlsToRevoke.length) { + this.urlsToRevoke.forEach(url => { + URL.revokeObjectURL(url); + }); + this.urlsToRevoke.length = 0; + } + + Object.keys(this.sharedMedia).forEach(key => { + this.sharedMedia[key].innerHTML = ''; - Object.keys(this.sharedMedia).forEach(key => { - this.sharedMedia[key].innerHTML = ''; - + if(!this.historiesStorage[this.peerID] || !this.historiesStorage[this.peerID][key]) { let parent = this.sharedMedia[key].parentElement; if(!parent.querySelector('.preloader')) { putPreloader(parent, true); } - }); - - this.prevTabID = -1; - this.scroll.setVirtualContainer(null); - (this.profileTabs.firstElementChild.children[1] as HTMLLIElement).click(); // set media - - //this.scroll.getScrollTopOffset(); - - this.loadSidebarMedia(true); - }); - - this.mediaDivsByIDs = {}; - - this.lazyLoadQueueSidebar.clear(); - - this.urlsToRevoke.forEach(url => { - URL.revokeObjectURL(url); - }); - this.urlsToRevoke.length = 0; - - this.sharedMediaTypes.forEach(type => { - this.usedFromHistory[type] = 0; + } }); - - let setText = (text: string, el: HTMLDivElement) => { - window.requestAnimationFrame(() => { - if(el.childElementCount > 1) { - el.firstElementChild.remove(); - } - - let p = document.createElement('p'); - p.innerHTML = text; - el.prepend(p); - - el.style.display = ''; - //this.scroll.getScrollTopOffset(); - }); - }; - + (this.profileTabs.firstElementChild.children[1] as HTMLLIElement).click(); // set media + } + + public setLoadMutex(promise: Promise) { + this.loadMutex = promise; + } + + public setPeer(peerID: number) { + this.peerID = peerID; + this.cleanup(); + } + + public fillProfileElements() { + let peerID = this.peerID = $rootScope.selectedPeerID; + + this.cleanupHTML(); + + this.profileElements.avatar.setAttribute('peer', '' + peerID); + // username - if(peerID != appImManager.myID) { + if(peerID != $rootScope.myID) { let username = appPeersManager.getPeerUsername(peerID); if(username) { setText(appPeersManager.getPeerUsername(peerID), this.profileElements.username); @@ -656,13 +707,12 @@ class AppSidebarRight { } else { window.requestAnimationFrame(() => { this.profileElements.notificationsRow.style.display = 'none'; - //this.scroll.getScrollTopOffset(); - }) + }); } if(peerID > 0) { let user = appUsersManager.getUser(peerID); - if(user.phone && peerID != appImManager.myID) { + if(user.phone && peerID != $rootScope.myID) { setText(user.rPhone, this.profileElements.phone); } @@ -672,7 +722,7 @@ class AppSidebarRight { return; } - if(userFull.rAbout && peerID != appImManager.myID) { + if(userFull.rAbout && peerID != $rootScope.myID) { setText(userFull.rAbout, this.profileElements.bio); } @@ -682,8 +732,6 @@ class AppSidebarRight { appImManager.pinnedMsgID = userFull.pinned_msg_id; appMessagesManager.wrapSingleMessage(userFull.pinned_msg_id); } - - //this.scroll.getScrollTopOffset(); }); } else { let chat = appPeersManager.getPeer(peerID); @@ -699,13 +747,8 @@ class AppSidebarRight { if(chatFull.about) { setText(RichTextProcessor.wrapRichText(chatFull.about), this.profileElements.bio); } - - //this.scroll.getScrollTopOffset(); }); } - - //this.scroll.getScrollTopOffset(); - //this.loadSidebarMedia(); } } diff --git a/src/lib/appManagers/appStickersManager.ts b/src/lib/appManagers/appStickersManager.ts index 2e5825bf..2d0fc200 100644 --- a/src/lib/appManagers/appStickersManager.ts +++ b/src/lib/appManagers/appStickersManager.ts @@ -60,9 +60,6 @@ class AppStickersManager { for(let id in sets) { let set = sets[id]; set.documents.forEach(doc => { - delete doc.downloaded; - delete doc.url; - this.saveSticker(doc); }); } @@ -78,13 +75,6 @@ class AppStickersManager { public saveSticker(doc: MTDocument) { if(this.documents[doc.id]) return this.documents[doc.id]; - /* Object.keys(doc).forEach(key => { - if(doc[key] instanceof Uint8Array) { - doc[key] = Array.from(doc[key]); - } - }); */ - - doc.file_reference = Array.from(doc.file_reference); doc = appDocsManager.saveDoc(doc); this.documents[doc.id] = doc; diff --git a/src/lib/appManagers/appUsersManager.ts b/src/lib/appManagers/appUsersManager.ts index 977aa4b4..cb54a09d 100644 --- a/src/lib/appManagers/appUsersManager.ts +++ b/src/lib/appManagers/appUsersManager.ts @@ -228,7 +228,7 @@ export class AppUsersManager { var nameWords = apiUser.sortName.split(' '); var firstWord = nameWords.shift(); var lastWord = nameWords.pop(); - apiUser.initials = firstWord.charAt(0) + (lastWord ? lastWord.charAt(0) : firstWord.charAt(1)); + apiUser.initials = firstWord.charAt(0) + (lastWord ? lastWord.charAt(0) : ''); if(apiUser.status) { if(apiUser.status.expires) { diff --git a/src/lib/polyfill.ts b/src/lib/polyfill.ts index 88bd2c7f..42b48fd2 100644 --- a/src/lib/polyfill.ts +++ b/src/lib/polyfill.ts @@ -84,6 +84,10 @@ Uint8Array.prototype.toString = function() { return String.fromCharCode.apply(null, [...this]); }; +Uint8Array.prototype.toJSON = function() { + return [...this]; +}; + Array.prototype.forEachReverse = function(callback: (value: T, index?: number, array?: Array) => void) { let length = this.length; for(var i = length - 1; i >= 0; --i) { @@ -113,7 +117,8 @@ declare global { hex: string; randomize: () => Uint8Array, concat: (...args: Array) => Uint8Array, - toString: () => string + toString: () => string, + toJSON: () => number[], } interface Array { diff --git a/src/lib/storage.ts b/src/lib/storage.ts index 4f0b3fc4..93f1bf02 100644 --- a/src/lib/storage.ts +++ b/src/lib/storage.ts @@ -67,8 +67,11 @@ class ConfigStorage { value = obj[key]; key = prefix + key; this.cache[key] = value; - //value = value instanceof Uint8Array ? Array.from(value) : JSON.stringify(value); - value = JSON.stringify(value); + value = JSON.stringify(value, (key, value) => { + if(key == 'downloaded' || (key == 'url' && value.indexOf('blob:') === 0)) return undefined; + return value; + }); + if(this.useLs) { try { //console.log('setItem', key, value); diff --git a/src/lib/utils.js b/src/lib/utils.js index 4d9cbc73..cb58136e 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -754,7 +754,8 @@ function versionCompare (ver1, ver2) { } - var badCharsRe = /[`~!@#$%^&*()\-_=+\[\]\\|{}'";:\/?.>,<\s]+/g, + //var badCharsRe = /[`~!@#$%^&*()\-_=+\[\]\\|{}'";:\/?.>,<\s]+/g, + var badCharsRe = /[`~!@#$%^&*()\-_=+\[\]\\|{}'";:\/?.>,<]+/g, trimRe = /^\s+|\s$/g function createIndex () { @@ -766,7 +767,7 @@ function versionCompare (ver1, ver2) { function cleanSearchText(text, latinize = true) { var hasTag = text.charAt(0) == '%'; - text = text.replace(badCharsRe, ' ').replace(trimRe, ''); + text = text.replace(badCharsRe, '').replace(trimRe, ''); if(latinize) { text = text.replace(/[^A-Za-z0-9]/g, (ch) => { var latinizeCh = Config.LatinizeMap[ch]; diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index 296ccac4..b7080f2f 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -14,36 +14,52 @@ .bubble { padding-top: 5px; - /* display: grid; - grid-template-columns: 1fr $chat-max-width 1fr; - grid-row-gap: 0px; */ max-width: $chat-max-width; margin: 0 auto; position: relative; &.is-selected { - &:before { + &:after { position: absolute; - width: 200%; - height: 100%; - background-color: rgba(0, 132, 255, .3); - content: " "; - display: block; left: -50%; top: 0; + height: 100%; + content: " "; + background-color: rgba(0, 132, 255, .3); animation: bubbleSelected 2s linear; + z-index: 1; } - &:not(.is-group-last):before { + &:not(.is-group-last):after { height: calc(100% + 5px); } } + &.is-first-unread { + &:before { + content: "Unread messages"; + height: 30px; + margin-bottom: 5px; + margin-left: -50%; + text-align: center; + color: #538BCC; + line-height: 2.1; + font-weight: 500; + font-size: 15px; + background-color: rgba(255, 255, 255, 0.95); + } + } + + &.is-selected:after, &.is-first-unread:before { + width: 200%; + display: block; + } + &.is-date { position: -webkit-sticky; position: sticky; top: 5px; - z-index: 2; + z-index: 3; pointer-events: none; &.is-sticky { @@ -53,11 +69,6 @@ } } - /* &:before, &:after { - content: " "; - width: 100%; - } */ - &__container { //min-width: 60px; min-width: 56px; @@ -70,6 +81,7 @@ /* font-size: 0; */ width: max-content; height: fit-content; + z-index: 2; > .user-avatar { position: absolute; @@ -560,6 +572,14 @@ .emoji { font-size: 1.2rem; } + + pre, code { + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ + } } // all for audio (not voice) @@ -1204,7 +1224,62 @@ } } +.reply-markup { + position: absolute; + width: 100%; + + &-row { + margin-top: 5px; + overflow: hidden; + height: 40px; + display: flex; + + &:last-child { + border-bottom-left-radius: $border-radius-big; + border-bottom-right-radius: $border-radius-big; + } + } + + &-button { + display: flex; + justify-content: center; + align-items: center; + border-radius: 6px; + background-color: rgba(0, 0, 0, 0.23); + z-index: 2; + font-size: 14px; + user-select: none; + text-align: center; + color: white !important; + outline: none; + border: none; + width: 100%; + cursor: pointer; + position: relative; + transition: background-color 0.35s ease; + + &:hover { + background-color: rgba(0, 0, 0, 0.06); + } + & + & { + margin-left: 5px; + } + + &.is-link:before { + content: $tgico-next; + position: absolute; + right: 2px; + top: 2px; + display: block; + transform: rotate(-45deg); + } + + /* img.emoji { + vertical-align: middle !important; + } */ + } +} poll-element { margin-top: -1px; diff --git a/src/scss/partials/_fonts.scss b/src/scss/partials/_fonts.scss index 2f09bc3a..dfa0d3a1 100644 --- a/src/scss/partials/_fonts.scss +++ b/src/scss/partials/_fonts.scss @@ -129,7 +129,7 @@ content: "\e91f"; } .tgico-next:before { - content: "\e920"; + content: $tgico-next; } .tgico-nosound:before { content: "\e921"; diff --git a/src/scss/partials/_ico.scss b/src/scss/partials/_ico.scss index fb119e03..f5528316 100644 --- a/src/scss/partials/_ico.scss +++ b/src/scss/partials/_ico.scss @@ -5,3 +5,4 @@ $tgico-check: "\e900"; $tgico-checks: "\e95a"; $tgico-sending: "\e919"; $tgico-close: "\e943"; +$tgico-next: "\e920"; diff --git a/src/scss/partials/_mediaViewer.scss b/src/scss/partials/_mediaViewer.scss index 3172afae..0737b854 100644 --- a/src/scss/partials/_mediaViewer.scss +++ b/src/scss/partials/_mediaViewer.scss @@ -1,3 +1,7 @@ +$open-duration: .2s; +//$open-duration: 10s; +$move-duration: .35s; + .media-viewer { position: fixed; top: 0; @@ -20,7 +24,7 @@ flex-direction: column; justify-content: center; color: #8b8b8b; - transition: .2s; + transition: $open-duration; &:hover { color: #fff; @@ -53,7 +57,7 @@ .btn-icon { margin: 0 .25rem; - transition: .2s; + transition: $open-duration; &:hover { color: #fff; @@ -91,14 +95,6 @@ align-items: center; justify-content: center; visibility: hidden; - - &.loading { - img, video { - object-fit: cover; - width: 100%; - height: 100%; - } - } } img, video { @@ -108,22 +104,11 @@ max-width: 1280px; */ } - img { - object-fit: contain; - } - - video { - width: 100%; - height: 100%; - /* object-fit: cover; */ - object-fit: contain; - } - .media-viewer-caption { flex: 1; text-align: center; color: $color-gray; - transition: .2s; + transition: $open-duration; max-width: 50vw; word-break: break-word; overflow: hidden; @@ -165,7 +150,7 @@ top: 50%; transform: translateY(-50%) rotate(90deg); opacity: 0; - transition: .2s opacity; + transition: $open-duration opacity; z-index: 5; /* box-shadow: 0 1px 2px 0 rgba(16, 35, 47, 0.07); */ } @@ -189,40 +174,40 @@ transform-origin: top left; overflow: hidden; //border-radius: 0; - background-repeat: no-repeat; - background-size: cover; - background-position: center center; .ckin__player { width: 100%; height: 100%; + position: absolute; + left: 0; + top: 0; } img, video { width: 100%; height: 100%; - opacity: 1; - transition: .2s opacity; user-select: none; - } - - &.cover { - align-items: center; - - img, video { - width: auto; - height: auto; - overflow: hidden; - opacity: 0; - } + object-fit: cover; } &.active { - transition: .2s transform; + transition: $open-duration transform; } &.moving { - transition: 350ms transform ease; + transition: $move-duration transform ease; } } + + // возможно тут это вообще не нужно + &-aspecter { + width: 100%; + height: 100%; + transform: scale(1); + overflow: hidden; + } + + &-mover.active &-aspecter { + transition: $open-duration all; + } } diff --git a/src/scss/partials/_rightSIdebar.scss b/src/scss/partials/_rightSIdebar.scss index 52d94f44..456847f7 100644 --- a/src/scss/partials/_rightSIdebar.scss +++ b/src/scss/partials/_rightSIdebar.scss @@ -211,7 +211,7 @@ flex-direction: column; padding: 4px 7.5px 7.5px; - > div { + .media-row { display: grid; grid-template-columns: 1fr 1fr 1fr; grid-auto-rows: max-content; @@ -219,14 +219,9 @@ place-items: start; padding-top: 3.5px; - > div { + .media-item { width: 100%; cursor: pointer; - - background-repeat: no-repeat; - background-size: cover; - background-position: center center; - display: flex; background-color: #000; position: relative; @@ -241,8 +236,8 @@ } } - span.video-time { - position: relative; + .video-time { + position: absolute; left: 5px; top: 4px; height: 18px; @@ -254,6 +249,12 @@ color: white; } + .media-image { + width: 100%; + max-height: 100%; + object-fit: cover; + } + /* span.video-play { background-color: $time-background; color: #fff; diff --git a/src/scss/partials/_selector.scss b/src/scss/partials/_selector.scss index 13f5d055..40345d1b 100644 --- a/src/scss/partials/_selector.scss +++ b/src/scss/partials/_selector.scss @@ -14,9 +14,11 @@ flex-direction: column; &-search-container { - flex: 1 1 auto; + flex: 0 0 auto; + //flex: 1 1 auto; position: relative; max-height: 132px; + overflow: hidden; .scrollable { position: relative; diff --git a/src/scss/style.scss b/src/scss/style.scss index 9c1ffca6..77fa41a0 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -276,6 +276,37 @@ input { } } +@keyframes fadeInFadeOut { + 0% { + opacity: 0; + } + + 10% { + opacity: 1; + } + + 50% { + opacity: 1; + } + + to { + opacity: 0; + } +} + +.toast { + position: fixed; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + padding: .5rem 1rem; + background-color: rgba(0, 0, 0, .66); + color: #fff; + font-size: 1rem; + border-radius: $border-radius-medium; + animation: fadeInFadeOut 3s linear forwards; +} + hr { width: 100%; border: none; diff --git a/webpack.common.js b/webpack.common.js index 2869d275..5dc250d6 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -115,6 +115,7 @@ module.exports = { filename: `index.html`, template: 'public/index_template.html', inject: true, + //inject: 'head', /* minify: { removeComments: true, collapseWhitespace: true,