Browse Source

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
master
Eduard Kuzmenko 3 years ago
parent
commit
43cfa9ee12
  1. 5
      src/components/appSearchSuper..ts
  2. 347
      src/components/chat/bubbles.ts
  3. 33
      src/components/chat/chat.ts
  4. 8
      src/components/chat/input.ts
  5. 53
      src/components/chat/messageRender.ts
  6. 2
      src/components/chat/replies.ts
  7. 7
      src/components/misc.ts
  8. 35
      src/components/preloader.ts
  9. 4
      src/components/sidebarLeft/index.ts
  10. 190
      src/components/sidebarLeft/tabs/background.ts
  11. 6
      src/components/sidebarLeft/tabs/generalSettings.ts
  12. 4
      src/components/slider.ts
  13. 2
      src/components/wrappers.ts
  14. 19
      src/helpers/blur.ts
  15. 6
      src/helpers/dom.ts
  16. 2
      src/helpers/heavyQueue.ts
  17. 4
      src/helpers/number.ts
  18. 4
      src/lib/appManagers/appChatsManager.ts
  19. 20
      src/lib/appManagers/appDownloadManager.ts
  20. 30
      src/lib/appManagers/appImManager.ts
  21. 23
      src/lib/appManagers/appMessagesManager.ts
  22. 36
      src/lib/appManagers/appStateManager.ts
  23. 2
      src/lib/rootScope.ts
  24. 14
      src/scss/partials/_avatar.scss
  25. 4
      src/scss/partials/_button.scss
  26. 30
      src/scss/partials/_chat.scss
  27. 24
      src/scss/partials/_chatBubble.scss
  28. 38
      src/scss/partials/_leftSidebar.scss
  29. 38
      src/scss/style.scss

5
src/components/appSearchSuper..ts

@ -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);

347
src/components/chat/bubbles.ts

@ -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));
}
let targetMid: number; this.messagesQueueOnRenderAdditional = undefined;
if(backLimit) { if(!Object.keys(this.bubbles).length) {
targetMid = maxId; return;
} else { }
if(additionMsgId) {
targetMid = additionMsgId; let sortedMids = getObjectKeysAndSort(this.bubbles, 'desc');
} else { // * if maxId === 0
targetMid = Math.max(...sortedMids); 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);
} }
}
const topIds = sortedMids.slice(sortedMids.findIndex(mid => targetMid > mid)); const topIds = sortedMids.slice(sortedMids.findIndex(mid => targetMid > mid));
const middleIds = isAdditionRender ? [] : [targetMid]; const middleIds = isAdditionRender ? [] : [targetMid];
const bottomIds = isAdditionRender ? [] : sortedMids.slice(0, sortedMids.findIndex(mid => targetMid >= mid)).reverse(); const bottomIds = isAdditionRender ? [] : sortedMids.slice(0, sortedMids.findIndex(mid => targetMid >= mid)).reverse();
if(DEBUG) {
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 offsetIndex = isAdditionRender ? 0 : 1;
const animateAsLadder = (mids: number[], offsetIndex = 0) => {
const animationPromise = deferredPromise<void>();
let lastMsDelay = 0;
mids.forEach((mid, idx) => {
if(!this.bubbles[mid]) {
this.log.warn('animateAsLadder: no bubble by mid:', mid);
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); const delay = isAdditionRender ? 10 : 40;
const offsetIndex = isAdditionRender ? 0 : 1;
const animateAsLadder = (mids: number[], offsetIndex = 0) => {
const animationPromise = deferredPromise<void>();
let lastMsDelay = 0;
mids.forEach((mid, idx) => {
if(!this.bubbles[mid]) {
this.log.warn('animateAsLadder: no bubble by mid:', mid);
return;
}
fastRaf(() => { const contentWrapper = this.bubbles[mid].lastElementChild as HTMLElement;
contentWrapper.classList.remove('zoom-fade');
});
});
if(!mids.length) { lastMsDelay = ((idx + offsetIndex) || 0.1) * delay;
animationPromise.resolve(); //lastMsDelay = (idx + offsetIndex) * delay;
//lastMsDelay = (idx || 0.1) * 1000;
contentWrapper.classList.add('zoom-fade');
contentWrapper.style.transitionDelay = lastMsDelay + 'ms';
if(idx === (mids.length - 1)) {
const onTransitionEnd = (e: TransitionEvent) => {
if(e.target !== contentWrapper) {
return;
}
animationPromise.resolve();
contentWrapper.removeEventListener('transitionend', onTransitionEnd);
};
contentWrapper.addEventListener('transitionend', onTransitionEnd);
} }
//this.log('supa', bubble);
return {lastMsDelay, animationPromise}; setBubbles.push(contentWrapper);
}; });
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];
const topRes = animateAsLadder(topIds, offsetIndex); fastRaf(() => {
const middleRes = animateAsLadder(middleIds); setBubbles.forEach(contentWrapper => {
const bottomRes = animateAsLadder(bottomIds, offsetIndex); contentWrapper.classList.remove('zoom-fade');
const promises = [topRes.animationPromise, middleRes.animationPromise, bottomRes.animationPromise]; });
const delays: number[] = [topRes.lastMsDelay, middleRes.lastMsDelay, bottomRes.lastMsDelay]; });
let promise: Promise<any>; let promise: Promise<any>;
if(topIds.length || middleIds.length || bottomIds.length) { if(topIds.length || middleIds.length || bottomIds.length) {
promise = Promise.all(promises); promise = Promise.all(promises);
promise.then(() => { promise.then(() => {
fastRaf(() => { fastRaf(() => {
setBubbles.forEach(contentWrapper => { setBubbles.forEach(contentWrapper => {
contentWrapper.style.transitionDelay = ''; contentWrapper.style.transitionDelay = '';
});
}); });
}); });
dispatchHeavyAnimationEvent(promise, Math.max(...delays) + 200); // * 200 - transition time
}
(promise || Promise.resolve()).then(() => { // ! в хроме, каким-то образом из-за zoom-fade класса начинает прыгать скролл при подгрузке сообщений вверх,
setTimeout(() => { // preload messages // ! т.е. скролл не ставится, так же, как в сафари при translateZ на блок выше scrollable
this.loadMoreHistory(reverse, true); if(!isSafari) {
}, 0); 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);
} //}
} }
//} //}
}); });

