Chat background image

Fix chat jumping scroll after animation in Chrome
Refactor bubble reply rendering
Fix reply in discussions
Changed subscribers formatting
Fix chat resizing again
This commit is contained in:
Eduard Kuzmenko 2021-01-31 03:52:14 +02:00
parent b2126e385f
commit 43cfa9ee12
29 changed files with 713 additions and 285 deletions

View File

@ -386,8 +386,9 @@ export default class AppSearchSuper {
}); });
} }
wrapped.images.thumb && wrapped.images.thumb.classList.add('grid-item-media'); [wrapped.images.thumb, wrapped.images.full].filter(Boolean).forEach(image => {
wrapped.images.full && wrapped.images.full.classList.add('grid-item-media'); image.classList.add('grid-item-media');
});
promises.push(wrapped.loadPromises.thumb); promises.push(wrapped.loadPromises.thumb);

View File

@ -1,4 +1,4 @@
import { AppImManager, CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager"; import { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager";
import type { AppMessagesManager, HistoryResult, MyMessage } from "../../lib/appManagers/appMessagesManager"; import type { AppMessagesManager, HistoryResult, MyMessage } from "../../lib/appManagers/appMessagesManager";
import type { AppStickersManager } from "../../lib/appManagers/appStickersManager"; import type { AppStickersManager } from "../../lib/appManagers/appStickersManager";
import type { AppUsersManager } from "../../lib/appManagers/appUsersManager"; import type { AppUsersManager } from "../../lib/appManagers/appUsersManager";
@ -7,7 +7,8 @@ import type { AppPhotosManager } from "../../lib/appManagers/appPhotosManager";
import type { AppDocsManager } from "../../lib/appManagers/appDocsManager"; import type { AppDocsManager } from "../../lib/appManagers/appDocsManager";
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager"; import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
import type sessionStorage from '../../lib/sessionStorage'; import type sessionStorage from '../../lib/sessionStorage';
import { findUpClassName, cancelEvent, findUpTag, whichChild, getElementByPoint, attachClickEvent, positionElementByIndex } from "../../helpers/dom"; import type Chat from "./chat";
import { findUpClassName, cancelEvent, findUpTag, whichChild, getElementByPoint, attachClickEvent, positionElementByIndex, reflowScrollableElement } from "../../helpers/dom";
import { getObjectKeysAndSort } from "../../helpers/object"; import { getObjectKeysAndSort } from "../../helpers/object";
import { isTouchSupported } from "../../helpers/touchSupport"; import { isTouchSupported } from "../../helpers/touchSupport";
import { logger } from "../../lib/logger"; import { logger } from "../../lib/logger";
@ -33,7 +34,6 @@ import { wrapAlbum, wrapPhoto, wrapVideo, wrapDocument, wrapSticker, wrapPoll, w
import { MessageRender } from "./messageRender"; import { MessageRender } from "./messageRender";
import LazyLoadQueue from "../lazyLoadQueue"; import LazyLoadQueue from "../lazyLoadQueue";
import { AppChatsManager } from "../../lib/appManagers/appChatsManager"; import { AppChatsManager } from "../../lib/appManagers/appChatsManager";
import Chat from "./chat";
import ListenerSetter from "../../helpers/listenerSetter"; import ListenerSetter from "../../helpers/listenerSetter";
import PollElement from "../poll"; import PollElement from "../poll";
import AudioElement from "../audio"; import AudioElement from "../audio";
@ -87,9 +87,6 @@ export default class ChatBubbles {
private preloader: ProgressivePreloader = null; private preloader: ProgressivePreloader = null;
private scrolledAll: boolean;
public scrolledAllDown: boolean;
private loadedTopTimes = 0; private loadedTopTimes = 0;
private loadedBottomTimes = 0; private loadedBottomTimes = 0;
@ -115,6 +112,7 @@ export default class ChatBubbles {
public scrollingToNewBubble: HTMLElement; public scrollingToNewBubble: HTMLElement;
public isFirstLoad = true; public isFirstLoad = true;
private needReflowScroll: boolean;
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, private storage: typeof sessionStorage) { 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, private storage: typeof sessionStorage) {
//this.chat.log.error('Bubbles construction'); //this.chat.log.error('Bubbles construction');
@ -326,7 +324,7 @@ export default class ChatBubbles {
}); */ }); */
this.needUpdate.forEachReverse((obj, idx) => { this.needUpdate.forEachReverse((obj, idx) => {
if(obj.replyMid === mid, obj.replyToPeerId === peerId) { if(obj.replyMid === mid && obj.replyToPeerId === peerId) {
const {mid, replyMid} = this.needUpdate.splice(idx, 1)[0]; const {mid, replyMid} = this.needUpdate.splice(idx, 1)[0];
//this.log('messages_downloaded', mid, replyMid, i, this.needUpdate, this.needUpdate.length, mids, this.bubbles[mid]); //this.log('messages_downloaded', mid, replyMid, i, this.needUpdate, this.needUpdate.length, mids, this.bubbles[mid]);
@ -340,8 +338,11 @@ export default class ChatBubbles {
delete message.reply_to_mid; // ! WARNING! delete message.reply_to_mid; // ! WARNING!
} }
this.renderMessage(message, true, false, bubble, false); MessageRender.setReply({
//this.renderMessage(message, true, true, bubble, false); chat: this.chat,
bubble,
message
});
} }
}); });
}); });
@ -400,7 +401,7 @@ export default class ChatBubbles {
this.listenerSetter.add(rootScope, 'history_append', (e) => { this.listenerSetter.add(rootScope, 'history_append', (e) => {
let details = e; let details = e;
if(!this.scrolledAllDown) { if(!this.scrollable.loadedAll.bottom) {
this.chat.setMessageId(); this.chat.setMessageId();
} else { } else {
this.renderNewMessagesByIds([details.messageId], true); this.renderNewMessagesByIds([details.messageId], true);
@ -469,7 +470,7 @@ export default class ChatBubbles {
if(readed.length) { if(readed.length) {
let maxId = Math.max(...readed); let maxId = Math.max(...readed);
if(this.scrolledAllDown) { if(this.scrollable.loadedAll.bottom) {
const bubblesMaxId = Math.max(...Object.keys(this.bubbles).map(i => +i)); const bubblesMaxId = Math.max(...Object.keys(this.bubbles).map(i => +i));
if(maxId >= bubblesMaxId) { if(maxId >= bubblesMaxId) {
maxId = this.appMessagesManager.getHistoryStorage(this.peerId, this.chat.threadId).maxId || maxId; maxId = this.appMessagesManager.getHistoryStorage(this.peerId, this.chat.threadId).maxId || maxId;
@ -505,12 +506,13 @@ export default class ChatBubbles {
const onResizeEnd = () => { const onResizeEnd = () => {
const height = this.scrollable.container.offsetHeight; const height = this.scrollable.container.offsetHeight;
if(height !== wasHeight) { // * fix opening keyboard while ESG is active, offsetHeight will change right between 'start' and this first frame const isScrolledDown = this.scrollable.isScrolledDown;
if(height !== wasHeight && (!skip || !isScrolledDown)) { // * fix opening keyboard while ESG is active, offsetHeight will change right between 'start' and this first frame
part += wasHeight - height; part += wasHeight - height;
} }
/* if(DEBUG) { /* if(DEBUG) {
this.log('resize end', scrolled, this.scrollable.scrollTop, height, this.scrollable.isScrolledDown); this.log('resize end', scrolled, part, this.scrollable.scrollTop, height, wasHeight, this.scrollable.isScrolledDown);
} */ } */
if(part) { if(part) {
@ -972,8 +974,8 @@ export default class ChatBubbles {
/* TEST_SCROLL || */ /* TEST_SCROLL || */
this.chat.setPeerPromise || this.chat.setPeerPromise ||
this.isHeavyAnimationInProgress || this.isHeavyAnimationInProgress ||
(top && this.getHistoryTopPromise) || (top && (this.getHistoryTopPromise || this.scrollable.loadedAll.top)) ||
(!top && this.getHistoryBottomPromise) (!top && (this.getHistoryBottomPromise || this.scrollable.loadedAll.bottom))
) { ) {
return; return;
} }
@ -982,28 +984,28 @@ export default class ChatBubbles {
const history = Object.keys(this.bubbles).map(id => +id).sort((a, b) => a - b); const history = Object.keys(this.bubbles).map(id => +id).sort((a, b) => a - b);
if(!history.length) return; if(!history.length) return;
if(top && !this.scrolledAll) { if(top) {
/* if(DEBUG) { if(DEBUG) {
this.log('Will load more (up) history by id:', history[0], 'maxId:', history[history.length - 1], history); this.log('Will load more (up) history by id:', history[0], 'maxId:', history[history.length - 1], justLoad/* , history */);
} */ }
/* if(history.length == 75) { /* if(history.length == 75) {
this.log('load more', this.scrollable.scrollHeight, this.scrollable.scrollTop, this.scrollable); this.log('load more', this.scrollable.scrollHeight, this.scrollable.scrollTop, this.scrollable);
return; return;
} */ } */
/* false && */this.getHistory(history[0], true, undefined, undefined, justLoad); /* false && */this.getHistory(history[0], true, undefined, undefined, justLoad);
} } else {
//let dialog = this.appMessagesManager.getDialogByPeerId(this.peerId)[0];
const historyStorage = this.appMessagesManager.getHistoryStorage(this.peerId, this.chat.threadId);
// if scroll down after search
if(history.indexOf(historyStorage.maxId) !== -1) {
return;
}
if(this.scrolledAllDown) return; if(DEBUG) {
this.log('Will load more (down) history by id:', history[history.length - 1], justLoad/* , history */);
//let dialog = this.appMessagesManager.getDialogByPeerId(this.peerId)[0]; }
const historyStorage = this.appMessagesManager.getHistoryStorage(this.peerId, this.chat.threadId);
// if scroll down after search
if(!top && history.indexOf(historyStorage.maxId) === -1/* && this.chat.type == 'chat' */) {
/* if(DEBUG) {
this.log('Will load more (down) history by maxId:', history[history.length - 1], history);
} */
/* false && */this.getHistory(history[history.length - 1], false, true, undefined, justLoad); /* false && */this.getHistory(history[history.length - 1], false, true, undefined, justLoad);
} }
@ -1029,7 +1031,7 @@ export default class ChatBubbles {
}, 1350); }, 1350);
} }
if(this.scrollable.getDistanceToEnd() < 300 && this.scrolledAllDown) { if(this.scrollable.getDistanceToEnd() < 300 && this.scrollable.loadedAll.bottom) {
this.bubblesContainer.classList.add('scrolled-down'); this.bubblesContainer.classList.add('scrolled-down');
this.scrolledDown = true; this.scrolledDown = true;
} else if(this.bubblesContainer.classList.contains('scrolled-down')) { } else if(this.bubblesContainer.classList.contains('scrolled-down')) {
@ -1044,6 +1046,8 @@ export default class ChatBubbles {
public setScroll() { public setScroll() {
this.scrollable = new Scrollable(this.bubblesContainer/* .firstElementChild */ as HTMLElement, 'IM', /* 10300 */300); this.scrollable = new Scrollable(this.bubblesContainer/* .firstElementChild */ as HTMLElement, 'IM', /* 10300 */300);
this.scrollable.loadedAll.top = false;
this.scrollable.loadedAll.bottom = false;
/* const getScrollOffset = () => { /* const getScrollOffset = () => {
//return Math.round(Math.max(300, appPhotosManager.windowH / 1.5)); //return Math.round(Math.max(300, appPhotosManager.windowH / 1.5));
@ -1141,7 +1145,7 @@ export default class ChatBubbles {
} }
public renderNewMessagesByIds(mids: number[], scrolledDown = this.scrolledDown) { public renderNewMessagesByIds(mids: number[], scrolledDown = this.scrolledDown) {
if(!this.scrolledAllDown) { // seems search active or sliced if(!this.scrollable.loadedAll.bottom) { // seems search active or sliced
//this.log('renderNewMessagesByIds: seems search is active, skipping render:', mids); //this.log('renderNewMessagesByIds: seems search is active, skipping render:', mids);
return; return;
} }
@ -1295,8 +1299,8 @@ export default class ChatBubbles {
public cleanup(bubblesToo = false) { public cleanup(bubblesToo = false) {
////console.time('appImManager cleanup'); ////console.time('appImManager cleanup');
this.scrolledAll = false; this.scrollable.loadedAll.top = false;
this.scrolledAllDown = false; this.scrollable.loadedAll.bottom = false;
if(TEST_SCROLL !== undefined) { if(TEST_SCROLL !== undefined) {
TEST_SCROLL = TEST_SCROLL_TIMES; TEST_SCROLL = TEST_SCROLL_TIMES;
@ -1520,13 +1524,13 @@ export default class ChatBubbles {
// warning // warning
if(!lastMsgId || this.bubbles[topMessage] || lastMsgId == topMessage) { if(!lastMsgId || this.bubbles[topMessage] || lastMsgId == topMessage) {
this.scrolledAllDown = true; this.scrollable.loadedAll.bottom = true;
} }
this.log('scrolledAllDown:', this.scrolledAllDown); this.log('scrolledAllDown:', this.scrollable.loadedAll.bottom);
//if(!this.unreaded.length && dialog) { // lol //if(!this.unreaded.length && dialog) { // lol
if(this.scrolledAllDown && topMessage) { // lol if(this.scrollable.loadedAll.bottom && topMessage) { // lol
this.onScrolledAllDown(); this.onScrolledAllDown();
} }
@ -2375,28 +2379,12 @@ export default class ChatBubbles {
} }
if(message.reply_to_mid && message.reply_to_mid !== this.chat.threadId) { if(message.reply_to_mid && message.reply_to_mid !== this.chat.threadId) {
const replyToPeerId = message.reply_to.reply_to_peer_id ? this.appPeersManager.getPeerId(message.reply_to.reply_to_peer_id) : this.peerId; MessageRender.setReply({
chat: this.chat,
let originalMessage = this.appMessagesManager.getMessageByPeer(replyToPeerId, message.reply_to_mid); bubble,
let originalPeerTitle: string; bubbleContainer,
message
/////////this.log('message to render reply', originalMessage, originalPeerTitle, bubble, message); });
// need to download separately
if(originalMessage._ == 'messageEmpty') {
//////////this.log('message to render reply empty, need download', message, message.reply_to_mid);
this.appMessagesManager.wrapSingleMessage(replyToPeerId, message.reply_to_mid);
this.needUpdate.push({replyToPeerId, replyMid: message.reply_to_mid, mid: message.mid});
originalPeerTitle = 'Loading...';
} else {
originalPeerTitle = this.appPeersManager.getPeerTitle(originalMessage.fromId || originalMessage.fwdFromId, true) || '';
}
const wrapped = wrapReply(originalPeerTitle, originalMessage.message || '', originalMessage);
bubbleContainer.append(wrapped);
//bubbleContainer.insertBefore(, nameContainer);
bubble.classList.add('is-reply');
} }
const needAvatar = this.chat.isAnyGroup() && !isOut; const needAvatar = this.chat.isAnyGroup() && !isOut;
@ -2474,7 +2462,7 @@ export default class ChatBubbles {
// commented bot getProfile in getHistory! // commented bot getProfile in getHistory!
if(!history/* .filter((id: number) => id > 0) */.length) { if(!history/* .filter((id: number) => id > 0) */.length) {
if(!isBackLimit) { if(!isBackLimit) {
this.scrolledAll = true; this.scrollable.loadedAll.top = true;
/* if(this.chat.type === 'discussion') { /* if(this.chat.type === 'discussion') {
const serviceStartMessageId = this.appMessagesManager.threadsServiceMessagesIdsStorage[this.peerId + '_' + this.chat.threadId]; const serviceStartMessageId = this.appMessagesManager.threadsServiceMessagesIdsStorage[this.peerId + '_' + this.chat.threadId];
@ -2482,7 +2470,7 @@ export default class ChatBubbles {
history.push(this.chat.threadId); history.push(this.chat.threadId);
} */ } */
} else { } else {
this.scrolledAllDown = true; this.scrollable.loadedAll.bottom = true;
} }
} }
@ -2503,7 +2491,7 @@ export default class ChatBubbles {
const historyStorage = this.appMessagesManager.getHistoryStorage(this.peerId, this.chat.threadId); const historyStorage = this.appMessagesManager.getHistoryStorage(this.peerId, this.chat.threadId);
if(history.includes(historyStorage.maxId)) { if(history.includes(historyStorage.maxId)) {
this.scrolledAllDown = true; this.scrollable.loadedAll.bottom = true;
} }
//console.time('appImManager render history'); //console.time('appImManager render history');
@ -2511,7 +2499,9 @@ export default class ChatBubbles {
return new Promise<boolean>((resolve, reject) => { return new Promise<boolean>((resolve, reject) => {
//await new Promise((resolve) => setTimeout(resolve, 1e3)); //await new Promise((resolve) => setTimeout(resolve, 1e3));
//this.log('performHistoryResult: will render some messages:', history.length, this.isHeavyAnimationInProgress); /* if(DEBUG) {
this.log('performHistoryResult: will render some messages:', history.length, this.isHeavyAnimationInProgress, this.messagesQueuePromise);
} */
const method = (reverse ? history.shift : history.pop).bind(history); const method = (reverse ? history.shift : history.pop).bind(history);
@ -2534,12 +2524,19 @@ export default class ChatBubbles {
previousScrollHeightMinusTop = scrollTop; previousScrollHeightMinusTop = scrollTop;
} */ } */
//this.log('performHistoryResult: messagesQueueOnRender, scrollTop:', scrollTop, scrollHeight, previousScrollHeightMinusTop); /* if(DEBUG) {
this.log('performHistoryResult: messagesQueueOnRender, scrollTop:', scrollTop, scrollHeight, previousScrollHeightMinusTop);
} */
this.messagesQueueOnRender = undefined; this.messagesQueueOnRender = undefined;
}; };
//} //}
//} //}
if(this.needReflowScroll) {
reflowScrollableElement(this.scrollable.container);
this.needReflowScroll = false;
}
while(history.length) { while(history.length) {
let message = this.chat.getMessage(method()); let message = this.chat.getMessage(method());
this.renderMessage(message, reverse, true); this.renderMessage(message, reverse, true);
@ -2577,13 +2574,11 @@ export default class ChatBubbles {
//isTouchSupported && isApple && (this.scrollable.container.style.overflow = ''); //isTouchSupported && isApple && (this.scrollable.container.style.overflow = '');
if(isSafari/* && !isAppleMobile */) { // * fix blinking and jumping if(isSafari/* && !isAppleMobile */) { // * fix blinking and jumping
this.scrollable.container.style.display = 'none'; reflowScrollableElement(this.scrollable.container);
void this.scrollable.container.offsetLeft; // reflow
this.scrollable.container.style.display = '';
} }
/* if(DEBUG) { /* if(DEBUG) {
this.log('performHistoryResult: have set up scrollTop:', newScrollTop, this.scrollable.scrollTop, this.isHeavyAnimationInProgress); this.log('performHistoryResult: have set up scrollTop:', newScrollTop, this.scrollable.scrollTop, this.scrollable.scrollHeight, this.isHeavyAnimationInProgress);
} */ } */
} }
@ -2629,8 +2624,8 @@ export default class ChatBubbles {
return promise; return promise;
} else if(this.chat.type === 'scheduled') { } else if(this.chat.type === 'scheduled') {
return this.appMessagesManager.getScheduledMessages(this.peerId).then(mids => { return this.appMessagesManager.getScheduledMessages(this.peerId).then(mids => {
this.scrolledAll = true; this.scrollable.loadedAll.top = true;
this.scrolledAllDown = true; this.scrollable.loadedAll.bottom = true;
return {history: mids.slice().reverse()}; return {history: mids.slice().reverse()};
}); });
} }
@ -2650,8 +2645,8 @@ export default class ChatBubbles {
//console.time('appImManager call getHistory'); //console.time('appImManager call getHistory');
const pageCount = this.appPhotosManager.windowH / 38/* * 1.25 */ | 0; const pageCount = this.appPhotosManager.windowH / 38/* * 1.25 */ | 0;
//const loadCount = Object.keys(this.bubbles).length > 0 ? 50 : pageCount; //const loadCount = Object.keys(this.bubbles).length > 0 ? 50 : pageCount;
//const realLoadCount = Object.keys(this.bubbles).length > 0 || additionMsgId ? Math.max(40, pageCount) : pageCount;//const realLoadCount = 50; const realLoadCount = Object.keys(this.bubbles).length > 0 || additionMsgId ? Math.max(40, pageCount) : pageCount;//const realLoadCount = 50;
const realLoadCount = pageCount;//const realLoadCount = 50; //const realLoadCount = pageCount;//const realLoadCount = 50;
let loadCount = realLoadCount; let loadCount = realLoadCount;
/* if(TEST_SCROLL) { /* if(TEST_SCROLL) {
@ -2729,7 +2724,7 @@ export default class ChatBubbles {
const serviceStartMessageId = this.appMessagesManager.threadsServiceMessagesIdsStorage[this.peerId + '_' + this.chat.threadId]; const serviceStartMessageId = this.appMessagesManager.threadsServiceMessagesIdsStorage[this.peerId + '_' + this.chat.threadId];
if(serviceStartMessageId) historyResult.history.push(serviceStartMessageId); if(serviceStartMessageId) historyResult.history.push(serviceStartMessageId);
historyResult.history.push(...this.chat.getMidsByMid(this.chat.threadId).reverse()); historyResult.history.push(...this.chat.getMidsByMid(this.chat.threadId).reverse());
this.scrolledAll = true; this.scrollable.loadedAll.top = true;
} }
} }
}; };
@ -2785,123 +2780,127 @@ export default class ChatBubbles {
const waitPromise = isAdditionRender ? processPromise(resultPromise) : promise; const waitPromise = isAdditionRender ? processPromise(resultPromise) : promise;
if(isFirstMessageRender && rootScope.settings.animationsEnabled/* && false */) { if(isFirstMessageRender && rootScope.settings.animationsEnabled/* && false */) {
let times = isAdditionRender ? 2 : 1;
this.messagesQueueOnRenderAdditional = () => { this.messagesQueueOnRenderAdditional = () => {
if(Object.keys(this.bubbles).length > 1) { this.log('ship went past rocks of magnets');
let sortedMids = getObjectKeysAndSort(this.bubbles, 'desc');
if(isAdditionRender && additionMsgIds.length) { if(--times) return;
sortedMids = sortedMids.filter(mid => !additionMsgIds.includes(mid));
this.messagesQueueOnRenderAdditional = undefined;
if(!Object.keys(this.bubbles).length) {
return;
}
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 targetMid: number; const topIds = sortedMids.slice(sortedMids.findIndex(mid => targetMid > mid));
if(backLimit) { const middleIds = isAdditionRender ? [] : [targetMid];
targetMid = maxId; const bottomIds = isAdditionRender ? [] : sortedMids.slice(0, sortedMids.findIndex(mid => targetMid >= mid)).reverse();
} else {
if(additionMsgId) { if(DEBUG) {
targetMid = additionMsgId;
} else { // * if maxId === 0
targetMid = Math.max(...sortedMids);
}
}
const topIds = sortedMids.slice(sortedMids.findIndex(mid => targetMid > mid));
const middleIds = isAdditionRender ? [] : [targetMid];
const bottomIds = isAdditionRender ? [] : sortedMids.slice(0, sortedMids.findIndex(mid => targetMid >= mid)).reverse();
this.log('getHistory: targeting mid:', targetMid, maxId, additionMsgId, this.log('getHistory: targeting mid:', targetMid, maxId, additionMsgId,
topIds.map(m => this.appMessagesManager.getServerMessageId(m)), topIds.map(m => this.appMessagesManager.getServerMessageId(m)),
bottomIds.map(m => this.appMessagesManager.getServerMessageId(m))); bottomIds.map(m => this.appMessagesManager.getServerMessageId(m)));
}
const setBubbles: HTMLElement[] = []; const setBubbles: HTMLElement[] = [];
const delay = isAdditionRender ? 10 : 40; const delay = isAdditionRender ? 10 : 40;
const offsetIndex = isAdditionRender ? 0 : 1; const offsetIndex = isAdditionRender ? 0 : 1;
const animateAsLadder = (mids: number[], offsetIndex = 0) => { const animateAsLadder = (mids: number[], offsetIndex = 0) => {
const animationPromise = deferredPromise<void>(); const animationPromise = deferredPromise<void>();
let lastMsDelay = 0; let lastMsDelay = 0;
mids.forEach((mid, idx) => { mids.forEach((mid, idx) => {
if(!this.bubbles[mid]) { if(!this.bubbles[mid]) {
this.log.warn('animateAsLadder: no bubble by mid:', mid); this.log.warn('animateAsLadder: no bubble by mid:', mid);
return; return;
}
const contentWrapper = this.bubbles[mid].lastElementChild as HTMLElement;
lastMsDelay = ((idx + offsetIndex) || 0.1) * delay;
//lastMsDelay = (idx + offsetIndex) * delay;
//lastMsDelay = (idx || 0.1) * 1000;
//if(idx || isSafari) {
// ! 0.1 = 1ms задержка для Safari, без этого первое сообщение над самым нижним может появиться позже другого с animation-delay, LOL !
//contentWrapper.style.animationDelay = lastMsDelay + 'ms';
//}
contentWrapper.classList.add('zoom-fade');
contentWrapper.style.transitionDelay = lastMsDelay + 'ms';
if(idx === (mids.length - 1)) {
const onTransitionEnd = (e: TransitionEvent) => {
if(e.target !== contentWrapper) {
return;
}
//contentWrapper.style.animationDelay = '';
//contentWrapper.classList.remove('zoom-fade');
//this.log('onTransitionEnd', e);
animationPromise.resolve();
contentWrapper.removeEventListener('transitionend', onTransitionEnd);
};
contentWrapper.addEventListener('transitionend', onTransitionEnd);
}
//this.log('supa', bubble);
setBubbles.push(contentWrapper);
fastRaf(() => {
contentWrapper.classList.remove('zoom-fade');
});
});
if(!mids.length) {
animationPromise.resolve();
} }
return {lastMsDelay, animationPromise}; const contentWrapper = this.bubbles[mid].lastElementChild as HTMLElement;
};
const topRes = animateAsLadder(topIds, offsetIndex); lastMsDelay = ((idx + offsetIndex) || 0.1) * delay;
const middleRes = animateAsLadder(middleIds); //lastMsDelay = (idx + offsetIndex) * delay;
const bottomRes = animateAsLadder(bottomIds, offsetIndex); //lastMsDelay = (idx || 0.1) * 1000;
const promises = [topRes.animationPromise, middleRes.animationPromise, bottomRes.animationPromise];
const delays: number[] = [topRes.lastMsDelay, middleRes.lastMsDelay, bottomRes.lastMsDelay]; contentWrapper.classList.add('zoom-fade');
contentWrapper.style.transitionDelay = lastMsDelay + 'ms';
let promise: Promise<any>; if(idx === (mids.length - 1)) {
if(topIds.length || middleIds.length || bottomIds.length) { const onTransitionEnd = (e: TransitionEvent) => {
promise = Promise.all(promises); if(e.target !== contentWrapper) {
promise.then(() => { return;
fastRaf(() => { }
setBubbles.forEach(contentWrapper => {
contentWrapper.style.transitionDelay = ''; animationPromise.resolve();
}); contentWrapper.removeEventListener('transitionend', onTransitionEnd);
}); };
});
dispatchHeavyAnimationEvent(promise, Math.max(...delays) + 200); // * 200 - transition time contentWrapper.addEventListener('transitionend', onTransitionEnd);
}
//this.log('supa', bubble);
setBubbles.push(contentWrapper);
});
if(!mids.length) {
animationPromise.resolve();
} }
(promise || Promise.resolve()).then(() => { return {lastMsDelay, animationPromise};
setTimeout(() => { // preload messages };
this.loadMoreHistory(reverse, true);
}, 0); 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];
fastRaf(() => {
setBubbles.forEach(contentWrapper => {
contentWrapper.classList.remove('zoom-fade');
}); });
});
let promise: Promise<any>;
if(topIds.length || middleIds.length || bottomIds.length) {
promise = Promise.all(promises);
promise.then(() => {
fastRaf(() => {
setBubbles.forEach(contentWrapper => {
contentWrapper.style.transitionDelay = '';
});
});
// ! в хроме, каким-то образом из-за zoom-fade класса начинает прыгать скролл при подгрузке сообщений вверх,
// ! т.е. скролл не ставится, так же, как в сафари при translateZ на блок выше scrollable
if(!isSafari) {
this.needReflowScroll = true;
}
});
dispatchHeavyAnimationEvent(promise, Math.max(...delays) + 200); // * 200 - transition time
} }
if(!isAdditionRender) { (promise || Promise.resolve()).then(() => {
this.messagesQueueOnRenderAdditional = undefined; setTimeout(() => { // preload messages
} this.loadMoreHistory(reverse, true);
}, 0);
});
}; };
} else { } else {
this.messagesQueueOnRenderAdditional = undefined; this.messagesQueueOnRenderAdditional = undefined;
@ -2938,7 +2937,7 @@ export default class ChatBubbles {
//ids = ids.slice(-removeCount); //ids = ids.slice(-removeCount);
//ids = ids.slice(removeCount * 2); //ids = ids.slice(removeCount * 2);
ids = ids.slice(safeCount); ids = ids.slice(safeCount);
this.scrolledAllDown = false; this.scrollable.loadedAll.bottom = false;
//this.log('getHistory: slice bottom messages:', ids.length, loadCount); //this.log('getHistory: slice bottom messages:', ids.length, loadCount);
//this.getHistoryBottomPromise = undefined; // !WARNING, это нужно для обратной загрузки истории, если запрос словил флуд //this.getHistoryBottomPromise = undefined; // !WARNING, это нужно для обратной загрузки истории, если запрос словил флуд
@ -2946,7 +2945,7 @@ export default class ChatBubbles {
//ids = ids.slice(0, removeCount); //ids = ids.slice(0, removeCount);
//ids = ids.slice(0, ids.length - (removeCount * 2)); //ids = ids.slice(0, ids.length - (removeCount * 2));
ids = ids.slice(0, ids.length - safeCount); ids = ids.slice(0, ids.length - safeCount);
this.scrolledAll = false; this.scrollable.loadedAll.top = false;
//this.log('getHistory: slice up messages:', ids.length, loadCount); //this.log('getHistory: slice up messages:', ids.length, loadCount);
//this.getHistoryTopPromise = undefined; // !WARNING, это нужно для обратной загрузки истории, если запрос словил флуд //this.getHistoryTopPromise = undefined; // !WARNING, это нужно для обратной загрузки истории, если запрос словил флуд
@ -2962,9 +2961,9 @@ export default class ChatBubbles {
// preload more // preload more
//if(!isFirstMessageRender) { //if(!isFirstMessageRender) {
if(this.chat.type === 'chat'/* || this.chat.type === 'discussion' */) { if(this.chat.type === 'chat'/* || this.chat.type === 'discussion' */) {
const storage = this.appMessagesManager.getHistoryStorage(peerId, this.chat.threadId); /* const storage = this.appMessagesManager.getHistoryStorage(peerId, this.chat.threadId);
const isMaxIdInHistory = storage.history.indexOf(maxId) !== -1; const isMaxIdInHistory = storage.history.indexOf(maxId) !== -1;
if(isMaxIdInHistory) { // * otherwise it is a search or jump if(isMaxIdInHistory || true) { // * otherwise it is a search or jump */
setTimeout(() => { setTimeout(() => {
if(reverse) { if(reverse) {
this.loadMoreHistory(true, true); this.loadMoreHistory(true, true);
@ -2972,7 +2971,7 @@ export default class ChatBubbles {
this.loadMoreHistory(false, true); this.loadMoreHistory(false, true);
} }
}, 0); }, 0);
} //}
} }
//} //}
}); });

View File

@ -24,6 +24,9 @@ import ChatInput from "./input";
import ChatSelection from "./selection"; import ChatSelection from "./selection";
import ChatTopbar from "./topbar"; import ChatTopbar from "./topbar";
import { REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config"; import { REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config";
import { renderImageFromUrl } from "../misc";
import SetTransition from "../singleTransition";
import { fastRaf } from "../../helpers/schedulers";
export type ChatType = 'chat' | 'pinned' | 'replies' | 'discussion' | 'scheduled'; export type ChatType = 'chat' | 'pinned' | 'replies' | 'discussion' | 'scheduled';
@ -68,6 +71,36 @@ export default class Chat extends EventListenerBase<{
this.appImManager.chatsContainer.append(this.container); this.appImManager.chatsContainer.append(this.container);
} }
public setBackground(url: string): Promise<void> {
const item = document.createElement('div');
item.classList.add('chat-background-item');
return new Promise<void>((resolve) => {
const cb = () => {
const prev = this.backgroundEl.children[this.backgroundEl.childElementCount - 1] as HTMLElement;
this.backgroundEl.append(item);
// * одного недостаточно, при обновлении страницы все равно фон появляется неплавно
// ! с requestAnimationFrame лучше, но все равно иногда моргает, так что использую два фаста.
fastRaf(() => {
fastRaf(() => {
SetTransition(item, 'is-visible', true, 200, prev ? () => {
prev.remove();
} : null);
});
});
resolve();
};
if(url) {
renderImageFromUrl(item, url, cb);
} else {
cb();
}
});
}
public setType(type: ChatType) { public setType(type: ChatType) {
this.type = type; this.type = type;

View File

@ -13,13 +13,12 @@ import apiManager from "../../lib/mtproto/mtprotoworker";
//import Recorder from '../opus-recorder/dist/recorder.min'; //import Recorder from '../opus-recorder/dist/recorder.min';
import opusDecodeController from "../../lib/opusDecodeController"; import opusDecodeController from "../../lib/opusDecodeController";
import RichTextProcessor from "../../lib/richtextprocessor"; import RichTextProcessor from "../../lib/richtextprocessor";
import { attachClickEvent, blurActiveElement, cancelEvent, cancelSelection, findUpClassName, getRichValue, getSelectedNodes, isInputEmpty, markdownTags, MarkdownType, placeCaretAtEnd, isSendShortcutPressed, fixSafariStickyInput } from "../../helpers/dom"; import { attachClickEvent, blurActiveElement, cancelEvent, cancelSelection, findUpClassName, getRichValue, isInputEmpty, markdownTags, MarkdownType, placeCaretAtEnd, isSendShortcutPressed } from "../../helpers/dom";
import { ButtonMenuItemOptions } from '../buttonMenu'; import { ButtonMenuItemOptions } from '../buttonMenu';
import emoticonsDropdown from "../emoticonsDropdown"; import emoticonsDropdown from "../emoticonsDropdown";
import PopupCreatePoll from "../popups/createPoll"; import PopupCreatePoll from "../popups/createPoll";
import PopupForward from '../popups/forward'; import PopupForward from '../popups/forward';
import PopupNewMedia from '../popups/newMedia'; import PopupNewMedia from '../popups/newMedia';
import Scrollable from "../scrollable";
import { toast } from "../toast"; import { toast } from "../toast";
import { wrapReply } from "../wrappers"; import { wrapReply } from "../wrappers";
import InputField from '../inputField'; import InputField from '../inputField';
@ -36,7 +35,6 @@ import rootScope from '../../lib/rootScope';
import PopupPinMessage from '../popups/unpinMessage'; import PopupPinMessage from '../popups/unpinMessage';
import { debounce } from '../../helpers/schedulers'; import { debounce } from '../../helpers/schedulers';
import { tsNow } from '../../helpers/date'; import { tsNow } from '../../helpers/date';
import { isSafari } from '../../helpers/userAgent';
const RECORD_MIN_TIME = 500; const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.'; const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@ -756,7 +754,7 @@ export default class ChatInput {
if(this.chat.type === 'chat' || this.chat.type === 'discussion') { if(this.chat.type === 'chat' || this.chat.type === 'discussion') {
this.listenerSetter.add(this.messageInput, 'focusin', () => { this.listenerSetter.add(this.messageInput, 'focusin', () => {
if(this.chat.bubbles.scrolledAllDown) { if(this.chat.bubbles.scrollable.loadedAll.bottom) {
this.appMessagesManager.readAllHistory(this.chat.peerId, this.chat.threadId); this.appMessagesManager.readAllHistory(this.chat.peerId, this.chat.threadId);
} }
}); });
@ -1448,7 +1446,7 @@ export default class ChatInput {
this.willSendWebPage = null; this.willSendWebPage = null;
} }
this.replyToMsgId = this.chat.threadId; this.replyToMsgId = undefined;
this.forwardingMids.length = 0; this.forwardingMids.length = 0;
this.forwardingFromPeerId = 0; this.forwardingFromPeerId = 0;
this.editMsgId = undefined; this.editMsgId = undefined;

View File

@ -1,6 +1,7 @@
import { getFullDate } from "../../helpers/date"; import { getFullDate } from "../../helpers/date";
import { formatNumber } from "../../helpers/number"; import { formatNumber } from "../../helpers/number";
import RichTextProcessor from "../../lib/richtextprocessor"; import RichTextProcessor from "../../lib/richtextprocessor";
import { wrapReply } from "../wrappers";
import Chat from "./chat"; import Chat from "./chat";
import RepliesElement from "./replies"; import RepliesElement from "./replies";
@ -34,7 +35,7 @@ export namespace MessageRender {
} }
} }
if(message.edit_date) { if(message.edit_date && chat.type !== 'scheduled') {
bubble.classList.add('is-edited'); bubble.classList.add('is-edited');
time = '<i class="edited">edited</i> ' + time; time = '<i class="edited">edited</i> ' + time;
} }
@ -74,4 +75,54 @@ export namespace MessageRender {
bubbleContainer.prepend(repliesFooter); bubbleContainer.prepend(repliesFooter);
return isFooter; return isFooter;
}; };
export const setReply = ({chat, bubble, bubbleContainer, message}: {
chat: Chat,
bubble: HTMLElement,
bubbleContainer?: HTMLElement,
message: any
}) => {
const isReplacing = !bubbleContainer;
if(isReplacing) {
bubbleContainer = bubble.querySelector('.bubble-content');
}
const currentReplyDiv = isReplacing ? bubbleContainer.querySelector('.reply') : null;
if(!message.reply_to_mid) {
if(currentReplyDiv) {
currentReplyDiv.remove();
}
bubble.classList.remove('is-reply');
return;
}
const replyToPeerId = message.reply_to.reply_to_peer_id ? chat.appPeersManager.getPeerId(message.reply_to.reply_to_peer_id) : chat.peerId;
let originalMessage = chat.appMessagesManager.getMessageByPeer(replyToPeerId, message.reply_to_mid);
let originalPeerTitle: string;
/////////this.log('message to render reply', originalMessage, originalPeerTitle, bubble, message);
// need to download separately
if(originalMessage._ === 'messageEmpty') {
//////////this.log('message to render reply empty, need download', message, message.reply_to_mid);
chat.appMessagesManager.wrapSingleMessage(replyToPeerId, message.reply_to_mid);
chat.bubbles.needUpdate.push({replyToPeerId, replyMid: message.reply_to_mid, mid: message.mid});
originalPeerTitle = 'Loading...';
} else {
originalPeerTitle = chat.appPeersManager.getPeerTitle(originalMessage.fromId || originalMessage.fwdFromId, true) || '';
}
const wrapped = wrapReply(originalPeerTitle, originalMessage.message || '', originalMessage);
if(currentReplyDiv) {
currentReplyDiv.replaceWith(wrapped);
} else {
bubbleContainer.append(wrapped);
}
//bubbleContainer.insertBefore(, nameContainer);
bubble.classList.add('is-reply');
};
} }

View File

@ -62,7 +62,7 @@ export default class RepliesElement extends HTMLElement {
if(!avatarElem) { if(!avatarElem) {
avatarElem = new AvatarElement(); avatarElem = new AvatarElement();
avatarElem.setAttribute('dialog', '0'); avatarElem.setAttribute('dialog', '0');
avatarElem.classList.add('avatar-32'); avatarElem.classList.add('avatar-30');
if(this.loadPromises) { if(this.loadPromises) {
avatarElem.loadPromises = this.loadPromises; avatarElem.loadPromises = this.loadPromises;

View File

@ -17,7 +17,10 @@ const set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoE
// проблема функции в том, что она не подходит для ссылок, пригодна только для blob'ов, потому что обычным ссылкам нужен 'load' каждый раз. // проблема функции в том, что она не подходит для ссылок, пригодна только для blob'ов, потому что обычным ссылкам нужен 'load' каждый раз.
export function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string, callback?: (err?: Event) => void, useCache = false): boolean { 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) { if(((loadedURLs[url]/* && false */) && useCache) || elem instanceof HTMLVideoElement) {
set(elem, url); if(elem) {
set(elem, url);
}
callback && callback(); callback && callback();
return true; return true;
} else { } else {
@ -27,7 +30,7 @@ export function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGIma
loader.src = url; loader.src = url;
//let perf = performance.now(); //let perf = performance.now();
loader.addEventListener('load', () => { loader.addEventListener('load', () => {
if(!isImage) { if(!isImage && elem) {
set(elem, url); set(elem, url);
} }

View File

@ -139,29 +139,42 @@ export default class ProgressivePreloader {
this.promise = promise; this.promise = promise;
const tempId = --this.tempId; const tempId = --this.tempId;
const startTime = Date.now();
const onEnd = (err: Error) => { const onEnd = (err: Error) => {
promise.notify = null; promise.notify = null;
if(tempId === this.tempId) { if(tempId !== this.tempId) {
if(!err && this.cancelable) { return;
this.setProgress(100); }
const elapsedTime = Date.now() - startTime;
//console.log('[PP]: end', this.detached, performance.now());
if(!err && this.cancelable) {
this.setProgress(100);
const delay = TRANSITION_TIME * 0.75;
if(elapsedTime < delay) {
this.detach();
} else {
setTimeout(() => { // * wait for transition complete setTimeout(() => { // * wait for transition complete
if(tempId === this.tempId) { if(tempId === this.tempId) {
this.detach(); this.detach();
} }
}, TRANSITION_TIME * 0.75); }, delay);
} else { }
if(this.tryAgainOnFail) { } else {
this.setManual(); if(this.tryAgainOnFail) {
} else { this.setManual();
this.detach(); } else {
} this.detach();
} }
this.promise = promise = null;
} }
this.promise = promise = null;
}; };
promise promise

View File

@ -446,7 +446,7 @@ export class SettingSection {
public title: HTMLElement; public title: HTMLElement;
public caption: HTMLElement; public caption: HTMLElement;
constructor(name: string, caption?: string) { constructor(name?: string, caption?: string) {
this.container = document.createElement('div'); this.container = document.createElement('div');
this.container.classList.add('sidebar-left-section'); this.container.classList.add('sidebar-left-section');
@ -473,7 +473,7 @@ export class SettingSection {
} }
} }
export const generateSection = (appendTo: Scrollable, name: string, caption?: string) => { export const generateSection = (appendTo: Scrollable, name?: string, caption?: string) => {
const section = new SettingSection(name, caption); const section = new SettingSection(name, caption);
appendTo.append(section.container); appendTo.append(section.container);
return section.content; return section.content;

View File

@ -0,0 +1,190 @@
import { generateSection } from "..";
import blur from "../../../helpers/blur";
import { deferredPromise } from "../../../helpers/cancellablePromise";
import { attachClickEvent, findUpClassName } from "../../../helpers/dom";
import { AccountWallPapers, WallPaper } from "../../../layer";
import appDocsManager, { MyDocument } from "../../../lib/appManagers/appDocsManager";
import appDownloadManager from "../../../lib/appManagers/appDownloadManager";
import appImManager from "../../../lib/appManagers/appImManager";
import appStateManager from "../../../lib/appManagers/appStateManager";
import apiManager from "../../../lib/mtproto/mtprotoworker";
import rootScope from "../../../lib/rootScope";
import Button from "../../button";
import CheckboxField from "../../checkbox";
import ProgressivePreloader from "../../preloader";
import SidebarSlider, { SliderSuperTab } from "../../slider";
import { wrapPhoto } from "../../wrappers";
export default class AppBackgroundTab extends SliderSuperTab {
constructor(slider: SidebarSlider) {
super(slider, true);
}
init() {
this.container.classList.add('background-container');
this.title.innerText = 'Chat Background';
{
const container = generateSection(this.scrollable);
const uploadButton = Button('btn-primary btn-transparent', {icon: 'cameraadd', text: 'Upload Wallpaper'});
const colorButton = Button('btn-primary btn-transparent', {icon: 'colorize', text: 'Set a Color'});
const blurCheckboxField = CheckboxField('Blur Wallpaper Image', 'blur', false, 'settings.background.blur');
blurCheckboxField.input.addEventListener('change', () => {
const active = grid.querySelector('.active') as HTMLElement;
if(!active) return;
// * wait for animation end
setTimeout(() => {
setBackgroundDocument(active.dataset.slug, appDocsManager.getDoc(active.dataset.docId));
}, 100);
});
container.append(uploadButton, colorButton, blurCheckboxField.label);
}
const grid = document.createElement('div');
grid.classList.add('grid');
const saveToCache = (url: string) => {
fetch(url).then(response => {
appDownloadManager.cacheStorage.save('background-image', response);
});
};
const setBackgroundDocument = (slug: string, doc: MyDocument) => {
rootScope.settings.background.slug = slug;
rootScope.settings.background.type = 'image';
appStateManager.pushToState('settings', rootScope.settings);
const download = appDocsManager.downloadDoc(doc, appImManager.chat.bubbles ? appImManager.chat.bubbles.lazyLoadQueue.queueId : 0);
const deferred = deferredPromise<void>();
deferred.addNotifyListener = download.addNotifyListener;
deferred.cancel = download.cancel;
download.then(() => {
if(rootScope.settings.background.slug !== slug || rootScope.settings.background.type !== 'image') {
return;
}
if(rootScope.settings.background.blur) {
setTimeout(() => {
blur(doc.url, 12, 4)
.then(url => {
if(rootScope.settings.background.slug !== slug || rootScope.settings.background.type !== 'image') {
return;
}
saveToCache(url);
return appImManager.setBackground(url);
})
.then(deferred.resolve);
}, 200);
} else {
saveToCache(doc.url);
appImManager.setBackground(doc.url).then(deferred.resolve);
}
});
return deferred;
};
const setActive = () => {
const active = grid.querySelector('.active');
const target = rootScope.settings.background.type === 'image' ? grid.querySelector(`.grid-item[data-slug="${rootScope.settings.background.slug}"]`) : null;
if(active === target) {
return;
}
if(active) {
active.classList.remove('active');
}
if(target) {
target.classList.add('active');
}
};
rootScope.on('background_change', setActive);
apiManager.invokeApiHashable('account.getWallPapers').then((accountWallpapers) => {
const wallpapers = (accountWallpapers as AccountWallPapers.accountWallPapers).wallpapers as WallPaper.wallPaper[];
wallpapers.forEach((wallpaper) => {
if(wallpaper.pFlags.pattern || (wallpaper.document as MyDocument).mime_type.indexOf('application/') === 0) {
return;
}
wallpaper.document = appDocsManager.saveDoc(wallpaper.document);
const container = document.createElement('div');
container.classList.add('grid-item');
const wrapped = wrapPhoto({
photo: wallpaper.document,
message: null,
container: container,
boxWidth: 0,
boxHeight: 0,
withoutPreloader: true
});
[wrapped.images.thumb, wrapped.images.full].filter(Boolean).forEach(image => {
image.classList.add('grid-item-media');
});
container.dataset.docId = wallpaper.document.id;
container.dataset.slug = wallpaper.slug;
if(rootScope.settings.background.type === 'image' && rootScope.settings.background.slug === wallpaper.slug) {
container.classList.add('active');
}
grid.append(container);
});
let clicked: Set<string> = new Set();
attachClickEvent(grid, (e) => {
const target = findUpClassName(e.target, 'grid-item') as HTMLElement;
if(!target) return;
const {docId, slug} = target.dataset;
if(clicked.has(docId)) return;
clicked.add(docId);
const preloader = new ProgressivePreloader({
cancelable: true,
tryAgainOnFail: false
});
const doc = appDocsManager.getDoc(docId);
const load = () => {
const promise = setBackgroundDocument(slug, doc);
if(!doc.url || rootScope.settings.background.blur) {
preloader.attach(target, true, promise);
}
};
preloader.construct();
attachClickEvent(target, (e) => {
if(preloader.preloader.parentElement) {
preloader.onClick(e);
} else {
load();
}
});
load();
console.log(doc);
});
console.log(accountWallpapers);
});
this.scrollable.append(grid);
}
}

View File

@ -9,6 +9,8 @@ import appStateManager from "../../../lib/appManagers/appStateManager";
import rootScope from "../../../lib/rootScope"; import rootScope from "../../../lib/rootScope";
import { isApple } from "../../../helpers/userAgent"; import { isApple } from "../../../helpers/userAgent";
import Row from "../../row"; import Row from "../../row";
import { attachClickEvent } from "../../../helpers/dom";
import AppBackgroundTab from "./background";
export class RangeSettingSelector { export class RangeSettingSelector {
public container: HTMLDivElement; public container: HTMLDivElement;
@ -72,6 +74,10 @@ export default class AppGeneralSettingsTab extends SliderSuperTab {
const chatBackgroundButton = Button('btn-primary btn-transparent', {icon: 'photo', text: 'Chat Background'}); const chatBackgroundButton = Button('btn-primary btn-transparent', {icon: 'photo', text: 'Chat Background'});
attachClickEvent(chatBackgroundButton, () => {
new AppBackgroundTab(this.slider).open();
});
const animationsCheckboxField = CheckboxField('Enable Animations', 'animations', false, 'settings.animationsEnabled'); const animationsCheckboxField = CheckboxField('Enable Animations', 'animations', false, 'settings.animationsEnabled');
container.append(range.container, chatBackgroundButton, animationsCheckboxField.label); container.append(range.container, chatBackgroundButton, animationsCheckboxField.label);

View File

@ -69,12 +69,12 @@ export class SliderSuperTab implements SliderTab {
} }
/* public onCloseAfterTimeout() { public onCloseAfterTimeout() {
if(this.destroyable) { // ! WARNING, пока что это будет работать только с самой последней внутренней вкладкой ! if(this.destroyable) { // ! WARNING, пока что это будет работать только с самой последней внутренней вкладкой !
delete this.slider.tabs[this.id]; delete this.slider.tabs[this.id];
this.container.remove(); this.container.remove();
} }
} */ }
} }
const TRANSITION_TIME = 250; const TRANSITION_TIME = 250;

View File

@ -438,7 +438,7 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS
const preloader = new ProgressivePreloader(); const preloader = new ProgressivePreloader();
const load = () => { const load = () => {
const download = appDocsManager.saveDocFile(doc, appImManager.chat.bubbles.lazyLoadQueue.queueId); const download = appDocsManager.saveDocFile(doc, appImManager.chat.bubbles ? appImManager.chat.bubbles.lazyLoadQueue.queueId : 0);
download.then(() => { download.then(() => {
downloadDiv.classList.add('downloaded'); downloadDiv.classList.add('downloaded');

View File

@ -1,14 +1,18 @@
import { DEBUG } from '../lib/mtproto/mtproto_config';
import fastBlur from '../vendor/fastBlur'; import fastBlur from '../vendor/fastBlur';
import pushHeavyTask from './heavyQueue'; import pushHeavyTask from './heavyQueue';
const RADIUS = 2; const RADIUS = 2;
const ITERATIONS = 2; const ITERATIONS = 2;
function processBlur(dataUri: string) { function processBlur(dataUri: string, radius: number, iterations: number) {
return new Promise<string>((resolve) => { return new Promise<string>((resolve) => {
const img = new Image(); const img = new Image();
console.log('[blur] start'); const perf = performance.now();
if(DEBUG) {
console.log('[blur] start');
}
img.onload = () => { img.onload = () => {
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
@ -18,12 +22,15 @@ function processBlur(dataUri: string) {
const ctx = canvas.getContext('2d')!; const ctx = canvas.getContext('2d')!;
ctx.drawImage(img, 0, 0); ctx.drawImage(img, 0, 0);
fastBlur(ctx, 0, 0, canvas.width, canvas.height, RADIUS, ITERATIONS); fastBlur(ctx, 0, 0, canvas.width, canvas.height, radius, iterations);
//resolve(canvas.toDataURL()); //resolve(canvas.toDataURL());
canvas.toBlob(blob => { canvas.toBlob(blob => {
resolve(URL.createObjectURL(blob)); resolve(URL.createObjectURL(blob));
console.log('[blur] end');
if(DEBUG) {
console.log(`[blur] end, radius: ${radius}, iterations: ${iterations}, time: ${performance.now() - perf}`);
}
}); });
}; };
@ -31,11 +38,11 @@ function processBlur(dataUri: string) {
}); });
} }
export default function blur(dataUri: string) { export default function blur(dataUri: string, radius: number = RADIUS, iterations: number = ITERATIONS) {
return new Promise<string>((resolve) => { return new Promise<string>((resolve) => {
//return resolve(dataUri); //return resolve(dataUri);
pushHeavyTask({ pushHeavyTask({
items: [dataUri], items: [[dataUri, radius, iterations]],
context: null, context: null,
process: processBlur process: processBlur
}).then(results => { }).then(results => {

View File

@ -756,3 +756,9 @@ export function isSendShortcutPressed(e: KeyboardEvent) {
return false; return false;
} }
export function reflowScrollableElement(element: HTMLElement) {
element.style.display = 'none';
void element.offsetLeft; // reflow
element.style.display = '';
}

View File

@ -50,7 +50,7 @@ function timedChunk<T>(queue: HeavyQueue<T>) {
do { do {
await getHeavyAnimationPromise(); await getHeavyAnimationPromise();
const possiblePromise = queue.process.call(queue.context, todo.shift()); const possiblePromise = queue.process.apply(queue.context, todo.shift());
let realResult: T; let realResult: T;
if(possiblePromise instanceof Promise) { if(possiblePromise instanceof Promise) {
try { try {

View File

@ -1,6 +1,6 @@
export function numberWithCommas(x: number) { export function numberThousandSplitter(x: number, joiner = ',') {
const parts = x.toString().split("."); const parts = x.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, joiner);
return parts.join("."); return parts.join(".");
} }

View File

@ -1,4 +1,4 @@
import { numberWithCommas } from "../../helpers/number"; import { numberThousandSplitter } from "../../helpers/number";
import { isObject, safeReplaceObject, copy } from "../../helpers/object"; import { isObject, safeReplaceObject, copy } from "../../helpers/object";
import { ChatAdminRights, ChatBannedRights, ChatFull, ChatParticipants, InputChannel, InputChatPhoto, InputFile, InputPeer, SendMessageAction, Updates } from "../../layer"; import { ChatAdminRights, ChatBannedRights, ChatFull, ChatParticipants, InputChannel, InputChatPhoto, InputFile, InputPeer, SendMessageAction, Updates } from "../../layer";
import apiManager from '../mtproto/mtprotoworker'; import apiManager from '../mtproto/mtprotoworker';
@ -398,7 +398,7 @@ export class AppChatsManager {
} }
const isChannel = this.isBroadcast(id); const isChannel = this.isBroadcast(id);
return numberWithCommas(count || 1) + ' ' + (isChannel ? 'followers' : 'members'); return numberThousandSplitter(count || 1, ' ') + ' ' + (isChannel ? 'subscribers' : 'members');
} }
public wrapForFull(id: number, fullChat: any) { public wrapForFull(id: number, fullChat: any) {

View File

@ -23,7 +23,7 @@ export type Progress = {done: number, fileName: string, total: number, offset: n
export type ProgressCallback = (details: Progress) => void; export type ProgressCallback = (details: Progress) => void;
export class AppDownloadManager { export class AppDownloadManager {
private cacheStorage = new CacheStorageController('cachedFiles'); public cacheStorage = new CacheStorageController('cachedFiles');
private downloads: {[fileName: string]: Download} = {}; private downloads: {[fileName: string]: Download} = {};
private progress: {[fileName: string]: Progress} = {}; private progress: {[fileName: string]: Progress} = {};
private progressCallbacks: {[fileName: string]: Array<ProgressCallback>} = {}; private progressCallbacks: {[fileName: string]: Array<ProgressCallback>} = {};
@ -51,14 +51,18 @@ export class AppDownloadManager {
const deferred = deferredPromise<Blob>(); const deferred = deferredPromise<Blob>();
deferred.cancel = () => { deferred.cancel = () => {
const error = new Error('Download canceled'); //try {
error.name = 'AbortError'; const error = new Error('Download canceled');
error.name = 'AbortError';
apiManager.cancelDownload(fileName);
this.clearDownload(fileName); apiManager.cancelDownload(fileName);
this.clearDownload(fileName);
deferred.reject(error); deferred.reject(error);
deferred.cancel = () => {}; deferred.cancel = () => {};
/* } catch(err) {
} */
}; };
deferred.finally(() => { deferred.finally(() => {

View File

@ -21,7 +21,7 @@ import appStickersManager from './appStickersManager';
import appWebPagesManager from './appWebPagesManager'; import appWebPagesManager from './appWebPagesManager';
import { cancelEvent, getFilesFromEvent, placeCaretAtEnd } from '../../helpers/dom'; import { cancelEvent, getFilesFromEvent, placeCaretAtEnd } from '../../helpers/dom';
import PopupNewMedia from '../../components/popups/newMedia'; import PopupNewMedia from '../../components/popups/newMedia';
import { numberWithCommas } from '../../helpers/number'; import { numberThousandSplitter } from '../../helpers/number';
import MarkupTooltip from '../../components/chat/markupTooltip'; import MarkupTooltip from '../../components/chat/markupTooltip';
import { isTouchSupported } from '../../helpers/touchSupport'; import { isTouchSupported } from '../../helpers/touchSupport';
import appPollsManager from './appPollsManager'; import appPollsManager from './appPollsManager';
@ -33,6 +33,9 @@ import useHeavyAnimationCheck, { dispatchHeavyAnimationEvent } from '../../hooks
import appDraftsManager from './appDraftsManager'; import appDraftsManager from './appDraftsManager';
import serverTimeManager from '../mtproto/serverTimeManager'; import serverTimeManager from '../mtproto/serverTimeManager';
import sessionStorage from '../sessionStorage'; import sessionStorage from '../sessionStorage';
import { renderImageFromUrl } from '../../components/misc';
import appDownloadManager from './appDownloadManager';
import appStateManager, { AppStateManager } from './appStateManager';
//console.log('appImManager included33!'); //console.log('appImManager included33!');
@ -154,6 +157,16 @@ export class AppImManager {
animationIntersector.checkAnimations(false); animationIntersector.checkAnimations(false);
}); });
const isDefaultBackground = rootScope.settings.background.blur === AppStateManager.STATE_INIT.settings.background.blur &&
rootScope.settings.background.slug === AppStateManager.STATE_INIT.settings.background.slug;
if(!isDefaultBackground) {
appDownloadManager.cacheStorage.getFile('background-image').then(blob => {
this.setBackground(URL.createObjectURL(blob), false);
});
} else {
this.setBackground('');
}
/* rootScope.on('peer_changing', (chat) => { /* rootScope.on('peer_changing', (chat) => {
this.saveChatPosition(chat); this.saveChatPosition(chat);
}); });
@ -163,6 +176,15 @@ export class AppImManager {
}); */ }); */
} }
public setBackground(url: string, broadcastEvent = true): Promise<void> {
const promises = this.chats.map(chat => chat.setBackground(url));
return promises[promises.length - 1].then(() => {
if(broadcastEvent) {
rootScope.broadcast('background_change');
}
});
}
/* public saveChatPosition(chat: Chat) { /* public saveChatPosition(chat: Chat) {
const bubble = chat.bubbles.getBubbleByPoint('top'); const bubble = chat.bubbles.getBubbleByPoint('top');
if(bubble) { if(bubble) {
@ -503,6 +525,10 @@ export class AppImManager {
private createNewChat() { private createNewChat() {
const chat = new Chat(this, appChatsManager, appDocsManager, appInlineBotsManager, appMessagesManager, appPeersManager, appPhotosManager, appProfileManager, appStickersManager, appUsersManager, appWebPagesManager, appPollsManager, apiManager, appDraftsManager, serverTimeManager, sessionStorage); const chat = new Chat(this, appChatsManager, appDocsManager, appInlineBotsManager, appMessagesManager, appPeersManager, appPhotosManager, appProfileManager, appStickersManager, appUsersManager, appWebPagesManager, appPollsManager, apiManager, appDraftsManager, serverTimeManager, sessionStorage);
if(this.chats.length) {
chat.backgroundEl.append(this.chat.backgroundEl.lastElementChild.cloneNode(true));
}
this.chats.push(chat); this.chats.push(chat);
} }
@ -661,7 +687,7 @@ export class AppImManager {
if(participants_count < 2) return subtitle; if(participants_count < 2) return subtitle;
const onlines = await appChatsManager.getOnlines(chat.id); const onlines = await appChatsManager.getOnlines(chat.id);
if(onlines > 1) { if(onlines > 1) {
subtitle += ', ' + numberWithCommas(onlines) + ' online'; subtitle += ', ' + numberThousandSplitter(onlines, ' ') + ' online';
} }
return subtitle; return subtitle;

View File

@ -264,7 +264,7 @@ export class AppMessagesManager {
const folder = this.dialogsStorage.getFolder(+folderId); const folder = this.dialogsStorage.getFolder(+folderId);
for(let dialog of folder) { for(let dialog of folder) {
items.push(dialog); items.push([dialog]);
} }
} }
@ -421,6 +421,10 @@ export class AppMessagesManager {
//this.checkSendOptions(options); //this.checkSendOptions(options);
if(options.threadId && !options.replyToMsgId) {
options.replyToMsgId = options.threadId;
}
const MAX_LENGTH = 4096; const MAX_LENGTH = 4096;
if(text.length > MAX_LENGTH) { if(text.length > MAX_LENGTH) {
const splitted = splitStringByLength(text, MAX_LENGTH); const splitted = splitStringByLength(text, MAX_LENGTH);
@ -603,6 +607,11 @@ export class AppMessagesManager {
waveform: Uint8Array waveform: Uint8Array
}> = {}) { }> = {}) {
peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId; peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId;
if(options.threadId && !options.replyToMsgId) {
options.replyToMsgId = options.threadId;
}
//this.checkSendOptions(options); //this.checkSendOptions(options);
const messageId = this.generateTempMessageId(peerId); const messageId = this.generateTempMessageId(peerId);
const randomIdS = randomLong(); const randomIdS = randomLong();
@ -999,6 +1008,10 @@ export class AppMessagesManager {
}> = {}) { }> = {}) {
//this.checkSendOptions(options); //this.checkSendOptions(options);
if(options.threadId && !options.replyToMsgId) {
options.replyToMsgId = options.threadId;
}
if(files.length === 1) { if(files.length === 1) {
return this.sendFile(peerId, files[0], {...options, ...options.sendFileDetails[0]}); return this.sendFile(peerId, files[0], {...options, ...options.sendFileDetails[0]});
} }
@ -1152,6 +1165,10 @@ export class AppMessagesManager {
}> = {}) { }> = {}) {
peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId; peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId;
if(options.threadId && !options.replyToMsgId) {
options.replyToMsgId = options.threadId;
}
//this.checkSendOptions(options); //this.checkSendOptions(options);
const messageId = this.generateTempMessageId(peerId); const messageId = this.generateTempMessageId(peerId);
const randomIdS = randomLong(); const randomIdS = randomLong();
@ -1392,10 +1409,10 @@ export class AppMessagesManager {
private generateReplyHeader(replyToMsgId: number, replyToTopId?: number) { private generateReplyHeader(replyToMsgId: number, replyToTopId?: number) {
const header = { const header = {
_: 'messageReplyHeader', _: 'messageReplyHeader',
reply_to_msg_id: replyToMsgId, reply_to_msg_id: replyToMsgId || replyToTopId,
} as MessageReplyHeader; } as MessageReplyHeader;
if(replyToTopId && replyToTopId !== replyToMsgId) { if(replyToTopId && header.reply_to_msg_id !== replyToTopId) {
header.reply_to_top_id = replyToTopId; header.reply_to_top_id = replyToTopId;
} }

View File

@ -1,5 +1,5 @@
import type { Dialog } from './appMessagesManager'; import type { Dialog } from './appMessagesManager';
import { App, MOUNT_CLASS_TO, UserAuth } from '../mtproto/mtproto_config'; import { App, DEBUG, MOUNT_CLASS_TO, UserAuth } from '../mtproto/mtproto_config';
import EventListenerBase from '../../helpers/eventListenerBase'; import EventListenerBase from '../../helpers/eventListenerBase';
import rootScope from '../rootScope'; import rootScope from '../rootScope';
import sessionStorage from '../sessionStorage'; import sessionStorage from '../sessionStorage';
@ -54,12 +54,18 @@ export type State = Partial<{
stickers: { stickers: {
suggest: boolean, suggest: boolean,
loop: boolean loop: boolean
},
background: {
type: 'color' | 'image' | 'default',
blur: boolean,
color?: string,
slug?: string,
} }
}, },
drafts: AppDraftsManager['drafts'] drafts: AppDraftsManager['drafts']
}>; }>;
const STATE_INIT: State = { export const STATE_INIT: State = {
dialogs: [], dialogs: [],
allDialogsLoaded: {}, allDialogsLoaded: {},
chats: {}, chats: {},
@ -95,6 +101,11 @@ const STATE_INIT: State = {
stickers: { stickers: {
suggest: true, suggest: true,
loop: true loop: true
},
background: {
type: 'image',
blur: false,
slug: 'ByxGo2lrMFAIAAAAmkJxZabh8eM', // * new blurred camomile
} }
}, },
drafts: {} drafts: {}
@ -108,6 +119,7 @@ const REFRESH_KEYS = ['dialogs', 'allDialogsLoaded', 'messages', 'contactsList',
export class AppStateManager extends EventListenerBase<{ export class AppStateManager extends EventListenerBase<{
save: (state: State) => Promise<void> save: (state: State) => Promise<void>
}> { }> {
public static STATE_INIT = STATE_INIT;
public loaded: Promise<State>; public loaded: Promise<State>;
private log = logger('STATE'/* , LogLevels.error */); private log = logger('STATE'/* , LogLevels.error */);
@ -144,11 +156,25 @@ export class AppStateManager extends EventListenerBase<{
if(state.version !== STATE_VERSION) { if(state.version !== STATE_VERSION) {
state = copy(STATE_INIT); state = copy(STATE_INIT);
} else if((state.stateCreatedTime + REFRESH_EVERY) < time/* && false */) { } else if((state.stateCreatedTime + REFRESH_EVERY) < time/* && false */) {
this.log('will refresh state', state.stateCreatedTime, time); if(DEBUG) {
this.log('will refresh state', state.stateCreatedTime, time);
}
REFRESH_KEYS.forEach(key => { REFRESH_KEYS.forEach(key => {
// @ts-ignore // @ts-ignore
state[key] = copy(STATE_INIT[key]); state[key] = copy(STATE_INIT[key]);
}); });
const users: typeof state['users'] = {}, chats: typeof state['chats'] = {};
if(state.recentSearch?.length) {
state.recentSearch.forEach(peerId => {
if(peerId < 0) chats[peerId] = state.chats[peerId];
else users[peerId] = state.users[peerId];
});
}
state.users = users;
state.chats = chats;
} }
} }
@ -160,7 +186,9 @@ export class AppStateManager extends EventListenerBase<{
// ! probably there is better place for it // ! probably there is better place for it
rootScope.settings = this.state.settings; rootScope.settings = this.state.settings;
this.log('state res', state); if(DEBUG) {
this.log('state res', state);
}
//return resolve(); //return resolve();

View File

@ -88,6 +88,8 @@ type BroadcastEvents = {
'im_tab_change': number, 'im_tab_change': number,
'overlay_toggle': boolean, 'overlay_toggle': boolean,
'background_change': void,
}; };
class RootScope extends EventListenerBase<any> { class RootScope extends EventListenerBase<any> {

View File

@ -67,13 +67,21 @@ avatar-element {
} }
img { img {
width: 100%; //width: 100% !important;
height: 100%; //height: 100% !important;
border-radius: inherit; width: var(--size) !important;
height: var(--size) !important;
border-radius: inherit !important;
&.fade-in { &.fade-in {
animation: fade-in-opacity .2s ease forwards; animation: fade-in-opacity .2s ease forwards;
} }
&.emoji {
width: calc(1.125rem / var(--multiplier));
height: calc(1.125rem / var(--multiplier));
vertical-align: middle !important;
}
} }
path { path {

View File

@ -248,7 +248,7 @@
} }
} }
// ! example: multiselect input, button in pinned messages chat // ! example: multiselect input, button in pinned messages chat, settings, chat background tab
.btn-transparent { .btn-transparent {
color: #000; color: #000;
background-color: transparent; background-color: transparent;
@ -256,7 +256,7 @@
align-items: center; align-items: center;
padding: 0 .875rem; padding: 0 .875rem;
//width: auto; //width: auto;
text-transform: capitalize; //text-transform: capitalize;
font-weight: normal; font-weight: normal;
html.no-touch &:hover { html.no-touch &:hover {

View File

@ -116,7 +116,7 @@ $chat-helper-size: 39px;
background: none; background: none;
border: none; border: none;
width: 100%; width: 100%;
padding: 0 .5625rem; padding: .5rem .5625rem;
/* height: 100%; */ /* height: 100%; */
margin-top: -1px; margin-top: -1px;
max-height: calc(30rem - 2.5rem); // 2.5rem - input helper (reply) max-height: calc(30rem - 2.5rem); // 2.5rem - input helper (reply)
@ -563,12 +563,13 @@ $chat-helper-size: 39px;
&-background { &-background {
overflow: hidden; overflow: hidden;
background-color: #e6ebee;
&.no-transition:before { &.no-transition:before {
transition: none !important; transition: none !important;
} }
&, &:before { &, &-item {
position: absolute !important; position: absolute !important;
top: 0; top: 0;
left: 0; left: 0;
@ -576,24 +577,32 @@ $chat-helper-size: 39px;
right: 0; right: 0;
} }
&:before { &-item {
content: "";
display: block;
background-image: url('assets/img/bg.jpeg'); background-image: url('assets/img/bg.jpeg');
background-size: cover; background-size: cover;
background-position: center center; background-position: center center;
background-color: inherit;
body.animation-level-2 & {
transition: opacity var(--layer-transition);
opacity: 0;
&.is-visible:not(.backwards) {
opacity: 1;
}
}
@include respond-to(medium-screens) { @include respond-to(medium-screens) {
body.animation-level-2 & { body.animation-level-2 & {
// !WARNING, МАГИЧЕСКОЕ ЧИСЛО // !WARNING, МАГИЧЕСКОЕ ЧИСЛО
margin: -16rem -5rem -20rem 0; margin: -16.5rem 0 -20rem 0;
transform: scale(1); transform: scale(1);
transform-origin: left center; transform-origin: left center;
transition: transform var(--layer-transition); transition: transform var(--layer-transition), opacity var(--layer-transition);
} }
body.animation-level-2.is-right-column-shown & { body.animation-level-2.is-right-column-shown & {
transform: scale(.67); transform: scale(.666666667);
} }
} }
} }
@ -1036,6 +1045,8 @@ $chat-helper-size: 39px;
cursor: pointer; cursor: pointer;
//--translateY: 0; //--translateY: 0;
opacity: 1; opacity: 1;
transition: opacity var(--layer-transition), visibility 0s 0s !important;
visibility: visible;
/* &.is-broadcast { /* &.is-broadcast {
--translateY: 79px !important; --translateY: 79px !important;
@ -1151,12 +1162,13 @@ $chat-helper-size: 39px;
bottom: calc(var(--chat-input-size) + var(--bottom) + 10px); bottom: calc(var(--chat-input-size) + var(--bottom) + 10px);
cursor: default; cursor: default;
opacity: 0; opacity: 0;
visibility: hidden;
z-index: 2; z-index: 2;
//transition: transform var(--layer-transition), opacity var(--layer-transition) !important; //transition: transform var(--layer-transition), opacity var(--layer-transition) !important;
overflow: visible; overflow: visible;
//--translateY: calc(var(--chat-input-size) + 10px); //--translateY: calc(var(--chat-input-size) + 10px);
//--translateY: calc(100% + 10px); //--translateY: calc(100% + 10px);
transition: opacity var(--layer-transition) !important; transition: opacity var(--layer-transition), visibility 0s .2s !important;
transform: none !important; transform: none !important;
body.animation-level-0 & { body.animation-level-0 & {

View File

@ -14,24 +14,6 @@ $bubble-margin: .25rem;
} }
} }
/*
* zoom-fade-opacity
*/
@keyframes zoom-opacity-fade-in {
0% {
//transform: scale(.8) translateZ(0);
transform: scale3d(.8, .8, 1);
//transform: scale(.8);
opacity: 0;
}
100% {
//transform: scale(1) translateZ(0);
transform: scale3d(1, 1, 1);
//transform: scale(1);
opacity: 1;
}
}
.bubbles-date-group { .bubbles-date-group {
position: relative; position: relative;
@ -1522,11 +1504,9 @@ $bubble-margin: .25rem;
&.zoom-fade /* .bubble-content */ { &.zoom-fade /* .bubble-content */ {
//transform: scale(.8) translateZ(0); //transform: scale(.8) translateZ(0);
transform: scale3d(.8, .8, 1); transform: scale3d(.8, .8, 1) translateX(0);
//transform: scale(.8); //transform: scale(.8) translateX(0);
opacity: 0; opacity: 0;
//animation: zoom-opacity-fade-in .2s ease-in-out forwards;
//animation-delay: 0s;
} }
@include respond-to(not-handhelds) { @include respond-to(not-handhelds) {

View File

@ -931,4 +931,40 @@
--thumb-size: 12px; --thumb-size: 12px;
} }
} }
} }
.background-container {
.grid {
padding: 0 .5rem;
&-item {
&:after {
content: " ";
display: block;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border: 3px solid $color-blue;
opacity: 0;
transition: opacity .2s ease-in-out;
}
&.active {
&:after {
opacity: 1;
}
.grid-item-media {
transform: scale(.91);
}
}
&-media {
transition: transform .2s ease-in-out;
transform: scale(1);
}
}
}
}

View File

@ -843,21 +843,29 @@ img.emoji {
} }
} }
.grid-item { .grid {
height: 0; width: 100%;
padding-bottom: 100%; display: grid;
//overflow: hidden; grid-template-columns: repeat(3, 1fr);
position: relative; grid-auto-rows: 1fr;
cursor: pointer; grid-gap: .25rem;
user-select: none;
&-item {
&-media { height: 0;
position: absolute; padding-bottom: 100%;
left: 0; //overflow: hidden;
top: 0; position: relative;
width: 100%; cursor: pointer;
height: 100%; user-select: none;
object-fit: cover;
&-media {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
} }
} }