diff --git a/src/components/appSearchSuper..ts b/src/components/appSearchSuper..ts index 5c325cc1..1bd9f87a 100644 --- a/src/components/appSearchSuper..ts +++ b/src/components/appSearchSuper..ts @@ -803,7 +803,7 @@ export default class AppSearchSuper { } }), - appMessagesManager.getConversations(query, 0, 20, 0) + appMessagesManager.getConversations(query, 0, 20, 0).promise .then(onLoad) .then(value => { if(value) { diff --git a/src/components/appSelectPeers.ts b/src/components/appSelectPeers.ts index c8edcfeb..c598a1e9 100644 --- a/src/components/appSelectPeers.ts +++ b/src/components/appSelectPeers.ts @@ -261,7 +261,7 @@ export default class AppSelectPeers { const pageCount = windowSize.windowH / 72 * 1.25 | 0; const tempId = this.getTempId('dialogs'); - const promise = appMessagesManager.getConversations(this.query, this.offsetIndex, pageCount, this.folderId, true); + const promise = appMessagesManager.getConversations(this.query, this.offsetIndex, pageCount, this.folderId, true).promise; this.promise = promise; const value = await promise; if(this.tempIds.dialogs !== tempId) { diff --git a/src/components/scrollable.ts b/src/components/scrollable.ts index 414ba195..b8a5341a 100644 --- a/src/components/scrollable.ts +++ b/src/components/scrollable.ts @@ -212,11 +212,11 @@ export default class Scrollable extends ScrollableBase { } }; - public prepend(...elements: HTMLElement[]) { + public prepend(...elements: (HTMLElement | DocumentFragment)[]) { (this.splitUp || this.padding || this.container).prepend(...elements); } - public append(...elements: HTMLElement[]) { + public append(...elements: (HTMLElement | DocumentFragment)[]) { (this.splitUp || this.padding || this.container).append(...elements); } diff --git a/src/components/singleTransition.ts b/src/components/singleTransition.ts index 6095f27a..6aad59a3 100644 --- a/src/components/singleTransition.ts +++ b/src/components/singleTransition.ts @@ -6,7 +6,14 @@ import rootScope from "../lib/rootScope"; -const SetTransition = (element: HTMLElement, className: string, forwards: boolean, duration: number, onTransitionEnd?: () => void, useRafs?: number) => { +const SetTransition = ( + element: HTMLElement, + className: string, + forwards: boolean, + duration: number, + onTransitionEnd?: () => void, + useRafs?: number +) => { const {timeout, raf} = element.dataset; if(timeout !== undefined) { clearTimeout(+timeout); @@ -19,7 +26,7 @@ const SetTransition = (element: HTMLElement, className: string, forwards: boolea } } - if(useRafs) { + if(useRafs && rootScope.settings.animationsEnabled && duration) { element.dataset.raf = '' + window.requestAnimationFrame(() => { delete element.dataset.raf; SetTransition(element, className, forwards, duration, onTransitionEnd, useRafs - 1); @@ -43,7 +50,7 @@ const SetTransition = (element: HTMLElement, className: string, forwards: boolea onTransitionEnd && onTransitionEnd(); }; - if(!rootScope.settings.animationsEnabled) { + if(!rootScope.settings.animationsEnabled || !duration) { element.classList.remove('animating', 'backwards'); afterTimeout(); return; diff --git a/src/config/app.ts b/src/config/app.ts index c2b084e9..94d8d7cb 100644 --- a/src/config/app.ts +++ b/src/config/app.ts @@ -16,7 +16,7 @@ export const MAIN_DOMAIN = 'web.telegram.org'; const App = { id: 1025907, hash: '452b0359b988148995f22ff0f4229750', - version: '0.8.0', + version: '0.8.1', langPackVersion: '0.3.3', langPack: 'macos', langPackCode: 'en', diff --git a/src/helpers/dom/renderImageFromUrl.ts b/src/helpers/dom/renderImageFromUrl.ts index 3656c510..8571e644 100644 --- a/src/helpers/dom/renderImageFromUrl.ts +++ b/src/helpers/dom/renderImageFromUrl.ts @@ -4,6 +4,8 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ +// import { getHeavyAnimationPromise } from "../../hooks/useHeavyAnimationCheck"; + export const loadedURLs: {[url: string]: boolean} = {}; const set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string) => { if(elem instanceof HTMLImageElement || elem instanceof HTMLVideoElement) elem.src = url; @@ -12,8 +14,12 @@ const set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoE }; // проблема функции в том, что она не подходит для ссылок, пригодна только для blob'ов, потому что обычным ссылкам нужен 'load' каждый раз. -export default function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, - url: string, callback?: (err?: Event) => void, useCache = true) { +export default function renderImageFromUrl( + elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, + url: string, + callback?: (err?: Event) => void, + useCache = true +) { if(!url) { console.error('renderImageFromUrl: no url?', elem, url); callback && callback(); @@ -26,6 +32,7 @@ export default function renderImageFromUrl(elem: HTMLElement | HTMLImageElement } callback && callback(); + // callback && getHeavyAnimationPromise().then(() => callback()); } else { const isImage = elem instanceof HTMLImageElement; const loader = isImage ? elem as HTMLImageElement : new Image(); @@ -39,16 +46,10 @@ export default function renderImageFromUrl(elem: HTMLElement | HTMLImageElement loadedURLs[url] = true; //console.log('onload:', url, performance.now() - perf); - if(callback) { - // TODO: переделать прогрузки аватаров до начала анимации, иначе с этим ожиданием они неприятно появляются - /* getHeavyAnimationPromise().then(() => { - callback(); - }); */ - callback(); - } - - //callback && callback(); - }); + // TODO: переделать прогрузки аватаров до начала анимации, иначе с этим ожиданием они неприятно появляются + // callback && getHeavyAnimationPromise().then(() => callback()); + callback && callback(); + }, {once: true}); if(callback) { loader.addEventListener('error', callback); diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index 3d286bb0..3720ba52 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -4,6 +4,9 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ +import type DialogsStorage from "../storages/dialogs"; +import type {MyDialogFilter as DialogFilter, MyDialogFilter} from "../storages/filters"; +import type { LazyLoadQueueIntersector } from "../../components/lazyLoadQueue"; import AvatarElement from "../../components/avatar"; import DialogsContextMenu from "../../components/dialogsContextMenu"; import { horizontalMenu } from "../../components/horizontalMenu"; @@ -20,7 +23,6 @@ import apiUpdatesManager from "./apiUpdatesManager"; import appPeersManager from './appPeersManager'; import appImManager from "./appImManager"; import appMessagesManager, { Dialog } from "./appMessagesManager"; -import {MyDialogFilter as DialogFilter, MyDialogFilter} from "../storages/filters"; import appStateManager, { State } from "./appStateManager"; import appUsersManager from "./appUsersManager"; import Button from "../../components/button"; @@ -31,7 +33,6 @@ import appNotificationsManager from "./appNotificationsManager"; import PeerTitle from "../../components/peerTitle"; import I18n, { FormatterArguments, i18n, LangPackKey, _i18n } from "../langPack"; import findUpTag from "../../helpers/dom/findUpTag"; -import { LazyLoadQueueIntersector } from "../../components/lazyLoadQueue"; import lottieLoader from "../lottieLoader"; import { wrapLocalSticker, wrapPhoto } from "../../components/wrappers"; import AppEditFolderTab from "../../components/sidebarLeft/tabs/editFolder"; @@ -48,8 +49,8 @@ import { isTouchSupported } from "../../helpers/touchSupport"; import handleTabSwipe from "../../helpers/dom/handleTabSwipe"; import windowSize from "../../helpers/windowSize"; import isInDOM from "../../helpers/dom/isInDOM"; -import appPhotosManager from "./appPhotosManager"; -import DialogsStorage from "../storages/dialogs"; +import appPhotosManager, { MyPhoto } from "./appPhotosManager"; +import { MyDocument } from "./appDocsManager"; export type DialogDom = { avatarEl: AvatarElement, @@ -515,7 +516,7 @@ export class AppDialogsManager { if(this.isDialogMustBeInViewport(dialog)) { if(!this.doms.hasOwnProperty(dialog.peerId)) { - const ret = this.addDialogNew({dialog}); + const ret = this.addListDialog({dialog}); if(ret) { const idx = appMessagesManager.getDialogByPeerId(dialog.peerId)[1]; positionElementByIndex(ret.dom.listEl, this.chatList, idx); @@ -749,11 +750,12 @@ export class AppDialogsManager { try { //console.time('getDialogs time'); - const getConversationPromise = (this.filterId > 1 ? appUsersManager.getContacts() as Promise : Promise.resolve()).then(() => { - return appMessagesManager.getConversations('', offsetIndex, loadCount, filterId, true); - }); + const getConversationsResult = appMessagesManager.getConversations('', offsetIndex, loadCount, filterId, true); + if(getConversationsResult.cached) { + this.chatsPreloader.remove(); + } - const result = await getConversationPromise; + const result = await getConversationsResult.promise; if(this.loadDialogsPromise !== promise) { return; @@ -779,12 +781,26 @@ export class AppDialogsManager { if(result.dialogs.length) { const dialogs = side === 'top' ? result.dialogs.slice().reverse() : result.dialogs; + const container = document.createDocumentFragment(); + const loadPromises: Promise[] = []; + const append = side === 'bottom'; dialogs.forEach((dialog) => { - this.addDialogNew({ + this.addListDialog({ dialog, - append: side === 'bottom' + container, + append, + loadPromises, + isBatch: true }); }); + + if(container.childElementCount) { + if(loadPromises.length) { + await Promise.all(loadPromises).finally(); + } + + this.scroll[append ? 'append' : 'prepend'](container); + } } const offsetDialog = result.dialogs[side === 'top' ? 0 : result.dialogs.length - 1]; @@ -1215,11 +1231,28 @@ export class AppDialogsManager { private reorderDialogs() { //const perf = performance.now(); if(this.reorderDialogsTimeout) { - window.cancelAnimationFrame(this.reorderDialogsTimeout); + return; + } + + if(this.loadDialogsPromise) { + this.loadDialogsPromise.then(() => { + this.reorderDialogs(); + }); + + return; } this.reorderDialogsTimeout = window.requestAnimationFrame(() => { this.reorderDialogsTimeout = 0; + + if(this.loadDialogsPromise) { + this.loadDialogsPromise.then(() => { + this.reorderDialogs(); + }); + + return; + } + const dialogs = appMessagesManager.dialogsStorage.getFolder(this.filterId); const currentOrder = (Array.from(this.chatList.children) as HTMLElement[]).map(el => +el.dataset.peerId); @@ -1253,7 +1286,14 @@ export class AppDialogsManager { }); } - public setLastMessage(dialog: Dialog, lastMessage?: any, dom?: DialogDom, highlightWord?: string) { + public setLastMessage( + dialog: Dialog, + lastMessage?: any, + dom?: DialogDom, + highlightWord?: string, + loadPromises?: Promise[], + isBatch = false + ) { ///////console.log('setlastMessage:', lastMessage); if(!dom) { dom = this.getDialogDom(dialog.peerId); @@ -1280,7 +1320,6 @@ export class AppDialogsManager { return; } - const peer = dialog.peer; const peerId = dialog.peerId; //let peerId = appMessagesManager.getMessagePeer(lastMessage); @@ -1288,35 +1327,49 @@ export class AppDialogsManager { /* if(!dom.lastMessageSpan.classList.contains('user-typing')) */ { + let mediaContainer: HTMLElement; + if(!lastMessage.deleted && !draftMessage) { + const media: MyDocument | MyPhoto = appMessagesManager.getMediaFromMessage(lastMessage); + if(media && (media._ === 'photo' || (['video', 'gif'] as MyDocument['type'][]).includes(media.type))) { + const size = appPhotosManager.choosePhotoSize(media, 20, 20); + + if(size._ !== 'photoSizeEmpty') { + mediaContainer = document.createElement('div'); + mediaContainer.classList.add('dialog-subtitle-media'); + + wrapPhoto({ + photo: media, + message: lastMessage, + container: mediaContainer, + withoutPreloader: true, + size, + loadPromises + }); + + if((media as MyDocument).type === 'video') { + const playIcon = document.createElement('span'); + playIcon.classList.add('tgico-play'); + + mediaContainer.append(playIcon); + } + } + } + } + + const withoutMediaType = !!mediaContainer && !!lastMessage?.message; + let fragment: DocumentFragment; if(highlightWord && lastMessage.message) { - fragment = appMessagesManager.wrapMessageForReply(lastMessage, undefined, undefined, false, highlightWord); + fragment = appMessagesManager.wrapMessageForReply(lastMessage, undefined, undefined, false, highlightWord, withoutMediaType); } else if(draftMessage) { fragment = appMessagesManager.wrapMessageForReply(draftMessage); } else if(!lastMessage.deleted) { - fragment = appMessagesManager.wrapMessageForReply(lastMessage); + fragment = appMessagesManager.wrapMessageForReply(lastMessage, undefined, undefined, false, undefined, withoutMediaType); } - /* if(!lastMessage.deleted && !draftMessage) { - const photo = lastMessage.media?.photo; - if(photo) { - const div = document.createElement('div'); - div.classList.add('dialog-subtitle-media'); - - const size = appPhotosManager.choosePhotoSize(photo, 20, 20); - - wrapPhoto({ - photo, - message: lastMessage, - container: div, - withoutPreloader: true, - size - }); - - fragment.prepend(div); - // dom.subtitleEl.prepend(div); - } - } */ + if(mediaContainer) { + fragment.prepend(mediaContainer); + } replaceContent(dom.lastMessageSpan, fragment); @@ -1326,7 +1379,7 @@ export class AppDialogsManager { bold.classList.add('danger'); bold.append(i18n('Draft'), ': '); dom.lastMessageSpan.prepend(bold); - } else if(peer._ !== 'peerUser' && peerId !== lastMessage.fromId && !lastMessage.action) { + } else if(peerId < 0 && peerId !== lastMessage.fromId && !lastMessage.action) { const sender = appPeersManager.getPeer(lastMessage.fromId); if(sender && sender.id) { const senderBold = document.createElement('b'); @@ -1354,13 +1407,13 @@ export class AppDialogsManager { } else dom.lastTimeSpan.textContent = ''; if(this.doms[peerId] === dom) { - this.setUnreadMessages(dialog); + this.setUnreadMessages(dialog, isBatch); } else { // means search dom.listEl.dataset.mid = lastMessage.mid; } } - private setUnreadMessages(dialog: Dialog) { + private setUnreadMessages(dialog: Dialog, isBatch = false) { const dom = this.getDialogDom(dialog.peerId); if(dialog.folder_id === 1) { @@ -1372,10 +1425,12 @@ export class AppDialogsManager { return; } - const isMuted = appNotificationsManager.isPeerLocalMuted(dialog.peerId, true); - const wasMuted = dom.listEl.classList.contains('is-muted'); - if(isMuted !== wasMuted) { - SetTransition(dom.listEl, 'is-muted', isMuted, 200); + if(!isBatch) { + const isMuted = appNotificationsManager.isPeerLocalMuted(dialog.peerId, true); + const wasMuted = dom.listEl.classList.contains('is-muted'); + if(isMuted !== wasMuted) { + SetTransition(dom.listEl, 'is-muted', isMuted, 200); + } } const lastMessage = dialog.draft?._ === 'draftMessage' ? @@ -1422,12 +1477,14 @@ export class AppDialogsManager { } } - SetTransition(dom.unreadBadge, 'is-visible', hasUnreadBadge, 200, hasUnreadBadge ? undefined : () => { + const transitionDuration = isBatch ? 0 : 200; + + SetTransition(dom.unreadBadge, 'is-visible', hasUnreadBadge, transitionDuration, hasUnreadBadge ? undefined : () => { dom.unreadBadge.remove(); }, !isUnreadBadgeMounted ? 2 : 0); if(dom.mentionsBadge) { - SetTransition(dom.mentionsBadge, 'is-visible', hasMentionsBadge, 200, hasMentionsBadge ? undefined : () => { + SetTransition(dom.mentionsBadge, 'is-visible', hasMentionsBadge, transitionDuration, hasMentionsBadge ? undefined : () => { dom.mentionsBadge.remove(); delete dom.mentionsBadge; }, !isMentionBadgeMounted ? 2 : 0); @@ -1474,9 +1531,55 @@ export class AppDialogsManager { return this.doms[peerId]; } + private getDialog(dialog: Dialog | number): Dialog { + if(typeof(dialog) === 'number') { + const originalDialog = appMessagesManager.getDialogOnly(dialog); + if(!originalDialog) { + return { + peerId: dialog, + peer: appPeersManager.getOutputPeer(dialog), + pFlags: {} + } as any; + } + + return originalDialog; + } + + return dialog; + } + + private addListDialog(options: Parameters[0] & {isBatch?: boolean}) { + const dialog = this.getDialog(options.dialog); + + const filter = appMessagesManager.filtersStorage.getFilter(this.filterId); + if((filter && !appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) || + (!filter && this.filterId !== dialog.folder_id)) { + return; + } + + if(!options.container) { + options.container = this.scroll; + } + + const ret = this.addDialogNew(options); + + if(ret) { + this.doms[dialog.peerId] = ret.dom; + + const isMuted = appNotificationsManager.isPeerLocalMuted(dialog.peerId, true); + if(isMuted) { + ret.dom.listEl.classList.add('is-muted'); + } + + this.setLastMessage(dialog, undefined, undefined, undefined, options.loadPromises, options.isBatch); + } + + return ret; + } + public addDialogNew(options: { dialog: Dialog | number, - container?: HTMLUListElement | Scrollable | false, + container?: Parameters[1], drawStatus?: boolean, rippleEnabled?: boolean, onlyFirstName?: boolean, @@ -1485,60 +1588,40 @@ export class AppDialogsManager { avatarSize?: number, autonomous?: boolean, lazyLoadQueue?: LazyLoadQueueIntersector, + loadPromises?: Promise[] }) { - return this.addDialog(options.dialog, options.container, options.drawStatus, options.rippleEnabled, options.onlyFirstName, options.meAsSaved, options.append, options.avatarSize, options.autonomous, options.lazyLoadQueue); + return this.addDialog(options.dialog, options.container, options.drawStatus, options.rippleEnabled, options.onlyFirstName, options.meAsSaved, options.append, options.avatarSize, options.autonomous, options.lazyLoadQueue, options.loadPromises); } - public addDialog(_dialog: Dialog | number, container?: HTMLUListElement | Scrollable | false, drawStatus = true, rippleEnabled = true, onlyFirstName = false, meAsSaved = true, append = true, avatarSize = 54, autonomous = !!container, lazyLoadQueue?: LazyLoadQueueIntersector) { - let dialog: Dialog; - - if(typeof(_dialog) === 'number') { - let originalDialog = appMessagesManager.getDialogOnly(_dialog); - if(!originalDialog) { - originalDialog = { - peerId: _dialog, - peer: appPeersManager.getOutputPeer(_dialog), - pFlags: {} - } as any; - } - - dialog = originalDialog; - } else { - dialog = _dialog; - } - - const peerId: number = dialog.peerId; - - if(container === undefined) { - if(this.doms[peerId] || dialog.migratedTo !== undefined) return; - - const filter = appMessagesManager.filtersStorage.getFilter(this.filterId); - if((filter && !appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) || (!filter && this.filterId !== dialog.folder_id)) { - return; - } - } + public addDialog(_dialog: Dialog | number, + container?: HTMLElement | Scrollable | DocumentFragment | false, + drawStatus = true, + rippleEnabled = true, + onlyFirstName = false, + meAsSaved = true, + append = true, + avatarSize = 54, + autonomous = !!container, + lazyLoadQueue?: LazyLoadQueueIntersector, + loadPromises?: Promise[]) { + const dialog = this.getDialog(_dialog); + const peerId = dialog.peerId; const avatarEl = new AvatarElement(); + avatarEl.loadPromises = loadPromises; avatarEl.lazyLoadQueue = lazyLoadQueue; avatarEl.setAttribute('dialog', meAsSaved ? '1' : '0'); avatarEl.setAttribute('peer', '' + peerId); avatarEl.classList.add('dialog-avatar', 'avatar-' + avatarSize); - if(drawStatus && peerId !== rootScope.myId && dialog.peer) { - const peer = dialog.peer; - - switch(peer._) { - case 'peerUser': - const user = appUsersManager.getUser(peerId); - //console.log('found user', user); - - if(user.status && user.status._ === 'userStatusOnline') { - avatarEl.classList.add('is-online'); - } - - break; - default: - break; + if(drawStatus && peerId !== rootScope.myId) { + if(peerId > 0) { + const user = appUsersManager.getUser(peerId); + //console.log('found user', user); + + if(user.status && user.status._ === 'userStatusOnline') { + avatarEl.classList.add('is-online'); + } } } @@ -1629,23 +1712,8 @@ export class AppDialogsManager { good = true; } } */ - const method: 'append' | 'prepend' = append ? 'append' : 'prepend'; - if(container === undefined/* || good */) { - this.scroll[method](li); - - this.doms[dialog.peerId] = dom; - - /* if(container) { - container.append(li); - } */ - - const isMuted = appNotificationsManager.isPeerLocalMuted(dialog.peerId, true); - if(isMuted) { - li.classList.add('is-muted'); - } - - this.setLastMessage(dialog); - } else if(container) { + if(container) { + const method = append ? 'append' : 'prepend'; container[method](li); } diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 18fc82f4..0d03739c 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -1639,7 +1639,7 @@ export class AppMessagesManager { for(; folderId < 2; ++folderId) { let offsetIndex = 0; for(;;) { - const {dialogs} = await appMessagesManager.getConversations(query, offsetIndex, limit, folderId); + const {dialogs} = await appMessagesManager.getConversations(query, offsetIndex, limit, folderId).promise; if(dialogs.length) { outDialogs.push(...dialogs); @@ -2508,9 +2508,9 @@ export class AppMessagesManager { message.totalEntities = RichTextProcessor.mergeEntities(apiEntities, myEntities); // ! only in this order, otherwise bold and emoji formatting won't work } - public wrapMessageForReply(message: any, text: string, usingMids: number[], plain: true, highlightWord?: string): string; - public wrapMessageForReply(message: any, text?: string, usingMids?: number[], plain?: false, highlightWord?: string): DocumentFragment; - public wrapMessageForReply(message: any, text: string = message.message, usingMids?: number[], plain?: boolean, highlightWord?: string): DocumentFragment | string { + public wrapMessageForReply(message: any, text: string, usingMids: number[], plain: true, highlightWord?: string, withoutMediaType?: boolean): string; + public wrapMessageForReply(message: any, text?: string, usingMids?: number[], plain?: false, highlightWord?: string, withoutMediaType?: boolean): DocumentFragment; + public wrapMessageForReply(message: any, text: string = message.message, usingMids?: number[], plain?: boolean, highlightWord?: string, withoutMediaType?: boolean): DocumentFragment | string { const parts: (HTMLElement | string)[] = []; const addPart = (langKey: LangPackKey, part?: string | HTMLElement, text?: string) => { @@ -2557,7 +2557,7 @@ export class AppMessagesManager { usingFullAlbum = false; } - if(!usingFullAlbum) { + if(!usingFullAlbum && !withoutMediaType) { const media = message.media; switch(media._) { case 'messageMediaPhoto': diff --git a/src/lib/appManagers/appUsersManager.ts b/src/lib/appManagers/appUsersManager.ts index 4b3a83ea..990df65f 100644 --- a/src/lib/appManagers/appUsersManager.ts +++ b/src/lib/appManagers/appUsersManager.ts @@ -11,6 +11,7 @@ import { MOUNT_CLASS_TO } from "../../config/debug"; import { filterUnique } from "../../helpers/array"; +import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise"; import cleanSearchText from "../../helpers/cleanSearchText"; import cleanUsername from "../../helpers/cleanUsername"; import { tsNow } from "../../helpers/date"; @@ -42,7 +43,7 @@ export class AppUsersManager { private users: {[userId: number]: User}; private usernames: {[username: string]: number}; private contactsIndex: SearchIndex; - private contactsFillPromise: Promise>; + private contactsFillPromise: CancellablePromise>; private contactsList: Set; private updatedContactsList: boolean; @@ -138,7 +139,8 @@ export class AppUsersManager { }); if(contactsList.length) { - this.contactsFillPromise = Promise.resolve(this.contactsList); + this.contactsFillPromise = deferredPromise(); + this.contactsFillPromise.resolve(this.contactsList); } } @@ -198,13 +200,19 @@ export class AppUsersManager { public fillContacts() { if(this.contactsFillPromise && this.updatedContactsList) { - return this.contactsFillPromise; + return { + cached: this.contactsFillPromise.isFulfilled, + promise: this.contactsFillPromise + }; } this.updatedContactsList = true; - const promise = apiManager.invokeApi('contacts.getContacts').then((result) => { + const promise = deferredPromise>(); + apiManager.invokeApi('contacts.getContacts').then((result) => { if(result._ === 'contacts.contacts') { + this.contactsList.clear(); + this.saveApiUsers(result.users); result.contacts.forEach((contact) => { @@ -212,14 +220,19 @@ export class AppUsersManager { }); this.onContactsModified(); - } - this.contactsFillPromise = promise; + this.contactsFillPromise = promise; + } - return this.contactsList; + promise.resolve(this.contactsList); + }, () => { + this.updatedContactsList = false; }); - return this.contactsFillPromise || (this.contactsFillPromise = promise); + return { + cached: this.contactsFillPromise?.isFulfilled, + promise: this.contactsFillPromise || (this.contactsFillPromise = promise) + }; } public resolveUsername(username: string): Promise { @@ -265,7 +278,7 @@ export class AppUsersManager { } public getContacts(query?: string, includeSaved = false, sortBy: 'name' | 'online' | 'none' = 'name') { - return this.fillContacts().then(_contactsList => { + return this.fillContacts().promise.then(_contactsList => { let contactsList = [..._contactsList]; if(query) { const results = this.contactsIndex.search(query); diff --git a/src/lib/storages/dialogs.ts b/src/lib/storages/dialogs.ts index b0172cba..eb5e9e68 100644 --- a/src/lib/storages/dialogs.ts +++ b/src/lib/storages/dialogs.ts @@ -599,7 +599,26 @@ export default class DialogsStorage { return indexStr; } - public getDialogs(query = '', offsetIndex?: number, limit = 20, folderId = 0, skipMigrated = false) { + public getDialogs(query = '', offsetIndex?: number, limit = 20, folderId = 0, skipMigrated = false): { + cached: boolean; + promise: Promise<{ + dialogs: Dialog[]; + count: number; + isEnd: boolean; + }>; + } { + if(folderId > 1) { + const fillContactsResult = this.appUsersManager.fillContacts(); + if(!fillContactsResult.cached) { + return { + cached: false, + promise: fillContactsResult.promise.then(() => { + return this.getDialogs(query, offsetIndex, limit, folderId, skipMigrated).promise; + }) + }; + } + } + const realFolderId = folderId > 1 ? 0 : folderId; let curDialogStorage = this.getFolder(folderId, skipMigrated); @@ -641,37 +660,43 @@ export default class DialogsStorage { const loadedAll = this.isDialogsLoaded(realFolderId); if(query || loadedAll || curDialogStorage.length >= offset + limit) { - return Promise.resolve({ - dialogs: curDialogStorage.slice(offset, offset + limit), - count: loadedAll ? curDialogStorage.length : null, - isEnd: loadedAll && (offset + limit) >= curDialogStorage.length - }); + return { + cached: true, + promise: Promise.resolve({ + dialogs: curDialogStorage.slice(offset, offset + limit), + count: loadedAll ? curDialogStorage.length : null, + isEnd: loadedAll && (offset + limit) >= curDialogStorage.length + }) + }; } - return this.appMessagesManager.getTopMessages(limit, realFolderId).then(result => { - //const curDialogStorage = this[folderId]; - if(skipMigrated) { - curDialogStorage = this.getFolder(folderId, skipMigrated); - } - - offset = 0; - if(offsetIndex > 0) { - for(let length = curDialogStorage.length; offset < length; ++offset) { - if(offsetIndex > curDialogStorage[offset][indexStr]) { - break; + return { + cached: false, + promise: this.appMessagesManager.getTopMessages(limit, realFolderId).then(result => { + //const curDialogStorage = this[folderId]; + if(skipMigrated) { + curDialogStorage = this.getFolder(folderId, skipMigrated); + } + + offset = 0; + if(offsetIndex > 0) { + for(let length = curDialogStorage.length; offset < length; ++offset) { + if(offsetIndex > curDialogStorage[offset][indexStr]) { + break; + } } } - } - - //this.log.warn(offset, offset + limit, curDialogStorage.dialogs.length, this.dialogs.length); - - return { - dialogs: curDialogStorage.slice(offset, offset + limit), - count: result.count === undefined ? curDialogStorage.length : result.count, - // isEnd: this.isDialogsLoaded(realFolderId) && (offset + limit) >= curDialogStorage.length - isEnd: result.isEnd - }; - }); + + //this.log.warn(offset, offset + limit, curDialogStorage.dialogs.length, this.dialogs.length); + + return { + dialogs: curDialogStorage.slice(offset, offset + limit), + count: result.count === undefined ? curDialogStorage.length : result.count, + // isEnd: this.isDialogsLoaded(realFolderId) && (offset + limit) >= curDialogStorage.length + isEnd: result.isEnd + }; + }) + }; } // only 0 and 1 folders diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index 0c5bcece..8d2ccc65 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -2049,6 +2049,14 @@ $bubble-margin: .25rem; } } + &.sticker, + &.round, + &.emoji-big { + .message { + right: 0; + } + } + .reply-border, .quote:before { background-color: var(--message-out-primary-color); diff --git a/src/scss/partials/_chatlist.scss b/src/scss/partials/_chatlist.scss index 3d5e90cc..d7e7da22 100644 --- a/src/scss/partials/_chatlist.scss +++ b/src/scss/partials/_chatlist.scss @@ -307,6 +307,17 @@ ul.chatlist { display: inline-block; vertical-align: middle; + .tgico-play { + position: absolute; + z-index: 1; + color: #fff; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + line-height: 1; + font-size: .625rem; + } + .media-photo { width: inherit; height: inherit;