33
src/components/chat/chat.ts

@ -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;

8
src/components/chat/input.ts

@ -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;

53
src/components/chat/messageRender.ts

@ -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');
};
} }

2
src/components/chat/replies.ts

@ -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;

7
src/components/misc.ts

@ -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);
} }

35
src/components/preloader.ts

@ -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) {
this.setManual();
} else { } else {
if(this.tryAgainOnFail) { this.detach();
this.setManual();
} else {
this.detach();
}
} }
this.promise = promise = null;
} }
this.promise = promise = null;
}; };
promise promise

4
src/components/sidebarLeft/index.ts

@ -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;

190
src/components/sidebarLeft/tabs/background.ts

@ -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);
}
}

6
src/components/sidebarLeft/tabs/generalSettings.ts

@ -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);

4
src/components/slider.ts

@ -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;

2
src/components/wrappers.ts

@ -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');

19
src/helpers/blur.ts

@ -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 => {

6
src/helpers/dom.ts

@ -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 = '';
}

2
src/helpers/heavyQueue.ts

@ -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 {

4
src/helpers/number.ts

@ -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(".");
} }

4
src/lib/appManagers/appChatsManager.ts

@ -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) {

20
src/lib/appManagers/appDownloadManager.ts

@ -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.cancel = () => {};
/* } catch(err) {
deferred.reject(error); } */
deferred.cancel = () => {};
}; };
deferred.finally(() => { deferred.finally(() => {

30
src/lib/appManagers/appImManager.ts

@ -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;

23
src/lib/appManagers/appMessagesManager.ts

@ -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;
} }

36
src/lib/appManagers/appStateManager.ts

@ -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();

2
src/lib/rootScope.ts

@ -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> {

14
src/scss/partials/_avatar.scss

@ -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 {

4
src/scss/partials/_button.scss

@ -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 {

30
src/scss/partials/_chat.scss

@ -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 & {

24
src/scss/partials/_chatBubble.scss

@ -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) {

38
src/scss/partials/_leftSidebar.scss

@ -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);
}
}
}
}

38
src/scss/style.scss

@ -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;
}
} }
} }

Loading…
Cancel
Save