diff --git a/src/components/appMediaViewer.ts b/src/components/appMediaViewer.ts index d6f66608..bdc147b2 100644 --- a/src/components/appMediaViewer.ts +++ b/src/components/appMediaViewer.ts @@ -27,6 +27,7 @@ import appSidebarRight, { AppSidebarRight } from "./sidebarRight"; import SwipeHandler from "./swipeHandler"; import { months, ONE_DAY } from "../helpers/date"; import { SearchSuperContext } from "./appSearchSuper."; +import { DEBUG } from "../lib/mtproto/mtproto_config"; // TODO: масштабирование картинок (не SVG) при ресайзе, и правильный возврат на исходную позицию // TODO: картинки "обрезаются" если возвращаются или появляются с места, где есть их перекрытие (топбар, поле ввода) @@ -332,7 +333,9 @@ class AppMediaViewerBase { - this.log('loaded more media by maxId:', maxId, value, older, this.reverse); + /* if(DEBUG) { + this.log('loaded more media by maxId:', maxId, value, older, this.reverse); + } */ if(value.next_rate) { this.searchContext.nextRate = value.next_rate; @@ -1414,7 +1421,9 @@ export class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete', AppMe return; } - this.log('loaded more media by maxId:', /* maxId, */value, older, this.reverse); + // if(DEBUG) { + // this.log('loaded more media by maxId:', /* maxId, */value, older, this.reverse); + // } if(value.photos.length < loadCount) { this.loadedAllMediaDown = true; diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index eda973af..3c719bb0 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -113,6 +113,8 @@ export default class ChatBubbles { public isHeavyAnimationInProgress = false; public scrollingToNewBubble: HTMLElement; + public isFirstLoad = true; + constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appStickersManager: AppStickersManager, private appUsersManager: AppUsersManager, private appInlineBotsManager: AppInlineBotsManager, private appPhotosManager: AppPhotosManager, private appDocsManager: AppDocsManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager) { //this.chat.log.error('Bubbles construction'); @@ -371,10 +373,21 @@ export default class ChatBubbles { } }); + + let middleware: ReturnType; useHeavyAnimationCheck(() => { this.isHeavyAnimationInProgress = true; + this.lazyLoadQueue.lock(); + middleware = this.getMiddleware(); }, () => { this.isHeavyAnimationInProgress = false; + + if(middleware && middleware()) { + this.lazyLoadQueue.unlock(); + this.lazyLoadQueue.refresh(); + } + + middleware = null; }, this.listenerSetter); } @@ -1274,6 +1287,8 @@ export default class ChatBubbles { if(maxBubbleId <= 0) { maxBubbleId = Math.max(...Object.keys(this.bubbles).map(mid => +mid)); } + } else { + this.isFirstLoad = true; } const oldChatInner = this.chatInner; @@ -1288,14 +1303,12 @@ export default class ChatBubbles { // clear if(!cached) { - this.scrollable.container.innerHTML = ''; - //oldChatInner.remove(); - if(!samePeer) { + this.scrollable.container.innerHTML = ''; + //oldChatInner.remove(); this.chat.finishPeerChange(isTarget, isJump, lastMsgId); + this.preloader.attach(this.bubblesContainer); } - - this.preloader.attach(this.bubblesContainer); } //console.timeEnd('appImManager setPeer pre promise'); @@ -1304,13 +1317,13 @@ export default class ChatBubbles { const setPeerPromise = promise.then(() => { ////this.log('setPeer removing preloader'); + this.scrollable.container.innerHTML = ''; + //oldChatInner.remove(); + if(cached) { if(!samePeer) { this.chat.finishPeerChange(isTarget, isJump, lastMsgId); // * костыль } - - this.scrollable.container.innerHTML = ''; - //oldChatInner.remove(); } else { this.preloader.detach(); } @@ -1399,7 +1412,9 @@ export default class ChatBubbles { else dateMessage.container.append(bubble); return; */ - //this.log('renderMessagesQueue'); + /* if(DEBUG && message.mid === 4314759167) { + this.log('renderMessagesQueue', message, bubble, reverse, promises); + } */ this.messagesQueue.push({message, bubble, reverse, promises}); @@ -2488,13 +2503,16 @@ export default class ChatBubbles { let resultPromise: Promise; //const isFirstMessageRender = !!additionMsgID && result instanceof Promise && !appMessagesManager.getMessage(additionMsgID).grouped_id; - const isFirstMessageRender = additionMsgIds?.length; - if(isFirstMessageRender) { + const isAdditionRender = additionMsgIds?.length; + const isFirstMessageRender = (this.isFirstLoad && backLimit) || isAdditionRender; + if(isAdditionRender) { resultPromise = result as Promise; result = {history: additionMsgIds}; //additionMsgID = 0; } + this.isFirstLoad = false; + const processResult = (historyResult: typeof result) => { if(this.chat.type === 'discussion' && 'offsetIdOffset' in historyResult) { const isTopEnd = historyResult.offsetIdOffset >= (historyResult.count - loadCount); @@ -2531,7 +2549,7 @@ export default class ChatBubbles { ////console.timeEnd('render history total'); return getHeavyAnimationPromise().then(() => { - return this.performHistoryResult(result.history || [], reverse, isBackLimit, !isFirstMessageRender && additionMsgId); + return this.performHistoryResult(result.history || [], reverse, isBackLimit, !isAdditionRender && additionMsgId); }); }, (err) => { this.log.error('getHistory error:', err); @@ -2552,44 +2570,84 @@ export default class ChatBubbles { //this.log('getHistory cached result by maxId:', maxId, reverse, isBackLimit, result, peerId, justLoad); processResult(result); promise = getHeavyAnimationPromise().then(() => { - return this.performHistoryResult((result as HistoryResult).history || [], reverse, isBackLimit, !isFirstMessageRender && additionMsgId); + return this.performHistoryResult((result as HistoryResult).history || [], reverse, isBackLimit, !isAdditionRender && additionMsgId); }); //return (reverse ? this.getHistoryTopPromise = promise : this.getHistoryBottomPromise = promise); //return this.performHistoryResult(result.history || [], reverse, isBackLimit, additionMsgID, true); } - const waitPromise = isFirstMessageRender ? processPromise(resultPromise) : promise; + const waitPromise = isAdditionRender ? processPromise(resultPromise) : promise; if(isFirstMessageRender) { waitPromise.then(() => { - if(rootScope.settings.animationsEnabled) { - const mids = getObjectKeysAndSort(this.bubbles, 'desc').filter(mid => !additionMsgIds.includes(mid)); - const animationPromise = deferredPromise(); + if(rootScope.settings.animationsEnabled && Object.keys(this.bubbles).length) { + let sortedMids = getObjectKeysAndSort(this.bubbles, 'desc'); + + if(isAdditionRender && additionMsgIds.length) { + sortedMids = sortedMids.filter(mid => !additionMsgIds.includes(mid)); + } + + let targetMid: number; + if(backLimit) { + targetMid = maxId; + } else { + if(additionMsgId) { + targetMid = additionMsgId; + } else { // * if maxId === 0 + targetMid = Math.max(...sortedMids); + } + } - let lastMsDelay = 0; - mids.forEach((mid, idx) => { - const bubble = this.bubbles[mid]; + const topIds = sortedMids.slice(sortedMids.findIndex(mid => targetMid > mid)); + const middleIds = isAdditionRender ? [] : [targetMid]; + const bottomIds = sortedMids.slice(0, sortedMids.findIndex(mid => targetMid >= mid)).reverse(); + + /* this.log('getHistory: targeting mid:', targetMid, + topIds.map(m => this.appMessagesManager.getLocalMessageId(m)), + bottomIds.map(m => this.appMessagesManager.getLocalMessageId(m))); */ + + const delay = isAdditionRender ? 10 : 40; + const offsetIndex = isAdditionRender ? 0 : 1; + const animateAsLadder = (mids: number[], offsetIndex = 0) => { + const animationPromise = deferredPromise(); + let lastMsDelay = 0; + mids.forEach((mid, idx) => { + const bubble = this.bubbles[mid]; + + lastMsDelay = ((idx + offsetIndex) || 0.1) * delay; + //lastMsDelay = (idx || 0.1) * 1000; + //if(idx || isSafari) { + // ! 0.1 = 1ms задержка для Safari, без этого первое сообщение над самым нижним может появиться позже другого с animation-delay, LOL ! + bubble.style.animationDelay = lastMsDelay + 'ms'; + //} - lastMsDelay = ((idx || 0.1) * 10); - //if(idx || isSafari) { - // ! 0.1 = 1ms задержка для Safari, без этого первое сообщение над самым нижним может появиться позже другого с animation-delay, LOL ! - bubble.style.animationDelay = lastMsDelay + 'ms'; - //} + bubble.classList.add('zoom-fade'); + bubble.addEventListener('animationend', () => { + bubble.style.animationDelay = ''; + bubble.classList.remove('zoom-fade'); - bubble.classList.add('zoom-fade'); - bubble.addEventListener('animationend', () => { - bubble.style.animationDelay = ''; - bubble.classList.remove('zoom-fade'); + if(idx === (mids.length - 1)) { + animationPromise.resolve(); + } + }, {once: true}); + //this.log('supa', bubble); + }); - if(idx === (mids.length - 1)) { - animationPromise.resolve(); - } - }, {once: true}); - //this.log('supa', bubble); - }); + if(!mids.length) { + animationPromise.resolve(); + } + + return {lastMsDelay, animationPromise}; + }; + + const topRes = animateAsLadder(topIds, offsetIndex); + const middleRes = animateAsLadder(middleIds); + const bottomRes = animateAsLadder(bottomIds, offsetIndex); + const promises = [topRes.animationPromise, middleRes.animationPromise, bottomRes.animationPromise]; + const delays: number[] = [topRes.lastMsDelay, middleRes.lastMsDelay, bottomRes.lastMsDelay]; - if(mids.length) { - dispatchHeavyAnimationEvent(animationPromise, lastMsDelay); + if(topIds.length || middleIds.length || bottomIds.length) { + dispatchHeavyAnimationEvent(Promise.all(promises), Math.max(...delays)); } } diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index 3f6ab014..fd0a20dd 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -491,7 +491,7 @@ export default class ChatInput { } public saveDraft() { - if(!this.chat.peerId) return; + if(!this.chat.peerId || this.editMsgId) return; const entities: MessageEntity[] = []; const str = getRichValue(this.messageInputField.input, entities); @@ -537,17 +537,18 @@ export default class ChatInput { } public setDraft(draft?: MyDraftMessage, fromUpdate = true) { - if(!isInputEmpty(this.messageInput)) return; + if(!isInputEmpty(this.messageInput)) return false; if(!draft) { draft = this.appDraftsManager.getDraft(this.chat.peerId, this.chat.threadId); if(!draft) { - return; + return false; } } this.setInputValue(draft.rMessage, fromUpdate, fromUpdate); + return true; } public finishPeerChange() { @@ -954,7 +955,9 @@ export default class ChatInput { } } - this.saveDraftDebounced(); + if(!this.editMsgId) { + this.saveDraftDebounced(); + } this.updateSendBtn(); }; @@ -1105,7 +1108,7 @@ export default class ChatInput { } }; - public clearInput() { + public clearInput(canSetDraft = true) { if(isTouchSupported) { this.messageInput.innerText = ''; } else { @@ -1118,7 +1121,14 @@ export default class ChatInput { this.canUndoFromHTML = ''; } - this.onMessageInput(); + let set = false; + if(canSetDraft) { + set = this.setDraft(undefined, false); + } + + if(!set) { + this.onMessageInput(); + } } public isInputEmpty() { diff --git a/src/components/misc.ts b/src/components/misc.ts index 329d6cb8..f452e278 100644 --- a/src/components/misc.ts +++ b/src/components/misc.ts @@ -13,8 +13,8 @@ const set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoE }; // проблема функции в том, что она не подходит для ссылок, пригодна только для blob'ов, потому что обычным ссылкам нужен 'load' каждый раз. -export function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string, callback?: (err?: Event) => void): boolean { - if((loadedURLs[url]/* && false */) || elem instanceof HTMLVideoElement) { +export function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string, callback?: (err?: Event) => void, useCache = false): boolean { + if(((loadedURLs[url]/* && false */) && useCache) || elem instanceof HTMLVideoElement) { set(elem, url); callback && callback(); return true; diff --git a/src/components/preloader.ts b/src/components/preloader.ts index 1607faa2..3e337389 100644 --- a/src/components/preloader.ts +++ b/src/components/preloader.ts @@ -69,7 +69,7 @@ export default class ProgressivePreloader { promise.notify = null; if(tempId === this.tempId) { - if(successfully) { + if(successfully && this.cancelable) { this.setProgress(100); setTimeout(() => { // * wait for transition complete diff --git a/src/helpers/fastSmoothScroll.ts b/src/helpers/fastSmoothScroll.ts index 48880948..883f1eb1 100644 --- a/src/helpers/fastSmoothScroll.ts +++ b/src/helpers/fastSmoothScroll.ts @@ -83,7 +83,7 @@ export default function fastSmoothScroll( }); }); - return dispatchHeavyAnimationEvent(promise); + return axis === 'y' ? dispatchHeavyAnimationEvent(promise) : promise; } function scrollWithJs( @@ -225,7 +225,7 @@ function scrollWithJs( return t < 1; }; - if(!duration) { + if(!duration || !path) { cancelAnimationByKey(container); tick(); return Promise.resolve(); diff --git a/src/lib/appManagers/appDraftsManager.ts b/src/lib/appManagers/appDraftsManager.ts index 29d5cc5d..ad5813d7 100644 --- a/src/lib/appManagers/appDraftsManager.ts +++ b/src/lib/appManagers/appDraftsManager.ts @@ -156,15 +156,13 @@ export class AppDraftsManager { } else { draftObj = {_: 'draftMessage'} as any as DraftMessage.draftMessage; let message = localDraft.message; - let entities: MessageEntity[] = []; - //message = RichTextProcessor.parseEmojis(message); - //message = RichTextProcessor.parseMarkdown(message, entities, true); + let entities: MessageEntity[] = localDraft.entities; if(localDraft.reply_to_msg_id) { params.reply_to_msg_id = draftObj.reply_to_msg_id = localDraft.reply_to_msg_id; } - if(entities.length) { + if(entities?.length) { params.entities = draftObj.entities = entities; } diff --git a/src/lib/appManagers/appPeersManager.ts b/src/lib/appManagers/appPeersManager.ts index d047009e..ac96d906 100644 --- a/src/lib/appManagers/appPeersManager.ts +++ b/src/lib/appManagers/appPeersManager.ts @@ -18,7 +18,7 @@ import appUsersManager from "./appUsersManager"; #2996ad 11 sea #ce671b 5 orange */ -const DialogColorsFg = ['#c03d33', '#4fad2d', '#d09306', '#168acd', '#8544d6', '#cd4073', '#2996ad', '#ce671b']; +const DialogColorsFg = ['#fc5c51', '#0fb297', '#d09306', '#3d72ed', '#895dd5', '#cd4073', '#00c1a6', '#fa790f']; const DialogColors = ['red', 'green', 'yellow', 'blue', 'violet', 'pink', 'cyan', 'orange']; const DialogColorsMap = [0, 7, 4, 1, 6, 3, 5]; diff --git a/src/scss/partials/_badge.scss b/src/scss/partials/_badge.scss index d191fdf1..4ad6ad37 100644 --- a/src/scss/partials/_badge.scss +++ b/src/scss/partials/_badge.scss @@ -14,10 +14,6 @@ min-width: 1.25rem; line-height: 1.25rem !important; padding: 0 5.75px; - - html.is-safari & { - line-height: 22px !important; - } } &-24 { diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index abc9c64a..2a236993 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -97,7 +97,8 @@ $bubble-margin: .25rem; } */ &.is-highlighted:after { - background-color: rgba(0, 132, 255, .3); + //background-color: rgba(0, 132, 255, .3); + background-color: rgba(77, 142, 80, .4); body:not(.animation-level-0) & { animation: bubbleSelected 2s linear; diff --git a/src/scss/partials/_input.scss b/src/scss/partials/_input.scss index 6abbca49..29727517 100644 --- a/src/scss/partials/_input.scss +++ b/src/scss/partials/_input.scss @@ -140,6 +140,7 @@ padding: 0 6px; opacity: 1; left: .75rem; + font-weight: 500; } } } diff --git a/src/scss/partials/_leftSidebar.scss b/src/scss/partials/_leftSidebar.scss index 6c899546..075460bb 100644 --- a/src/scss/partials/_leftSidebar.scss +++ b/src/scss/partials/_leftSidebar.scss @@ -106,6 +106,10 @@ } } + .folders-tabs-scrollable li:first-child { + margin-left: .6875rem; + } + .item-main { .input-search { /* &-input {