Fix smoothness of sending animation
Fix comments button for round video
This commit is contained in:
parent
f60d0f82ce
commit
fec0fdf72f
@ -32,7 +32,7 @@ export class AnimationIntersector {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const player = this.byGroups[group].find(p => p.el == target);
|
const player = this.byGroups[group].find(p => p.el === target);
|
||||||
if(player) {
|
if(player) {
|
||||||
if(entry.isIntersecting) {
|
if(entry.isIntersecting) {
|
||||||
this.visible.add(player);
|
this.visible.add(player);
|
||||||
@ -58,7 +58,7 @@ export class AnimationIntersector {
|
|||||||
const found: AnimationItem[] = [];
|
const found: AnimationItem[] = [];
|
||||||
for(const group in this.byGroups) {
|
for(const group in this.byGroups) {
|
||||||
for(const player of this.byGroups[group]) {
|
for(const player of this.byGroups[group]) {
|
||||||
if(player.el == element) {
|
if(player.el === element) {
|
||||||
found.push(player);
|
found.push(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,7 +80,7 @@ export class AnimationIntersector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for(const group in this.byGroups) {
|
for(const group in this.byGroups) {
|
||||||
this.byGroups[group].findAndSplice(p => p == player);
|
this.byGroups[group].findAndSplice(p => p === player);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.observer.unobserve(el);
|
this.observer.unobserve(el);
|
||||||
@ -132,13 +132,17 @@ export class AnimationIntersector {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(blurred) {
|
if(blurred || (this.onlyOnePlayableGroup && this.onlyOnePlayableGroup !== group)) {
|
||||||
if(!animation.paused) {
|
if(!animation.paused) {
|
||||||
//console.warn('pause animation:', animation);
|
//console.warn('pause animation:', animation);
|
||||||
animation.pause();
|
animation.pause();
|
||||||
}
|
}
|
||||||
} else if(animation.paused && this.visible.has(player) && animation.autoplay && (!this.onlyOnePlayableGroup || this.onlyOnePlayableGroup == group)) {
|
} else if(animation.paused &&
|
||||||
//console.warn('play animation:', animation);
|
this.visible.has(player) &&
|
||||||
|
animation.autoplay &&
|
||||||
|
(!this.onlyOnePlayableGroup || this.onlyOnePlayableGroup === group)
|
||||||
|
) {
|
||||||
|
console.warn('play animation:', animation);
|
||||||
animation.play();
|
animation.play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import appUsersManager from "../lib/appManagers/appUsersManager";
|
|||||||
import rootScope from "../lib/rootScope";
|
import rootScope from "../lib/rootScope";
|
||||||
import { cancelEvent, findUpAttribute, findUpClassName } from "../helpers/dom";
|
import { cancelEvent, findUpAttribute, findUpClassName } from "../helpers/dom";
|
||||||
import Scrollable from "./scrollable";
|
import Scrollable from "./scrollable";
|
||||||
|
import { FocusDirection } from "../helpers/fastSmoothScroll";
|
||||||
|
|
||||||
type PeerType = 'contacts' | 'dialogs';
|
type PeerType = 'contacts' | 'dialogs';
|
||||||
|
|
||||||
@ -38,11 +39,31 @@ export default class AppSelectPeers {
|
|||||||
private loadedWhat: Partial<{[k in 'dialogs' | 'archived' | 'contacts']: true}> = {};
|
private loadedWhat: Partial<{[k in 'dialogs' | 'archived' | 'contacts']: true}> = {};
|
||||||
|
|
||||||
private renderedPeerIds: Set<number> = new Set();
|
private renderedPeerIds: Set<number> = new Set();
|
||||||
|
|
||||||
|
private appendTo: HTMLElement;
|
||||||
|
private onChange: (length: number) => void;
|
||||||
|
private peerType: PeerType[] = ['dialogs'];
|
||||||
|
private renderResultsFunc?: (peerIds: number[]) => void;
|
||||||
|
private chatRightsAction?: ChatRights;
|
||||||
|
private multiSelect = true;
|
||||||
|
|
||||||
constructor(private appendTo: HTMLElement, private onChange?: (length: number) => void, private peerType: PeerType[] = ['dialogs'], onFirstRender?: () => void, private renderResultsFunc?: (peerIds: number[]) => void, private chatRightsAction?: ChatRights, private multiSelect = true) {
|
constructor(options: {
|
||||||
|
appendTo: AppSelectPeers['appendTo'],
|
||||||
|
onChange?: AppSelectPeers['onChange'],
|
||||||
|
peerType?: AppSelectPeers['peerType'],
|
||||||
|
onFirstRender?: () => void,
|
||||||
|
renderResultsFunc?: AppSelectPeers['renderResultsFunc'],
|
||||||
|
chatRightsAction?: AppSelectPeers['chatRightsAction'],
|
||||||
|
multiSelect?: AppSelectPeers['multiSelect']
|
||||||
|
}) {
|
||||||
|
for(let i in options) {
|
||||||
|
// @ts-ignore
|
||||||
|
this[i] = options[i];
|
||||||
|
}
|
||||||
|
|
||||||
this.container.classList.add('selector');
|
this.container.classList.add('selector');
|
||||||
|
|
||||||
const f = (renderResultsFunc || this.renderResults).bind(this);
|
const f = (this.renderResultsFunc || this.renderResults).bind(this);
|
||||||
this.renderResultsFunc = (peerIds: number[]) => {
|
this.renderResultsFunc = (peerIds: number[]) => {
|
||||||
peerIds = peerIds.filter(peerId => {
|
peerIds = peerIds.filter(peerId => {
|
||||||
const notRendered = !this.renderedPeerIds.has(peerId);
|
const notRendered = !this.renderedPeerIds.has(peerId);
|
||||||
@ -55,10 +76,10 @@ export default class AppSelectPeers {
|
|||||||
|
|
||||||
this.input = document.createElement('input');
|
this.input = document.createElement('input');
|
||||||
this.input.classList.add('selector-search-input');
|
this.input.classList.add('selector-search-input');
|
||||||
this.input.placeholder = !peerType.includes('dialogs') ? 'Add People...' : 'Select chat';
|
this.input.placeholder = !this.peerType.includes('dialogs') ? 'Add People...' : 'Select chat';
|
||||||
this.input.type = 'text';
|
this.input.type = 'text';
|
||||||
|
|
||||||
if(multiSelect) {
|
if(this.multiSelect) {
|
||||||
let topContainer = document.createElement('div');
|
let topContainer = document.createElement('div');
|
||||||
topContainer.classList.add('selector-search-container');
|
topContainer.classList.add('selector-search-container');
|
||||||
|
|
||||||
@ -151,14 +172,14 @@ export default class AppSelectPeers {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.container.append(this.chatsContainer);
|
this.container.append(this.chatsContainer);
|
||||||
appendTo.append(this.container);
|
this.appendTo.append(this.container);
|
||||||
|
|
||||||
// WARNING TIMEOUT
|
// WARNING TIMEOUT
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
let getResultsPromise = this.getMoreResults() as Promise<any>;
|
let getResultsPromise = this.getMoreResults() as Promise<any>;
|
||||||
if(onFirstRender) {
|
if(options.onFirstRender) {
|
||||||
getResultsPromise.then(() => {
|
getResultsPromise.then(() => {
|
||||||
onFirstRender();
|
options.onFirstRender();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
@ -346,7 +367,7 @@ export default class AppSelectPeers {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public add(peerId: any, title?: string) {
|
public add(peerId: any, title?: string, scroll = true) {
|
||||||
//console.trace('add');
|
//console.trace('add');
|
||||||
this.selected.add(peerId);
|
this.selected.add(peerId);
|
||||||
|
|
||||||
@ -380,9 +401,12 @@ export default class AppSelectPeers {
|
|||||||
|
|
||||||
this.selectedContainer.insertBefore(div, this.input);
|
this.selectedContainer.insertBefore(div, this.input);
|
||||||
//this.selectedScrollable.scrollTop = this.selectedScrollable.scrollHeight;
|
//this.selectedScrollable.scrollTop = this.selectedScrollable.scrollHeight;
|
||||||
this.selectedScrollable.scrollTo(this.selectedScrollable.scrollHeight, 'top', true, true);
|
|
||||||
this.onChange && this.onChange(this.selected.size);
|
this.onChange && this.onChange(this.selected.size);
|
||||||
|
|
||||||
|
if(scroll) {
|
||||||
|
this.selectedScrollable.scrollIntoViewNew(this.input, 'center');
|
||||||
|
}
|
||||||
|
|
||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -410,4 +434,12 @@ export default class AppSelectPeers {
|
|||||||
public getSelected() {
|
public getSelected() {
|
||||||
return [...this.selected];
|
return [...this.selected];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public addInitial(values: any[]) {
|
||||||
|
values.forEach(value => {
|
||||||
|
this.add(value, undefined, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.selectedScrollable.scrollIntoViewNew(this.input, 'center', undefined, undefined, FocusDirection.Static);
|
||||||
|
}
|
||||||
}
|
}
|
@ -28,6 +28,8 @@ export default class BubbleGroups {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addBubble(bubble: HTMLDivElement, message: MyMessage, reverse: boolean) {
|
addBubble(bubble: HTMLDivElement, message: MyMessage, reverse: boolean) {
|
||||||
|
//return;
|
||||||
|
|
||||||
const timestamp = message.date;
|
const timestamp = message.date;
|
||||||
const mid = message.mid;
|
const mid = message.mid;
|
||||||
let fromId = message.fromId;
|
let fromId = message.fromId;
|
||||||
|
@ -39,6 +39,9 @@ import PollElement from "../poll";
|
|||||||
import AudioElement from "../audio";
|
import AudioElement from "../audio";
|
||||||
import { Message, MessageEntity, MessageReplies, MessageReplyHeader } from "../../layer";
|
import { Message, MessageEntity, MessageReplies, MessageReplyHeader } from "../../layer";
|
||||||
import { DEBUG, MOUNT_CLASS_TO, REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config";
|
import { DEBUG, MOUNT_CLASS_TO, REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config";
|
||||||
|
import { FocusDirection } from "../../helpers/fastSmoothScroll";
|
||||||
|
import useHeavyAnimationCheck, { getHeavyAnimationPromise } from "../../hooks/useHeavyAnimationCheck";
|
||||||
|
import { fastRaf } from "../../helpers/schedulers";
|
||||||
|
|
||||||
const IGNORE_ACTIONS = ['messageActionHistoryClear'];
|
const IGNORE_ACTIONS = ['messageActionHistoryClear'];
|
||||||
|
|
||||||
@ -105,6 +108,9 @@ export default class ChatBubbles {
|
|||||||
|
|
||||||
public replyFollowHistory: number[] = [];
|
public replyFollowHistory: number[] = [];
|
||||||
|
|
||||||
|
public isHeavyAnimationInProgress = false;
|
||||||
|
public scrollingToNewBubble: HTMLElement;
|
||||||
|
|
||||||
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) {
|
constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appStickersManager: AppStickersManager, private appUsersManager: AppUsersManager, private appInlineBotsManager: AppInlineBotsManager, private appPhotosManager: AppPhotosManager, private appDocsManager: AppDocsManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager) {
|
||||||
//this.chat.log.error('Bubbles construction');
|
//this.chat.log.error('Bubbles construction');
|
||||||
|
|
||||||
@ -146,6 +152,10 @@ export default class ChatBubbles {
|
|||||||
|
|
||||||
this.bubbleGroups.addBubble(bubble, message, false);
|
this.bubbleGroups.addBubble(bubble, message, false);
|
||||||
|
|
||||||
|
if(this.scrollingToNewBubble) {
|
||||||
|
this.scrollToNewLastBubble();
|
||||||
|
}
|
||||||
|
|
||||||
//this.renderMessage(message, false, false, bubble);
|
//this.renderMessage(message, false, false, bubble);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -341,6 +351,12 @@ export default class ChatBubbles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useHeavyAnimationCheck(() => {
|
||||||
|
this.isHeavyAnimationInProgress = true;
|
||||||
|
}, () => {
|
||||||
|
this.isHeavyAnimationInProgress = false;
|
||||||
|
}, this.listenerSetter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public constructPeerHelpers() {
|
public constructPeerHelpers() {
|
||||||
@ -789,7 +805,15 @@ export default class ChatBubbles {
|
|||||||
|
|
||||||
public loadMoreHistory(top: boolean, justLoad = false) {
|
public loadMoreHistory(top: boolean, justLoad = false) {
|
||||||
//this.log('loadMoreHistory', top);
|
//this.log('loadMoreHistory', top);
|
||||||
if(!this.peerId || /* TEST_SCROLL || */ this.chat.setPeerPromise || (top && this.getHistoryTopPromise) || (!top && this.getHistoryBottomPromise)) return;
|
if(!this.peerId ||
|
||||||
|
/* TEST_SCROLL || */
|
||||||
|
this.chat.setPeerPromise ||
|
||||||
|
this.isHeavyAnimationInProgress ||
|
||||||
|
(top && this.getHistoryTopPromise) ||
|
||||||
|
(!top && this.getHistoryBottomPromise)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// warning, если иды только отрицательные то вниз не попадёт (хотя мб и так не попадёт)
|
// warning, если иды только отрицательные то вниз не попадёт (хотя мб и так не попадёт)
|
||||||
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);
|
||||||
@ -817,8 +841,10 @@ export default class ChatBubbles {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onScroll = () => {
|
public onScroll = () => {
|
||||||
|
//return;
|
||||||
|
|
||||||
// * В таком случае, кнопка не будет моргать если чат в самом низу, и правильно отработает случай написания нового сообщения и проскролла вниз
|
// * В таком случае, кнопка не будет моргать если чат в самом низу, и правильно отработает случай написания нового сообщения и проскролла вниз
|
||||||
if(this.scrollable.scrollLocked && this.scrolledDown) return;
|
if(this.isHeavyAnimationInProgress && this.scrolledDown) return;
|
||||||
//lottieLoader.checkAnimations(false, 'chat');
|
//lottieLoader.checkAnimations(false, 'chat');
|
||||||
|
|
||||||
if(!isTouchSupported) {
|
if(!isTouchSupported) {
|
||||||
@ -947,7 +973,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.scrolledAllDown) { // seems search active or sliced
|
||||||
this.log('seems search is active, skipping render:', mids);
|
this.log('renderNewMessagesByIds: seems search is active, skipping render:', mids);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -960,25 +986,15 @@ export default class ChatBubbles {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mids = mids.filter(mid => !this.bubbles[mid]);
|
mids = mids.filter(mid => !this.bubbles[mid]);
|
||||||
mids.forEach((mid: number) => {
|
const promise = this.performHistoryResult(mids, false, true);
|
||||||
const message = this.chat.getMessage(mid);
|
if(scrolledDown) {
|
||||||
|
promise.then(() => {
|
||||||
/////////this.log('got new message to append:', message);
|
|
||||||
|
|
||||||
//this.unreaded.push(msgID);
|
|
||||||
this.renderMessage(message);
|
|
||||||
});
|
|
||||||
|
|
||||||
//if(scrolledDown) this.scrollable.scrollTop = this.scrollable.scrollHeight;
|
|
||||||
if(this.messagesQueuePromise && scrolledDown/* && false */) {
|
|
||||||
if(this.scrollable.isScrolledDown && !this.scrollable.scrollLocked) {
|
|
||||||
//this.log('renderNewMessagesByIDs: messagesQueuePromise before will set prev max');
|
|
||||||
this.scrollable.scrollTo(this.scrollable.scrollHeight - 1, 'top', false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.messagesQueuePromise.then(() => {
|
|
||||||
//this.log('renderNewMessagesByIDs: messagesQueuePromise after', this.scrollable.isScrolledDown);
|
//this.log('renderNewMessagesByIDs: messagesQueuePromise after', this.scrollable.isScrolledDown);
|
||||||
this.scrollable.scrollTo(this.scrollable.scrollHeight, 'top', true, true);
|
//this.scrollable.scrollTo(this.scrollable.scrollHeight, 'top', true, true, 5000);
|
||||||
|
//const bubble = this.bubbles[Math.max(...mids)];
|
||||||
|
this.scrollToNewLastBubble();
|
||||||
|
|
||||||
|
//this.scrollable.scrollIntoViewNew(this.chatInner, 'end');
|
||||||
|
|
||||||
/* setTimeout(() => {
|
/* setTimeout(() => {
|
||||||
this.log('messagesQueuePromise afterafter:', this.chatInner.childElementCount, this.scrollable.scrollHeight);
|
this.log('messagesQueuePromise afterafter:', this.chatInner.childElementCount, this.scrollable.scrollHeight);
|
||||||
@ -987,6 +1003,17 @@ export default class ChatBubbles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public scrollToNewLastBubble() {
|
||||||
|
const bubble = this.chatInner.lastElementChild.lastElementChild as HTMLElement;
|
||||||
|
this.log('scrollToNewLastBubble: will scroll into view:', bubble);
|
||||||
|
if(bubble) {
|
||||||
|
this.scrollingToNewBubble = bubble;
|
||||||
|
this.scrollable.scrollIntoViewNew(bubble, 'end').then(() => {
|
||||||
|
this.scrollingToNewBubble = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public highlightBubble(element: HTMLElement) {
|
public highlightBubble(element: HTMLElement) {
|
||||||
const datasetKey = 'highlightTimeout';
|
const datasetKey = 'highlightTimeout';
|
||||||
if(element.dataset[datasetKey]) {
|
if(element.dataset[datasetKey]) {
|
||||||
@ -1168,7 +1195,7 @@ export default class ChatBubbles {
|
|||||||
const mounted = this.getMountedBubble(lastMsgId);
|
const mounted = this.getMountedBubble(lastMsgId);
|
||||||
if(mounted) {
|
if(mounted) {
|
||||||
if(isTarget) {
|
if(isTarget) {
|
||||||
this.scrollable.scrollIntoView(mounted.bubble);
|
this.scrollable.scrollIntoViewNew(mounted.bubble, 'center');
|
||||||
this.highlightBubble(mounted.bubble);
|
this.highlightBubble(mounted.bubble);
|
||||||
this.chat.setListenerResult('setPeer', lastMsgId, false);
|
this.chat.setListenerResult('setPeer', lastMsgId, false);
|
||||||
} else if(topMessage && !isJump) {
|
} else if(topMessage && !isJump) {
|
||||||
@ -1269,11 +1296,6 @@ export default class ChatBubbles {
|
|||||||
|
|
||||||
//if(dialog && lastMsgID && lastMsgID != topMessage && (this.bubbles[lastMsgID] || this.firstUnreadBubble)) {
|
//if(dialog && lastMsgID && lastMsgID != topMessage && (this.bubbles[lastMsgID] || this.firstUnreadBubble)) {
|
||||||
if((topMessage && isJump) || isTarget) {
|
if((topMessage && isJump) || isTarget) {
|
||||||
if(this.scrollable.scrollLocked) {
|
|
||||||
clearTimeout(this.scrollable.scrollLocked);
|
|
||||||
this.scrollable.scrollLocked = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fromUp = maxBubbleId > 0 && (maxBubbleId < lastMsgId || lastMsgId < 0);
|
const fromUp = maxBubbleId > 0 && (maxBubbleId < lastMsgId || lastMsgId < 0);
|
||||||
const forwardingUnread = historyStorage.readMaxId === lastMsgId && !isTarget;
|
const forwardingUnread = historyStorage.readMaxId === lastMsgId && !isTarget;
|
||||||
if(!fromUp && (samePeer || forwardingUnread)) {
|
if(!fromUp && (samePeer || forwardingUnread)) {
|
||||||
@ -1290,7 +1312,7 @@ export default class ChatBubbles {
|
|||||||
|
|
||||||
// ! sometimes there can be no bubble
|
// ! sometimes there can be no bubble
|
||||||
if(bubble) {
|
if(bubble) {
|
||||||
this.scrollable.scrollIntoView(bubble, samePeer/* , fromUp */);
|
this.scrollable.scrollIntoViewNew(bubble, forwardingUnread ? 'start' : 'center', undefined, undefined, !samePeer ? FocusDirection.Static : undefined);
|
||||||
if(!forwardingUnread) {
|
if(!forwardingUnread) {
|
||||||
this.highlightBubble(bubble);
|
this.highlightBubble(bubble);
|
||||||
}
|
}
|
||||||
@ -1395,43 +1417,50 @@ export default class ChatBubbles {
|
|||||||
|
|
||||||
this.messagesQueue.push({message, bubble, reverse, promises});
|
this.messagesQueue.push({message, bubble, reverse, promises});
|
||||||
|
|
||||||
if(!this.messagesQueuePromise) {
|
this.setMessagesQueuePromise();
|
||||||
this.messagesQueuePromise = new Promise((resolve, reject) => {
|
}
|
||||||
setTimeout(() => {
|
|
||||||
const chatInner = this.chatInner;
|
|
||||||
const queue = this.messagesQueue.slice();
|
|
||||||
this.messagesQueue.length = 0;
|
|
||||||
|
|
||||||
const promises = queue.reduce((acc, {promises}) => acc.concat(promises), []);
|
public setMessagesQueuePromise() {
|
||||||
|
if(this.messagesQueuePromise) return;
|
||||||
|
|
||||||
// * это нужно для того, чтобы если захочет подгрузить reply или какое-либо сообщение, то скролл не прервался
|
this.messagesQueuePromise = new Promise((resolve, reject) => {
|
||||||
if(this.scrollable.scrollLocked) {
|
setTimeout(() => {
|
||||||
promises.push(this.scrollable.scrollLockedPromise);
|
const chatInner = this.chatInner;
|
||||||
|
const queue = this.messagesQueue.slice();
|
||||||
|
this.messagesQueue.length = 0;
|
||||||
|
|
||||||
|
const promises = queue.reduce((acc, {promises}) => acc.concat(promises), []);
|
||||||
|
|
||||||
|
// * это нужно для того, чтобы если захочет подгрузить reply или какое-либо сообщение, то скролл не прервался
|
||||||
|
// * если добавить этот промис - в таком случае нужно сделать, чтобы скроллило к последнему сообщению после рендера
|
||||||
|
// promises.push(getHeavyAnimationPromise());
|
||||||
|
|
||||||
|
//this.log('promises to call', promises, queue);
|
||||||
|
Promise.all(promises).then(() => {
|
||||||
|
if(this.chatInner != chatInner) {
|
||||||
|
//this.log.warn('chatInner changed!', this.chatInner, chatInner);
|
||||||
|
return reject('chatInner changed!');
|
||||||
}
|
}
|
||||||
|
|
||||||
//this.log('promises to call', promises, queue);
|
if(this.messagesQueueOnRender) {
|
||||||
Promise.all(promises).then(() => {
|
this.messagesQueueOnRender();
|
||||||
if(this.chatInner != chatInner) {
|
}
|
||||||
//this.log.warn('chatInner changed!', this.chatInner, chatInner);
|
|
||||||
return reject('chatInner changed!');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.messagesQueueOnRender) {
|
queue.forEach(({message, bubble, reverse}) => {
|
||||||
this.messagesQueueOnRender();
|
this.setBubblePosition(bubble, message, reverse);
|
||||||
}
|
});
|
||||||
|
|
||||||
queue.forEach(({message, bubble, reverse}) => {
|
//setTimeout(() => {
|
||||||
this.setBubblePosition(bubble, message, reverse);
|
resolve();
|
||||||
});
|
//}, 500);
|
||||||
|
this.messagesQueuePromise = null;
|
||||||
|
|
||||||
//setTimeout(() => {
|
if(this.messagesQueue.length) {
|
||||||
resolve();
|
this.setMessagesQueuePromise();
|
||||||
//}, 500);
|
}
|
||||||
this.messagesQueuePromise = null;
|
}, reject);
|
||||||
}, reject);
|
}, 0);
|
||||||
}, 0);
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public setBubblePosition(bubble: HTMLElement, message: any, reverse: boolean) {
|
public setBubblePosition(bubble: HTMLElement, message: any, reverse: boolean) {
|
||||||
@ -2259,14 +2288,14 @@ 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.log('performHistoryResult: will render some messages:', history.length, this.isHeavyAnimationInProgress);
|
||||||
|
|
||||||
const method = (reverse ? history.shift : history.pop).bind(history);
|
const method = (reverse ? history.shift : history.pop).bind(history);
|
||||||
|
|
||||||
//const padding = 10000;
|
//const padding = 10000;
|
||||||
const realLength = this.scrollable.container.childElementCount;
|
//const realLength = this.scrollable.container.childElementCount;
|
||||||
let previousScrollHeightMinusTop: number/* , previousScrollHeight: number */;
|
let previousScrollHeightMinusTop: number/* , previousScrollHeight: number */;
|
||||||
if(realLength > 0 && (reverse || isSafari)) { // for safari need set when scrolling bottom too
|
//if(realLength > 0/* && (reverse || isSafari) */) { // for safari need set when scrolling bottom too
|
||||||
this.messagesQueueOnRender = () => {
|
this.messagesQueueOnRender = () => {
|
||||||
const {scrollTop, scrollHeight} = this.scrollable;
|
const {scrollTop, scrollHeight} = this.scrollable;
|
||||||
|
|
||||||
@ -2284,7 +2313,7 @@ export default class ChatBubbles {
|
|||||||
//this.log('performHistoryResult: messagesQueueOnRender, scrollTop:', scrollTop, scrollHeight, previousScrollHeightMinusTop);
|
//this.log('performHistoryResult: messagesQueueOnRender, scrollTop:', scrollTop, scrollHeight, previousScrollHeightMinusTop);
|
||||||
this.messagesQueueOnRender = undefined;
|
this.messagesQueueOnRender = undefined;
|
||||||
};
|
};
|
||||||
}
|
//}
|
||||||
|
|
||||||
while(history.length) {
|
while(history.length) {
|
||||||
let message = this.chat.getMessage(method());
|
let message = this.chat.getMessage(method());
|
||||||
@ -2297,28 +2326,29 @@ export default class ChatBubbles {
|
|||||||
if(previousScrollHeightMinusTop !== undefined) {
|
if(previousScrollHeightMinusTop !== undefined) {
|
||||||
/* const scrollHeight = this.scrollable.scrollHeight;
|
/* const scrollHeight = this.scrollable.scrollHeight;
|
||||||
const addedHeight = scrollHeight - previousScrollHeight;
|
const addedHeight = scrollHeight - previousScrollHeight;
|
||||||
|
|
||||||
this.chatInner.style.paddingTop = (10000 - addedHeight) + 'px'; */
|
this.chatInner.style.paddingTop = (10000 - addedHeight) + 'px'; */
|
||||||
/* const scrollHeight = this.scrollable.scrollHeight;
|
/* const scrollHeight = this.scrollable.scrollHeight;
|
||||||
const addedHeight = scrollHeight - previousScrollHeight;
|
const addedHeight = scrollHeight - previousScrollHeight;
|
||||||
|
|
||||||
this.chatInner.style.paddingTop = (padding - addedHeight) + 'px';
|
this.chatInner.style.paddingTop = (padding - addedHeight) + 'px';
|
||||||
|
|
||||||
//const newScrollTop = reverse ? scrollHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop;
|
//const newScrollTop = reverse ? scrollHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop;
|
||||||
const newScrollTop = reverse ? scrollHeight - addedHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop;
|
const newScrollTop = reverse ? scrollHeight - addedHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop;
|
||||||
this.log('performHistoryResult: will set scrollTop',
|
this.log('performHistoryResult: will set scrollTop',
|
||||||
previousScrollHeightMinusTop, this.scrollable.scrollHeight,
|
previousScrollHeightMinusTop, this.scrollable.scrollHeight,
|
||||||
newScrollTop, this.scrollable.container.clientHeight); */
|
newScrollTop, this.scrollable.container.clientHeight); */
|
||||||
//const newScrollTop = reverse ? scrollHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop;
|
//const newScrollTop = reverse ? scrollHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop;
|
||||||
const newScrollTop = reverse ? this.scrollable.scrollHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop;
|
const newScrollTop = reverse ? this.scrollable.scrollHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop;
|
||||||
|
|
||||||
|
this.log('performHistoryResult: will set up scrollTop:', newScrollTop, this.isHeavyAnimationInProgress);
|
||||||
// touchSupport for safari iOS
|
// touchSupport for safari iOS
|
||||||
isTouchSupported && isApple && (this.scrollable.container.style.overflow = 'hidden');
|
isTouchSupported && isApple && (this.scrollable.container.style.overflow = 'hidden');
|
||||||
this.scrollable.scrollTop = newScrollTop;
|
this.scrollable.scrollTop = newScrollTop;
|
||||||
//this.scrollable.scrollTop = this.scrollable.scrollHeight;
|
//this.scrollable.scrollTop = this.scrollable.scrollHeight;
|
||||||
isTouchSupported && isApple && (this.scrollable.container.style.overflow = '');
|
isTouchSupported && isApple && (this.scrollable.container.style.overflow = '');
|
||||||
|
|
||||||
//this.log('performHistoryResult: have set up scrollTop:', newScrollTop, this.scrollable.scrollTop);
|
this.log('performHistoryResult: have set up scrollTop:', newScrollTop, this.scrollable.scrollTop, this.isHeavyAnimationInProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(true);
|
resolve(true);
|
||||||
|
@ -64,7 +64,7 @@ export namespace MessageRender {
|
|||||||
message: any,
|
message: any,
|
||||||
messageDiv: HTMLElement
|
messageDiv: HTMLElement
|
||||||
}) => {
|
}) => {
|
||||||
const isFooter = !bubble.classList.contains('sticker') && !bubble.classList.contains('emoji-big');
|
const isFooter = !bubble.classList.contains('sticker') && !bubble.classList.contains('emoji-big') && !bubble.classList.contains('round');
|
||||||
const repliesFooter = new RepliesElement();
|
const repliesFooter = new RepliesElement();
|
||||||
repliesFooter.message = message;
|
repliesFooter.message = message;
|
||||||
repliesFooter.type = isFooter ? 'footer' : 'beside';
|
repliesFooter.type = isFooter ? 'footer' : 'beside';
|
||||||
|
@ -12,6 +12,7 @@ import Chat from "./chat";
|
|||||||
import ListenerSetter from "../../helpers/listenerSetter";
|
import ListenerSetter from "../../helpers/listenerSetter";
|
||||||
import ButtonIcon from "../buttonIcon";
|
import ButtonIcon from "../buttonIcon";
|
||||||
import { debounce } from "../../helpers/schedulers";
|
import { debounce } from "../../helpers/schedulers";
|
||||||
|
import { getHeavyAnimationPromise } from "../../hooks/useHeavyAnimationCheck";
|
||||||
|
|
||||||
class AnimatedSuper {
|
class AnimatedSuper {
|
||||||
static DURATION = 200;
|
static DURATION = 200;
|
||||||
@ -514,7 +515,8 @@ export default class ChatPinnedMessage {
|
|||||||
await setPeerPromise;
|
await setPeerPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.chat.bubbles.scrollable.scrollLockedPromise;
|
//await this.chat.bubbles.scrollable.scrollLockedPromise;
|
||||||
|
await getHeavyAnimationPromise();
|
||||||
|
|
||||||
if(this.getCurrentIndexPromise) {
|
if(this.getCurrentIndexPromise) {
|
||||||
await this.getCurrentIndexPromise;
|
await this.getCurrentIndexPromise;
|
||||||
|
@ -2,6 +2,8 @@ import { findUpTag, whichChild } from "../helpers/dom";
|
|||||||
import { TransitionSlider } from "./transition";
|
import { TransitionSlider } from "./transition";
|
||||||
import { ScrollableX } from "./scrollable";
|
import { ScrollableX } from "./scrollable";
|
||||||
import rootScope from "../lib/rootScope";
|
import rootScope from "../lib/rootScope";
|
||||||
|
import { fastRaf } from "../helpers/schedulers";
|
||||||
|
import { FocusDirection } from "../helpers/fastSmoothScroll";
|
||||||
|
|
||||||
export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void, transitionTime = 250, scrollableX?: ScrollableX) {
|
export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void, transitionTime = 250, scrollableX?: ScrollableX) {
|
||||||
const selectTab = TransitionSlider(content, tabs || content.dataset.slider == 'tabs' ? 'tabs' : 'navigation', transitionTime, onTransitionEnd);
|
const selectTab = TransitionSlider(content, tabs || content.dataset.slider == 'tabs' ? 'tabs' : 'navigation', transitionTime, onTransitionEnd);
|
||||||
@ -23,7 +25,7 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?
|
|||||||
if(onClick) onClick(id, tabContent);
|
if(onClick) onClick(id, tabContent);
|
||||||
|
|
||||||
if(scrollableX) {
|
if(scrollableX) {
|
||||||
scrollableX.scrollIntoView(target.parentElement.children[id] as HTMLElement, true, transitionTime);
|
scrollableX.scrollIntoViewNew(target.parentElement.children[id] as HTMLElement, 'center', undefined, undefined, animate ? undefined : FocusDirection.Static, transitionTime, 'x');
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!rootScope.settings.animationsEnabled) {
|
if(!rootScope.settings.animationsEnabled) {
|
||||||
@ -35,32 +37,41 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?
|
|||||||
}
|
}
|
||||||
|
|
||||||
const prev = tabs.querySelector(tagName.toLowerCase() + '.active') as HTMLElement;
|
const prev = tabs.querySelector(tagName.toLowerCase() + '.active') as HTMLElement;
|
||||||
prev && prev.classList.remove('active');
|
|
||||||
|
|
||||||
|
fastRaf(() => {
|
||||||
|
prev && prev.classList.remove('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
const prevId = selectTab.prevId;
|
||||||
// stripe from ZINCHUK
|
// stripe from ZINCHUK
|
||||||
if(useStripe && selectTab.prevId !== -1 && animate) {
|
if(useStripe && prevId !== -1 && animate) {
|
||||||
const indicator = target.querySelector('i')!;
|
fastRaf(() => {
|
||||||
const currentIndicator = target.parentElement.children[selectTab.prevId].querySelector('i')!;
|
const indicator = target.querySelector('i')!;
|
||||||
|
const currentIndicator = target.parentElement.children[prevId].querySelector('i')!;
|
||||||
|
|
||||||
|
currentIndicator.classList.remove('animate');
|
||||||
|
indicator.classList.remove('animate');
|
||||||
|
|
||||||
|
// We move and resize our indicator so it repeats the position and size of the previous one.
|
||||||
|
const shiftLeft = currentIndicator.parentElement.parentElement.offsetLeft - indicator.parentElement.parentElement.offsetLeft;
|
||||||
|
const scaleFactor = currentIndicator.clientWidth / indicator.clientWidth;
|
||||||
|
indicator.style.transform = `translate3d(${shiftLeft}px, 0, 0) scale3d(${scaleFactor}, 1, 1)`;
|
||||||
|
|
||||||
currentIndicator.classList.remove('animate');
|
//console.log(`translate3d(${shiftLeft}px, 0, 0) scale3d(${scaleFactor}, 1, 1)`);
|
||||||
indicator.classList.remove('animate');
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
// We move and resize our indicator so it repeats the position and size of the previous one.
|
// Now we remove the transform to let it animate to its own position and size.
|
||||||
const shiftLeft = currentIndicator.parentElement.parentElement.offsetLeft - indicator.parentElement.parentElement.offsetLeft;
|
indicator.classList.add('animate');
|
||||||
const scaleFactor = currentIndicator.clientWidth / indicator.clientWidth;
|
indicator.style.transform = 'none';
|
||||||
indicator.style.transform = `translate3d(${shiftLeft}px, 0, 0) scale3d(${scaleFactor}, 1, 1)`;
|
});
|
||||||
|
|
||||||
//console.log(`translate3d(${shiftLeft}px, 0, 0) scale3d(${scaleFactor}, 1, 1)`);
|
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
// Now we remove the transform to let it animate to its own position and size.
|
|
||||||
indicator.classList.add('animate');
|
|
||||||
indicator.style.transform = 'none';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// stripe END
|
// stripe END
|
||||||
|
|
||||||
target.classList.add('active');
|
fastRaf(() => {
|
||||||
|
target.classList.add('active');
|
||||||
|
});
|
||||||
|
|
||||||
selectTab(id, animate);
|
selectTab(id, animate);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -318,7 +318,7 @@ export default class PopupCreatePoll extends PopupElement {
|
|||||||
|
|
||||||
this.questions.append(radioField.label);
|
this.questions.append(radioField.label);
|
||||||
|
|
||||||
this.scrollable.scrollIntoView(this.questions.lastElementChild as HTMLElement, true);
|
this.scrollable.scrollIntoViewNew(this.questions.lastElementChild as HTMLElement, 'center');
|
||||||
//this.scrollable.scrollTo(this.scrollable.scrollHeight, 'top', true, true);
|
//this.scrollable.scrollTo(this.scrollable.scrollHeight, 'top', true, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,24 +12,31 @@ export default class PopupForward extends PopupElement {
|
|||||||
|
|
||||||
if(onClose) this.onClose = onClose;
|
if(onClose) this.onClose = onClose;
|
||||||
|
|
||||||
this.selector = new AppSelectPeers(this.body, async() => {
|
this.selector = new AppSelectPeers({
|
||||||
const peerId = this.selector.getSelected()[0];
|
appendTo: this.body,
|
||||||
this.btnClose.click();
|
onChange: async() => {
|
||||||
|
const peerId = this.selector.getSelected()[0];
|
||||||
|
this.btnClose.click();
|
||||||
|
|
||||||
this.selector = null;
|
this.selector = null;
|
||||||
|
|
||||||
await (onSelect ? onSelect() || Promise.resolve() : Promise.resolve());
|
await (onSelect ? onSelect() || Promise.resolve() : Promise.resolve());
|
||||||
|
|
||||||
appImManager.setInnerPeer(peerId);
|
appImManager.setInnerPeer(peerId);
|
||||||
appImManager.chat.input.initMessagesForward(fromPeerId, mids.slice());
|
appImManager.chat.input.initMessagesForward(fromPeerId, mids.slice());
|
||||||
}, ['dialogs', 'contacts'], () => {
|
},
|
||||||
this.show();
|
peerType: ['dialogs', 'contacts'],
|
||||||
this.selector.checkForTriggers(); // ! due to zero height before mounting
|
onFirstRender: () => {
|
||||||
|
this.show();
|
||||||
|
this.selector.checkForTriggers(); // ! due to zero height before mounting
|
||||||
|
|
||||||
if(!isTouchSupported) {
|
if(!isTouchSupported) {
|
||||||
this.selector.input.focus();
|
this.selector.input.focus();
|
||||||
}
|
}
|
||||||
}, null, 'send', false);
|
},
|
||||||
|
chatRightsAction: 'send',
|
||||||
|
multiSelect: false
|
||||||
|
});
|
||||||
|
|
||||||
//this.scrollable = new Scrollable(this.body);
|
//this.scrollable = new Scrollable(this.body);
|
||||||
|
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise";
|
import { CancellablePromise } from "../helpers/cancellablePromise";
|
||||||
import { isTouchSupported } from "../helpers/touchSupport";
|
import { isTouchSupported } from "../helpers/touchSupport";
|
||||||
import { logger, LogLevels } from "../lib/logger";
|
import { logger, LogLevels } from "../lib/logger";
|
||||||
import smoothscroll, { SCROLL_TIME, SmoothScrollToOptions } from '../vendor/smoothscroll';
|
import fastSmoothScroll from "../helpers/fastSmoothScroll";
|
||||||
import rootScope from "../lib/rootScope";
|
import useHeavyAnimationCheck from "../hooks/useHeavyAnimationCheck";
|
||||||
(window as any).__forceSmoothScrollPolyfill__ = true;
|
|
||||||
smoothscroll();
|
|
||||||
/*
|
/*
|
||||||
var el = $0;
|
var el = $0;
|
||||||
var height = 0;
|
var height = 0;
|
||||||
@ -51,11 +49,10 @@ const scrollsIntersector = new IntersectionObserver(entries => {
|
|||||||
export class ScrollableBase {
|
export class ScrollableBase {
|
||||||
protected log: ReturnType<typeof logger>;
|
protected log: ReturnType<typeof logger>;
|
||||||
|
|
||||||
|
public onScrollMeasure: number = 0;
|
||||||
protected onScroll: () => void;
|
protected onScroll: () => void;
|
||||||
public getScrollValue: () => number;
|
|
||||||
|
|
||||||
public scrollLocked = 0;
|
public isHeavyAnimationInProgress: boolean;
|
||||||
public scrollLockedPromise: CancellablePromise<void> = Promise.resolve();
|
|
||||||
|
|
||||||
constructor(public el: HTMLElement, logPrefix = '', public container: HTMLElement = document.createElement('div')) {
|
constructor(public el: HTMLElement, logPrefix = '', public container: HTMLElement = document.createElement('div')) {
|
||||||
this.container.classList.add('scrollable');
|
this.container.classList.add('scrollable');
|
||||||
@ -73,55 +70,24 @@ export class ScrollableBase {
|
|||||||
protected setListeners() {
|
protected setListeners() {
|
||||||
window.addEventListener('resize', this.onScroll, {passive: true});
|
window.addEventListener('resize', this.onScroll, {passive: true});
|
||||||
this.container.addEventListener('scroll', this.onScroll, {passive: true, capture: true});
|
this.container.addEventListener('scroll', this.onScroll, {passive: true, capture: true});
|
||||||
|
|
||||||
|
useHeavyAnimationCheck(() => {
|
||||||
|
this.isHeavyAnimationInProgress = true;
|
||||||
|
|
||||||
|
if(this.onScrollMeasure) {
|
||||||
|
window.cancelAnimationFrame(this.onScrollMeasure);
|
||||||
|
}
|
||||||
|
}, () => {
|
||||||
|
this.isHeavyAnimationInProgress = false;
|
||||||
|
this.onScroll();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public append(element: HTMLElement) {
|
public append(element: HTMLElement) {
|
||||||
this.container.append(element);
|
this.container.append(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
public scrollTo(value: number, side: 'top' | 'left', smooth = true, important = false, scrollTime = SCROLL_TIME) {
|
public scrollIntoViewNew = fastSmoothScroll.bind(this, this.container);
|
||||||
if(this.scrollLocked && !important) return;
|
|
||||||
|
|
||||||
const scrollValue = this.getScrollValue();
|
|
||||||
if(scrollValue == Math.floor(value)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!rootScope.settings.animationsEnabled) {
|
|
||||||
smooth = false;
|
|
||||||
scrollTime = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wasLocked = !!this.scrollLocked;
|
|
||||||
if(wasLocked) clearTimeout(this.scrollLocked);
|
|
||||||
if(smooth) {
|
|
||||||
if(!wasLocked) {
|
|
||||||
this.scrollLockedPromise = deferredPromise<void>();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scrollLocked = window.setTimeout(() => {
|
|
||||||
this.scrollLocked = 0;
|
|
||||||
this.scrollLockedPromise.resolve();
|
|
||||||
//this.onScroll();
|
|
||||||
this.container.dispatchEvent(new CustomEvent('scroll'));
|
|
||||||
}, scrollTime);
|
|
||||||
} else if(wasLocked) {
|
|
||||||
this.scrollLockedPromise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: SmoothScrollToOptions = {
|
|
||||||
behavior: smooth ? 'smooth' : 'auto',
|
|
||||||
scrollTime
|
|
||||||
};
|
|
||||||
|
|
||||||
options[side] = value;
|
|
||||||
|
|
||||||
this.container.scrollTo(options as any);
|
|
||||||
|
|
||||||
if(!smooth) {
|
|
||||||
this.container.dispatchEvent(new CustomEvent('scroll'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SliceSides = 'top' | 'bottom';
|
export type SliceSides = 'top' | 'bottom';
|
||||||
@ -134,8 +100,6 @@ export default class Scrollable extends ScrollableBase {
|
|||||||
public onAdditionalScroll: () => void = null;
|
public onAdditionalScroll: () => void = null;
|
||||||
public onScrolledTop: () => void = null;
|
public onScrolledTop: () => void = null;
|
||||||
public onScrolledBottom: () => void = null;
|
public onScrolledBottom: () => void = null;
|
||||||
|
|
||||||
public onScrollMeasure: number = null;
|
|
||||||
|
|
||||||
public lastScrollTop: number = 0;
|
public lastScrollTop: number = 0;
|
||||||
public lastScrollDirection: number = 0;
|
public lastScrollDirection: number = 0;
|
||||||
@ -168,8 +132,16 @@ export default class Scrollable extends ScrollableBase {
|
|||||||
|
|
||||||
//return;
|
//return;
|
||||||
|
|
||||||
|
if(this.isHeavyAnimationInProgress) {
|
||||||
|
if(this.onScrollMeasure) {
|
||||||
|
window.cancelAnimationFrame(this.onScrollMeasure);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//if(this.onScrollMeasure || ((this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) && !this.splitUp && !this.onAdditionalScroll)) return;
|
//if(this.onScrollMeasure || ((this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) && !this.splitUp && !this.onAdditionalScroll)) return;
|
||||||
if((this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) && !this.splitUp && !this.onAdditionalScroll) return;
|
if((!this.onScrolledTop && !this.onScrolledBottom) && !this.splitUp && !this.onAdditionalScroll) return;
|
||||||
if(this.onScrollMeasure) window.cancelAnimationFrame(this.onScrollMeasure);
|
if(this.onScrollMeasure) window.cancelAnimationFrame(this.onScrollMeasure);
|
||||||
this.onScrollMeasure = window.requestAnimationFrame(() => {
|
this.onScrollMeasure = window.requestAnimationFrame(() => {
|
||||||
this.onScrollMeasure = 0;
|
this.onScrollMeasure = 0;
|
||||||
@ -189,7 +161,7 @@ export default class Scrollable extends ScrollableBase {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public checkForTriggers = () => {
|
public checkForTriggers = () => {
|
||||||
if(this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) return;
|
if((!this.onScrolledTop && !this.onScrolledBottom) || this.isHeavyAnimationInProgress) return;
|
||||||
|
|
||||||
const scrollHeight = this.container.scrollHeight;
|
const scrollHeight = this.container.scrollHeight;
|
||||||
if(!scrollHeight) { // незачем вызывать триггеры если блок пустой или не виден
|
if(!scrollHeight) { // незачем вызывать триггеры если блок пустой или не виден
|
||||||
@ -219,66 +191,6 @@ export default class Scrollable extends ScrollableBase {
|
|||||||
(this.splitUp || this.padding || this.container).append(...elements);
|
(this.splitUp || this.padding || this.container).append(...elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
public scrollIntoView(element: HTMLElement, smooth = true) {
|
|
||||||
if(element.parentElement && !this.scrollLocked) {
|
|
||||||
const isFirstUnread = element.classList.contains('is-first-unread');
|
|
||||||
|
|
||||||
let offset = element.getBoundingClientRect().top - this.container.getBoundingClientRect().top;
|
|
||||||
offset = this.scrollTop + offset;
|
|
||||||
|
|
||||||
if(!smooth && isFirstUnread) {
|
|
||||||
this.scrollTo(offset, 'top', false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientHeight = this.container.clientHeight;
|
|
||||||
const height = element.scrollHeight;
|
|
||||||
|
|
||||||
const d = height >= clientHeight ? 0 : (clientHeight - height) / 2;
|
|
||||||
offset -= d;
|
|
||||||
|
|
||||||
this.scrollTo(offset, 'top', smooth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getScrollValue = () => {
|
|
||||||
return this.scrollTop;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* public slice(side: SliceSides, safeCount: number) {
|
|
||||||
//const isOtherSideLoaded = this.loadedAll[side == 'top' ? 'bottom' : 'top'];
|
|
||||||
//const multiplier = 2 - +isOtherSideLoaded;
|
|
||||||
const multiplier = 2;
|
|
||||||
safeCount *= multiplier;
|
|
||||||
|
|
||||||
const length = this.splitUp.childElementCount;
|
|
||||||
|
|
||||||
if(length <= safeCount) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const children = Array.from(this.splitUp.children) as HTMLElement[];
|
|
||||||
const sliced = side == 'top' ? children.slice(0, length - safeCount) : children.slice(safeCount);
|
|
||||||
for(const el of sliced) {
|
|
||||||
el.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.log.error('slice', side, length, sliced.length, this.splitUp.childElementCount);
|
|
||||||
|
|
||||||
if(sliced.length) {
|
|
||||||
this.loadedAll[side] = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// * fix instant load of cutted side
|
|
||||||
if(side == 'top') {
|
|
||||||
this.lastScrollTop = 0;
|
|
||||||
} else {
|
|
||||||
this.lastScrollTop = this.scrollHeight + this.container.clientHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sliced;
|
|
||||||
} */
|
|
||||||
|
|
||||||
get isScrolledDown() {
|
get isScrolledDown() {
|
||||||
return this.scrollHeight - Math.round(this.scrollTop + this.container.offsetHeight) <= 1;
|
return this.scrollHeight - Math.round(this.scrollTop + this.container.offsetHeight) <= 1;
|
||||||
}
|
}
|
||||||
@ -326,26 +238,5 @@ export class ScrollableX extends ScrollableBase {
|
|||||||
this.container.attachEvent("onmousewheel", scrollHorizontally);
|
this.container.attachEvent("onmousewheel", scrollHorizontally);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public scrollIntoView(element: HTMLElement, smooth = true, scrollTime?: number) {
|
|
||||||
if(element.parentElement && !this.scrollLocked) {
|
|
||||||
let offset = element.getBoundingClientRect().left - this.container.getBoundingClientRect().left;
|
|
||||||
offset = this.getScrollValue() + offset;
|
|
||||||
|
|
||||||
const clientWidth = this.container.clientWidth;
|
|
||||||
const width = element.scrollWidth;
|
|
||||||
|
|
||||||
const d = width >= clientWidth ? 0 : (clientWidth - width) / 2;
|
|
||||||
offset -= d;
|
|
||||||
|
|
||||||
this.scrollTo(offset, 'left', smooth, undefined, scrollTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getScrollValue = () => {
|
|
||||||
return this.container.scrollLeft;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import SidebarSlider, { SliderSuperTab } from "../../slider";
|
|||||||
import AppSelectPeers from "../../appSelectPeers";
|
import AppSelectPeers from "../../appSelectPeers";
|
||||||
import { putPreloader } from "../../misc";
|
import { putPreloader } from "../../misc";
|
||||||
import Button from "../../button";
|
import Button from "../../button";
|
||||||
|
import { fastRaf } from "../../../helpers/schedulers";
|
||||||
|
|
||||||
export default class AppAddMembersTab extends SliderSuperTab {
|
export default class AppAddMembersTab extends SliderSuperTab {
|
||||||
private nextBtn: HTMLButtonElement;
|
private nextBtn: HTMLButtonElement;
|
||||||
@ -67,14 +68,18 @@ export default class AppAddMembersTab extends SliderSuperTab {
|
|||||||
this.skippable = options.skippable;
|
this.skippable = options.skippable;
|
||||||
|
|
||||||
this.onCloseAfterTimeout();
|
this.onCloseAfterTimeout();
|
||||||
this.selector = new AppSelectPeers(this.content, this.skippable ? null : (length) => {
|
this.selector = new AppSelectPeers({
|
||||||
this.nextBtn.classList.toggle('is-visible', !!length);
|
appendTo: this.content,
|
||||||
}, ['contacts']);
|
onChange: this.skippable ? null : (length) => {
|
||||||
|
this.nextBtn.classList.toggle('is-visible', !!length);
|
||||||
|
},
|
||||||
|
peerType: ['contacts']
|
||||||
|
});
|
||||||
this.selector.input.placeholder = options.placeholder;
|
this.selector.input.placeholder = options.placeholder;
|
||||||
|
|
||||||
if(options.selectedPeerIds) {
|
if(options.selectedPeerIds) {
|
||||||
options.selectedPeerIds.forEach(peerId => {
|
fastRaf(() => {
|
||||||
this.selector.add(peerId);
|
this.selector.addInitial(options.selectedPeerIds);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ import { MyDialogFilter as DialogFilter } from "../../../lib/storages/filters";
|
|||||||
import rootScope from "../../../lib/rootScope";
|
import rootScope from "../../../lib/rootScope";
|
||||||
import { copy } from "../../../helpers/object";
|
import { copy } from "../../../helpers/object";
|
||||||
import ButtonIcon from "../../buttonIcon";
|
import ButtonIcon from "../../buttonIcon";
|
||||||
|
import { FocusDirection } from "../../../helpers/fastSmoothScroll";
|
||||||
|
import { fastRaf } from "../../../helpers/schedulers";
|
||||||
|
|
||||||
export default class AppIncludedChatsTab extends SliderSuperTab {
|
export default class AppIncludedChatsTab extends SliderSuperTab {
|
||||||
private confirmBtn: HTMLElement;
|
private confirmBtn: HTMLElement;
|
||||||
@ -190,13 +192,18 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
|
|||||||
|
|
||||||
const selectedPeers = (this.type === 'included' ? filter.include_peers : filter.exclude_peers).slice();
|
const selectedPeers = (this.type === 'included' ? filter.include_peers : filter.exclude_peers).slice();
|
||||||
|
|
||||||
this.selector = new AppSelectPeers(this.container, this.onSelectChange, ['dialogs'], null, this.renderResults);
|
this.selector = new AppSelectPeers({
|
||||||
|
appendTo: this.container,
|
||||||
|
onChange: this.onSelectChange,
|
||||||
|
peerType: ['dialogs'],
|
||||||
|
renderResultsFunc: this.renderResults
|
||||||
|
});
|
||||||
this.selector.selected = new Set(selectedPeers);
|
this.selector.selected = new Set(selectedPeers);
|
||||||
this.selector.input.placeholder = 'Search';
|
this.selector.input.placeholder = 'Search';
|
||||||
|
|
||||||
const _add = this.selector.add.bind(this.selector);
|
const _add = this.selector.add.bind(this.selector);
|
||||||
this.selector.add = (peerId, title) => {
|
this.selector.add = (peerId, title, scroll) => {
|
||||||
const div = _add(peerId, details[peerId]?.text);
|
const div = _add(peerId, details[peerId]?.text, scroll);
|
||||||
if(details[peerId]) {
|
if(details[peerId]) {
|
||||||
div.querySelector('avatar-element').classList.add('tgico-' + details[peerId].ico);
|
div.querySelector('avatar-element').classList.add('tgico-' + details[peerId].ico);
|
||||||
}
|
}
|
||||||
@ -205,8 +212,8 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
|
|||||||
|
|
||||||
this.selector.list.parentElement.insertBefore(fragment, this.selector.list);
|
this.selector.list.parentElement.insertBefore(fragment, this.selector.list);
|
||||||
|
|
||||||
selectedPeers.forEach(peerId => {
|
fastRaf(() => {
|
||||||
this.selector.add(peerId);
|
this.selector.addInitial(selectedPeers);
|
||||||
});
|
});
|
||||||
|
|
||||||
for(const flag in filter.pFlags) {
|
for(const flag in filter.pFlags) {
|
||||||
@ -215,11 +222,6 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
|
|||||||
(categories.querySelector(`[data-peerId="${flag}"]`) as HTMLElement).click();
|
(categories.querySelector(`[data-peerId="${flag}"]`) as HTMLElement).click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ! потому что onOpen срабатывает раньше, чем блок отрисовывается, и высоты нет
|
|
||||||
setTimeout(() => {
|
|
||||||
this.selector.selectedScrollable.scrollTo(this.selector.selectedScrollable.scrollHeight, 'top', false, true);
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelectChange = (length: number) => {
|
onSelectChange = (length: number) => {
|
||||||
|
@ -71,19 +71,25 @@ export default class AppForwardTab implements SliderTab {
|
|||||||
this.cleanup();
|
this.cleanup();
|
||||||
this.mids = ids;
|
this.mids = ids;
|
||||||
|
|
||||||
this.selector = new AppSelectPeers(this.container, (length) => {
|
this.selector = new AppSelectPeers({
|
||||||
this.sendBtn.classList.toggle('is-visible', !!length);
|
appendTo: this.container,
|
||||||
}, ['dialogs', 'contacts'], () => {
|
onChange: (length) => {
|
||||||
//console.log('forward rendered:', this.container.querySelector('.selector ul').childElementCount);
|
this.sendBtn.classList.toggle('is-visible', !!length);
|
||||||
|
},
|
||||||
// !!!!!!!!!! UNCOMMENT BELOW IF NEED TO USE THIS CLASS
|
peerType: ['dialogs', 'contacts'],
|
||||||
////////////////////////////////////////appSidebarRight.selectTab(AppSidebarRight.SLIDERITEMSIDS.forward);
|
onFirstRender: () => {
|
||||||
appSidebarRight.toggleSidebar(true).then(() => {
|
//console.log('forward rendered:', this.container.querySelector('.selector ul').childElementCount);
|
||||||
if(this.selector) {
|
|
||||||
this.selector.checkForTriggers();
|
// !!!!!!!!!! UNCOMMENT BELOW IF NEED TO USE THIS CLASS
|
||||||
}
|
////////////////////////////////////////appSidebarRight.selectTab(AppSidebarRight.SLIDERITEMSIDS.forward);
|
||||||
});
|
appSidebarRight.toggleSidebar(true).then(() => {
|
||||||
document.body.classList.add('is-forward-active');
|
if(this.selector) {
|
||||||
}, null, 'send');
|
this.selector.checkForTriggers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.body.classList.add('is-forward-active');
|
||||||
|
},
|
||||||
|
chatRightsAction: 'send'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
52
src/helpers/animation.ts
Normal file
52
src/helpers/animation.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// * Jolly Cobra's animation.ts
|
||||||
|
|
||||||
|
import { fastRaf } from './schedulers';
|
||||||
|
import { CancellablePromise, deferredPromise } from './cancellablePromise';
|
||||||
|
|
||||||
|
interface AnimationInstance {
|
||||||
|
isCancelled: boolean;
|
||||||
|
deferred: CancellablePromise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnimationInstanceKey = any;
|
||||||
|
const instances: Map<AnimationInstanceKey, AnimationInstance> = new Map();
|
||||||
|
|
||||||
|
export function getAnimationInstance(key: AnimationInstanceKey) {
|
||||||
|
return instances.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cancelAnimationByKey(key: AnimationInstanceKey) {
|
||||||
|
const instance = getAnimationInstance(key);
|
||||||
|
if(instance) {
|
||||||
|
instance.isCancelled = true;
|
||||||
|
instances.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function animateSingle(tick: Function, key: AnimationInstanceKey, instance?: AnimationInstance) {
|
||||||
|
if(!instance) {
|
||||||
|
cancelAnimationByKey(key);
|
||||||
|
instance = { isCancelled: false, deferred: deferredPromise<void>() };
|
||||||
|
instances.set(key, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
fastRaf(() => {
|
||||||
|
if(instance.isCancelled) return;
|
||||||
|
|
||||||
|
if(tick()) {
|
||||||
|
animateSingle(tick, key, instance);
|
||||||
|
} else {
|
||||||
|
instance.deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return instance.deferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function animate(tick: Function) {
|
||||||
|
fastRaf(() => {
|
||||||
|
if(tick()) {
|
||||||
|
animate(tick);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
149
src/helpers/fastSmoothScroll.ts
Normal file
149
src/helpers/fastSmoothScroll.ts
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
// * Jolly Cobra's fastSmoothScroll slightly patched
|
||||||
|
|
||||||
|
import { dispatchHeavyAnimationEvent } from '../hooks/useHeavyAnimationCheck';
|
||||||
|
import { fastRaf } from './schedulers';
|
||||||
|
import { animateSingle, cancelAnimationByKey } from './animation';
|
||||||
|
import rootScope from '../lib/rootScope';
|
||||||
|
|
||||||
|
const MAX_DISTANCE = 1500;
|
||||||
|
const MIN_JS_DURATION = 250;
|
||||||
|
const MAX_JS_DURATION = 600;
|
||||||
|
|
||||||
|
export enum FocusDirection {
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
Static,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function fastSmoothScroll(
|
||||||
|
container: HTMLElement,
|
||||||
|
element: HTMLElement,
|
||||||
|
position: ScrollLogicalPosition,
|
||||||
|
margin = 0,
|
||||||
|
maxDistance = MAX_DISTANCE,
|
||||||
|
forceDirection?: FocusDirection,
|
||||||
|
forceDuration?: number,
|
||||||
|
axis: 'x' | 'y' = 'y'
|
||||||
|
) {
|
||||||
|
//return;
|
||||||
|
|
||||||
|
if(!rootScope.settings.animationsEnabled) {
|
||||||
|
forceDirection = FocusDirection.Static;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(forceDirection === FocusDirection.Static) {
|
||||||
|
forceDuration = 0;
|
||||||
|
return scrollWithJs(container, element, position, margin, forceDuration, axis);
|
||||||
|
/* return Promise.resolve();
|
||||||
|
|
||||||
|
element.scrollIntoView({ block: position });
|
||||||
|
|
||||||
|
cancelAnimationByKey(container);
|
||||||
|
return Promise.resolve(); */
|
||||||
|
}
|
||||||
|
|
||||||
|
if(axis === 'y') {
|
||||||
|
const elementRect = element.getBoundingClientRect();
|
||||||
|
const containerRect = container.getBoundingClientRect();
|
||||||
|
|
||||||
|
const offsetTop = elementRect.top - containerRect.top;
|
||||||
|
if(forceDirection === undefined) {
|
||||||
|
if(offsetTop < -maxDistance) {
|
||||||
|
container.scrollTop += (offsetTop + maxDistance);
|
||||||
|
} else if(offsetTop > maxDistance) {
|
||||||
|
container.scrollTop += (offsetTop - maxDistance);
|
||||||
|
}
|
||||||
|
} else if(forceDirection === FocusDirection.Up) { // * not tested yet
|
||||||
|
container.scrollTop = offsetTop + container.scrollTop + maxDistance;
|
||||||
|
} else if(forceDirection === FocusDirection.Down) { // * not tested yet
|
||||||
|
container.scrollTop = Math.max(0, offsetTop + container.scrollTop - maxDistance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const promise = new Promise((resolve) => {
|
||||||
|
fastRaf(() => {
|
||||||
|
scrollWithJs(container, element, position, margin, forceDuration, axis)
|
||||||
|
.then(resolve);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return dispatchHeavyAnimationEvent(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollWithJs(
|
||||||
|
container: HTMLElement, element: HTMLElement, position: ScrollLogicalPosition, margin = 0, forceDuration?: number, axis: 'x' | 'y' = 'y'
|
||||||
|
) {
|
||||||
|
const rectStartKey = axis === 'y' ? 'top' : 'left';
|
||||||
|
const rectEndKey = axis === 'y' ? 'bottom' : 'right';
|
||||||
|
const sizeKey = axis === 'y' ? 'height' : 'width';
|
||||||
|
const scrollSizeKey = axis === 'y' ? 'scrollHeight' : 'scrollWidth';
|
||||||
|
const scrollPositionKey = axis === 'y' ? 'scrollTop' : 'scrollLeft';
|
||||||
|
|
||||||
|
//const { offsetTop: elementTop, offsetHeight: elementHeight } = element;
|
||||||
|
const elementRect = element.getBoundingClientRect();
|
||||||
|
const containerRect = container.getBoundingClientRect();
|
||||||
|
|
||||||
|
const elementPosition = elementRect[rectStartKey] - containerRect[rectStartKey];
|
||||||
|
const elementSize = element[scrollSizeKey]; // margin is exclusive in DOMRect
|
||||||
|
|
||||||
|
const containerSize = containerRect[sizeKey];
|
||||||
|
|
||||||
|
const scrollPosition = container[scrollPositionKey];
|
||||||
|
const scrollSize = container[scrollSizeKey];
|
||||||
|
|
||||||
|
let path!: number;
|
||||||
|
|
||||||
|
switch(position) {
|
||||||
|
case 'start':
|
||||||
|
path = (elementPosition - margin) - scrollPosition;
|
||||||
|
break;
|
||||||
|
case 'end':
|
||||||
|
//path = (elementTop + elementHeight + margin) - containerHeight;
|
||||||
|
path = elementRect[rectEndKey] + (elementSize - elementRect[sizeKey]) - containerRect[rectEndKey];
|
||||||
|
break;
|
||||||
|
// 'nearest' is not supported yet
|
||||||
|
case 'nearest':
|
||||||
|
case 'center':
|
||||||
|
path = elementSize < containerSize
|
||||||
|
? (elementPosition + elementSize / 2) - (containerSize / 2)
|
||||||
|
: elementPosition - margin;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log('scrollWithJs: will scroll path:', path, element);
|
||||||
|
|
||||||
|
if(path < 0) {
|
||||||
|
const remainingPath = -scrollPosition;
|
||||||
|
path = Math.max(path, remainingPath);
|
||||||
|
} else if(path > 0) {
|
||||||
|
const remainingPath = scrollSize - (scrollPosition + containerSize);
|
||||||
|
path = Math.min(path, remainingPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = container[scrollPositionKey] + path;
|
||||||
|
const duration = forceDuration ?? (
|
||||||
|
MIN_JS_DURATION + (Math.abs(path) / MAX_DISTANCE) * (MAX_JS_DURATION - MIN_JS_DURATION)
|
||||||
|
);
|
||||||
|
const startAt = Date.now();
|
||||||
|
|
||||||
|
const tick = () => {
|
||||||
|
const t = duration ? Math.min((Date.now() - startAt) / duration, 1) : 1;
|
||||||
|
|
||||||
|
const currentPath = path * (1 - transition(t));
|
||||||
|
container[scrollPositionKey] = Math.round(target - currentPath);
|
||||||
|
|
||||||
|
return t < 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!duration) {
|
||||||
|
cancelAnimationByKey(container);
|
||||||
|
tick();
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return animateSingle(tick, container);
|
||||||
|
}
|
||||||
|
|
||||||
|
function transition(t: number) {
|
||||||
|
return 1 - ((1 - t) ** 3.5);
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
// * Jolly Cobra's schedulers
|
// * Jolly Cobra's schedulers
|
||||||
import { AnyToVoidFunction } from "../types";
|
import { AnyToVoidFunction, NoneToVoidFunction } from "../types";
|
||||||
|
|
||||||
//type Scheduler = typeof requestAnimationFrame | typeof onTickEnd | typeof runNow;
|
//type Scheduler = typeof requestAnimationFrame | typeof onTickEnd | typeof runNow;
|
||||||
|
|
||||||
@ -109,9 +109,9 @@ export const pause = (ms: number) => new Promise((resolve) => {
|
|||||||
setTimeout(resolve, ms);
|
setTimeout(resolve, ms);
|
||||||
});
|
});
|
||||||
|
|
||||||
/* let fastRafCallbacks: NoneToVoidFunction[] | undefined;
|
let fastRafCallbacks: NoneToVoidFunction[] | undefined;
|
||||||
export function fastRaf(callback: NoneToVoidFunction) {
|
export function fastRaf(callback: NoneToVoidFunction) {
|
||||||
if (!fastRafCallbacks) {
|
if(!fastRafCallbacks) {
|
||||||
fastRafCallbacks = [callback];
|
fastRafCallbacks = [callback];
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
@ -122,4 +122,4 @@ export function fastRaf(callback: NoneToVoidFunction) {
|
|||||||
} else {
|
} else {
|
||||||
fastRafCallbacks.push(callback);
|
fastRafCallbacks.push(callback);
|
||||||
}
|
}
|
||||||
} */
|
}
|
||||||
|
59
src/hooks/useHeavyAnimationCheck.ts
Normal file
59
src/hooks/useHeavyAnimationCheck.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// * Jolly Cobra's useHeavyAnimationCheck.ts
|
||||||
|
|
||||||
|
//import { useEffect } from '../lib/teact/teact';
|
||||||
|
import { AnyToVoidFunction } from '../types';
|
||||||
|
import ListenerSetter from '../helpers/listenerSetter';
|
||||||
|
import { CancellablePromise, deferredPromise } from '../helpers/cancellablePromise';
|
||||||
|
|
||||||
|
const ANIMATION_START_EVENT = 'event-heavy-animation-start';
|
||||||
|
const ANIMATION_END_EVENT = 'event-heavy-animation-end';
|
||||||
|
|
||||||
|
let isAnimating = false;
|
||||||
|
let heavyAnimationPromise: CancellablePromise<void> = Promise.resolve();
|
||||||
|
let lastAnimationPromise: Promise<any>;
|
||||||
|
|
||||||
|
export const dispatchHeavyAnimationEvent = (promise: Promise<any>) => {
|
||||||
|
if(!isAnimating) {
|
||||||
|
heavyAnimationPromise = deferredPromise<void>();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.dispatchEvent(new Event(ANIMATION_START_EVENT));
|
||||||
|
isAnimating = true;
|
||||||
|
lastAnimationPromise = promise;
|
||||||
|
|
||||||
|
promise.then(() => {
|
||||||
|
if(lastAnimationPromise !== promise) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isAnimating = false;
|
||||||
|
document.dispatchEvent(new Event(ANIMATION_END_EVENT));
|
||||||
|
heavyAnimationPromise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
return heavyAnimationPromise;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getHeavyAnimationPromise = () => heavyAnimationPromise;
|
||||||
|
|
||||||
|
export default (
|
||||||
|
handleAnimationStart: AnyToVoidFunction,
|
||||||
|
handleAnimationEnd: AnyToVoidFunction,
|
||||||
|
listenerSetter?: ListenerSetter
|
||||||
|
) => {
|
||||||
|
//useEffect(() => {
|
||||||
|
if(isAnimating) {
|
||||||
|
handleAnimationStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
const add = listenerSetter ? listenerSetter.add.bind(listenerSetter, document) : document.addEventListener.bind(document);
|
||||||
|
const remove = listenerSetter ? listenerSetter.removeManual.bind(listenerSetter, document) : document.removeEventListener.bind(document);
|
||||||
|
add(ANIMATION_START_EVENT, handleAnimationStart);
|
||||||
|
add(ANIMATION_END_EVENT, handleAnimationEnd);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
remove(ANIMATION_END_EVENT, handleAnimationEnd);
|
||||||
|
remove(ANIMATION_START_EVENT, handleAnimationStart);
|
||||||
|
};
|
||||||
|
//}, [handleAnimationEnd, handleAnimationStart]);
|
||||||
|
};
|
@ -29,6 +29,7 @@ import SetTransition from '../../components/singleTransition';
|
|||||||
import ChatDragAndDrop from '../../components/chat/dragAndDrop';
|
import ChatDragAndDrop from '../../components/chat/dragAndDrop';
|
||||||
import { debounce } from '../../helpers/schedulers';
|
import { debounce } from '../../helpers/schedulers';
|
||||||
import lottieLoader from '../lottieLoader';
|
import lottieLoader from '../lottieLoader';
|
||||||
|
import useHeavyAnimationCheck from '../../hooks/useHeavyAnimationCheck';
|
||||||
|
|
||||||
//console.log('appImManager included33!');
|
//console.log('appImManager included33!');
|
||||||
|
|
||||||
@ -140,6 +141,14 @@ export class AppImManager {
|
|||||||
|
|
||||||
this.setSettings();
|
this.setSettings();
|
||||||
rootScope.on('settings_updated', () => this.setSettings());
|
rootScope.on('settings_updated', () => this.setSettings());
|
||||||
|
|
||||||
|
useHeavyAnimationCheck(() => {
|
||||||
|
animationIntersector.setOnlyOnePlayableGroup('lock');
|
||||||
|
animationIntersector.checkAnimations(true);
|
||||||
|
}, () => {
|
||||||
|
animationIntersector.setOnlyOnePlayableGroup('');
|
||||||
|
animationIntersector.checkAnimations(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private setSettings() {
|
private setSettings() {
|
||||||
|
@ -58,10 +58,9 @@ $bubble-margin: .25rem;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bubble {
|
.bubble {
|
||||||
padding-top: $bubble-margin;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
margin: 0 auto;
|
margin: 0 auto $bubble-margin;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
&.is-highlighted, &.is-selected, /* .bubbles.is-selecting */ & {
|
&.is-highlighted, &.is-selected, /* .bubbles.is-selecting */ & {
|
||||||
@ -70,7 +69,7 @@ $bubble-margin: .25rem;
|
|||||||
left: -50%;
|
left: -50%;
|
||||||
/* top: 0;
|
/* top: 0;
|
||||||
bottom: 0; */
|
bottom: 0; */
|
||||||
top: #{$bubble-margin / 2};
|
top: -#{$bubble-margin / 2};
|
||||||
bottom: -#{$bubble-margin / 2};
|
bottom: -#{$bubble-margin / 2};
|
||||||
content: " ";
|
content: " ";
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@ -369,7 +368,11 @@ $bubble-margin: .25rem;
|
|||||||
} */
|
} */
|
||||||
|
|
||||||
&.is-group-last {
|
&.is-group-last {
|
||||||
padding-bottom: $bubble-margin;
|
margin-bottom: #{$bubble-margin * 2};
|
||||||
|
|
||||||
|
&:before, &:after {
|
||||||
|
bottom: -#{$bubble-margin};
|
||||||
|
}
|
||||||
|
|
||||||
> .bubble-select-checkbox {
|
> .bubble-select-checkbox {
|
||||||
bottom: 8px;
|
bottom: 8px;
|
||||||
@ -382,6 +385,12 @@ $bubble-margin: .25rem;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.is-group-first {
|
||||||
|
&:before, &:after {
|
||||||
|
top: -#{$bubble-margin};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:not(.forwarded) {
|
&:not(.forwarded) {
|
||||||
&:not(.is-group-first) {
|
&:not(.is-group-first) {
|
||||||
.bubble__container > .name, .document-wrapper > .name {
|
.bubble__container > .name, .document-wrapper > .name {
|
||||||
@ -1141,7 +1150,7 @@ $bubble-margin: .25rem;
|
|||||||
|
|
||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
.document-selection {
|
.document-selection {
|
||||||
top: -2px; // * padding inner + half padding outer
|
top: -#{$bubble-margin / 2}; // * padding inner + half padding outer
|
||||||
}
|
}
|
||||||
|
|
||||||
.document-wrapper {
|
.document-wrapper {
|
||||||
@ -1153,7 +1162,7 @@ $bubble-margin: .25rem;
|
|||||||
|
|
||||||
&:last-of-type {
|
&:last-of-type {
|
||||||
.document-selection {
|
.document-selection {
|
||||||
bottom: -2px;
|
bottom: -#{$bubble-margin / 2};
|
||||||
}
|
}
|
||||||
|
|
||||||
.document-wrapper {
|
.document-wrapper {
|
||||||
@ -1175,7 +1184,7 @@ $bubble-margin: .25rem;
|
|||||||
&.is-group-last .document-container {
|
&.is-group-last .document-container {
|
||||||
&:last-of-type {
|
&:last-of-type {
|
||||||
.document-selection {
|
.document-selection {
|
||||||
bottom: -6px;
|
bottom: -$bubble-margin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1264,8 +1273,10 @@ $bubble-margin: .25rem;
|
|||||||
bottom: 55px;
|
bottom: 55px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.sticker .message {
|
&.sticker, &.with-replies.round, &.emoji-big {
|
||||||
bottom: 0;
|
.message {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1568,7 +1579,7 @@ $bubble-margin: .25rem;
|
|||||||
// * fix scroll with only 1 bubble
|
// * fix scroll with only 1 bubble
|
||||||
.bubbles-date-group:last-of-type {
|
.bubbles-date-group:last-of-type {
|
||||||
.bubble:last-of-type {
|
.bubble:last-of-type {
|
||||||
margin-bottom: 2px;
|
margin-bottom: $bubble-margin;
|
||||||
/* &:after, .document-container:last-of-type .document-selection {
|
/* &:after, .document-container:last-of-type .document-selection {
|
||||||
bottom: 0 !important;
|
bottom: 0 !important;
|
||||||
} */
|
} */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user