Bot description bubble
This commit is contained in:
parent
25b9de4e11
commit
394db0b49e
|
@ -10,9 +10,9 @@ import type { AppStickersManager } from "../../lib/appManagers/appStickersManage
|
|||
import type { AppUsersManager } from "../../lib/appManagers/appUsersManager";
|
||||
import type { AppInlineBotsManager } from "../../lib/appManagers/appInlineBotsManager";
|
||||
import type { AppPhotosManager } from "../../lib/appManagers/appPhotosManager";
|
||||
import type { AppDocsManager, MyDocument } from "../../lib/appManagers/appDocsManager";
|
||||
import type { MyDocument } from "../../lib/appManagers/appDocsManager";
|
||||
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
|
||||
import type stateStorage from '../../lib/stateStorage';
|
||||
import type { AppProfileManager } from "../../lib/appManagers/appProfileManager";
|
||||
import type Chat from "./chat";
|
||||
import { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager";
|
||||
import { getObjectKeysAndSort } from "../../helpers/object";
|
||||
|
@ -38,7 +38,6 @@ import { ripple } from "../ripple";
|
|||
import { wrapAlbum, wrapPhoto, wrapVideo, wrapDocument, wrapSticker, wrapPoll, wrapGroupedDocuments } from "../wrappers";
|
||||
import { MessageRender } from "./messageRender";
|
||||
import LazyLoadQueue from "../lazyLoadQueue";
|
||||
import { AppChatsManager } from "../../lib/appManagers/appChatsManager";
|
||||
import ListenerSetter from "../../helpers/listenerSetter";
|
||||
import PollElement from "../poll";
|
||||
import AudioElement from "../audio";
|
||||
|
@ -147,13 +146,16 @@ export default class ChatBubbles {
|
|||
[_ in MessageEntity['_']]: boolean
|
||||
}> = {};
|
||||
|
||||
private onAnimateLadder: () => void;
|
||||
|
||||
constructor(private chat: Chat,
|
||||
private appMessagesManager: AppMessagesManager,
|
||||
private appStickersManager: AppStickersManager,
|
||||
private appUsersManager: AppUsersManager,
|
||||
private appInlineBotsManager: AppInlineBotsManager,
|
||||
private appPhotosManager: AppPhotosManager,
|
||||
private appPeersManager: AppPeersManager
|
||||
private appPeersManager: AppPeersManager,
|
||||
private appProfileManager: AppProfileManager
|
||||
) {
|
||||
//this.chat.log.error('Bubbles construction');
|
||||
|
||||
|
@ -429,7 +431,7 @@ export default class ChatBubbles {
|
|||
}
|
||||
|
||||
const bubble = (e.target as HTMLElement).classList.contains('bubble') ? e.target as HTMLElement : null;
|
||||
if(bubble) {
|
||||
if(bubble && !bubble.classList.contains('bubble-first')) {
|
||||
const mid = +bubble.dataset.mid
|
||||
this.chat.input.initMessageReply(mid);
|
||||
}
|
||||
|
@ -2825,8 +2827,12 @@ export default class ChatBubbles {
|
|||
}
|
||||
|
||||
while(history.length) {
|
||||
let message = this.chat.getMessage(method());
|
||||
this.renderMessage(message, reverse, true);
|
||||
const message = this.chat.getMessage(method());
|
||||
if(message.id > 0) {
|
||||
this.renderMessage(message, reverse, true);
|
||||
} else {
|
||||
this.processLocalMessageRender(message);
|
||||
}
|
||||
}
|
||||
|
||||
(this.messagesQueuePromise || Promise.resolve())
|
||||
|
@ -2918,6 +2924,147 @@ export default class ChatBubbles {
|
|||
}
|
||||
}
|
||||
|
||||
private animateAsLadder(additionMsgId: number, additionMsgIds: number[], isAdditionRender: boolean, backLimit: number, maxId: number) {
|
||||
if(!Object.keys(this.bubbles).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.onAnimateLadder) {
|
||||
this.onAnimateLadder();
|
||||
}
|
||||
|
||||
let sortedMids = getObjectKeysAndSort(this.bubbles, 'desc');
|
||||
|
||||
if(isAdditionRender && additionMsgIds.length) {
|
||||
sortedMids = sortedMids.filter(mid => !additionMsgIds.includes(mid));
|
||||
}
|
||||
|
||||
let targetMid: number;
|
||||
if(backLimit) {
|
||||
targetMid = maxId || Math.max(...sortedMids); // * on discussion enter
|
||||
} else {
|
||||
if(additionMsgId) {
|
||||
targetMid = additionMsgId;
|
||||
} else { // * if maxId === 0
|
||||
targetMid = Math.max(...sortedMids);
|
||||
}
|
||||
}
|
||||
|
||||
const topIds = sortedMids.slice(sortedMids.findIndex(mid => targetMid > mid));
|
||||
const middleIds = isAdditionRender ? [] : [targetMid];
|
||||
const bottomIds = isAdditionRender ? [] : sortedMids.slice(0, sortedMids.findIndex(mid => targetMid >= mid)).reverse();
|
||||
|
||||
if(DEBUG) {
|
||||
this.log('getHistory: targeting mid:', targetMid, maxId, additionMsgId,
|
||||
topIds.map(m => this.appMessagesManager.getServerMessageId(m)),
|
||||
bottomIds.map(m => this.appMessagesManager.getServerMessageId(m)));
|
||||
}
|
||||
|
||||
const setBubbles: HTMLElement[] = [];
|
||||
|
||||
this.chatInner.classList.add('zoom-fading');
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
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];
|
||||
|
||||
fastRaf(() => {
|
||||
setBubbles.forEach(contentWrapper => {
|
||||
contentWrapper.classList.remove('zoom-fade');
|
||||
});
|
||||
});
|
||||
|
||||
let promise: Promise<any>;
|
||||
if(topIds.length || middleIds.length || bottomIds.length) {
|
||||
promise = Promise.all(promises);
|
||||
|
||||
dispatchHeavyAnimationEvent(promise, Math.max(...delays) + 200) // * 200 - transition time
|
||||
.then(() => {
|
||||
fastRaf(() => {
|
||||
setBubbles.forEach(contentWrapper => {
|
||||
contentWrapper.style.transitionDelay = '';
|
||||
});
|
||||
|
||||
this.chatInner.classList.remove('zoom-fading');
|
||||
});
|
||||
|
||||
// ! в хроме, каким-то образом из-за zoom-fade класса начинает прыгать скролл при подгрузке сообщений вверх,
|
||||
// ! т.е. скролл не ставится, так же, как в сафари при translateZ на блок выше scrollable
|
||||
if(!isSafari) {
|
||||
this.needReflowScroll = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
private processLocalMessageRender(message: any) {
|
||||
const bubble = this.renderMessage(message, false, false, undefined, false);
|
||||
bubble.classList.add('bubble-first', 'is-group-last', 'is-group-first');
|
||||
bubble.classList.remove('can-have-tail', 'is-in');
|
||||
|
||||
const messageDiv = bubble.querySelector('.message');
|
||||
const b = document.createElement('b');
|
||||
b.append(i18n('BotInfoTitle'));
|
||||
messageDiv.prepend(b, '\n\n');
|
||||
|
||||
if(this.messagesQueueOnRenderAdditional) {
|
||||
this.onAnimateLadder = () => {
|
||||
this.chatInner.prepend(bubble);
|
||||
this.onAnimateLadder = undefined;
|
||||
};
|
||||
} else {
|
||||
this.chatInner.prepend(bubble);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and render history
|
||||
* @param maxId max message id
|
||||
|
@ -3002,28 +3149,50 @@ export default class ChatBubbles {
|
|||
|
||||
this.isFirstLoad = false;
|
||||
|
||||
const processResult = (historyResult: typeof result) => {
|
||||
if(this.chat.type === 'discussion' && 'offsetIdOffset' in historyResult) {
|
||||
//this.log('discussion got history', loadCount, backLimit, historyResult, isTopEnd);
|
||||
|
||||
// * inject discussion start
|
||||
if(historyResult.history.isEnd(SliceEnd.Top)) {
|
||||
const processResult = async(historyResult: typeof result) => {
|
||||
if('offsetIdOffset' in historyResult && historyResult.history.isEnd(SliceEnd.Top)) {
|
||||
if(this.chat.type === 'discussion') { // * inject discussion start
|
||||
//this.log('discussion got history', loadCount, backLimit, historyResult, isTopEnd);
|
||||
const serviceStartMessageId = this.appMessagesManager.threadsServiceMessagesIdsStorage[this.peerId + '_' + this.chat.threadId];
|
||||
if(serviceStartMessageId) historyResult.history.push(serviceStartMessageId);
|
||||
historyResult.history.push(...this.chat.getMidsByMid(this.chat.threadId).reverse());
|
||||
this.scrollable.loadedAll.top = true;
|
||||
} else if(this.appUsersManager.isBot(this.peerId)) {
|
||||
this.log('inject bot description');
|
||||
|
||||
await this.appProfileManager.getProfile(this.peerId).then(userFull => {
|
||||
if(!userFull.bot_info?.description) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offset = this.appMessagesManager.generateMessageId(0);
|
||||
const message: Message.message = {
|
||||
_: 'message',
|
||||
date: 0,
|
||||
id: -(this.peerId + offset),
|
||||
message: userFull.bot_info.description,
|
||||
peer_id: this.appPeersManager.getOutputPeer(this.peerId),
|
||||
pFlags: {
|
||||
bot_description: true
|
||||
}
|
||||
};
|
||||
|
||||
this.appMessagesManager.saveMessages([message]);
|
||||
this.processLocalMessageRender(message);
|
||||
});
|
||||
}
|
||||
|
||||
this.scrollable.loadedAll.top = true;
|
||||
}
|
||||
};
|
||||
|
||||
const sup = (result: HistoryResult) => {
|
||||
const sup = async(result: HistoryResult) => {
|
||||
/* if(maxId && result.history?.length) {
|
||||
if(this.bubbles[maxId]) {
|
||||
result.history.findAndSplice(mid => mid === maxId);
|
||||
}
|
||||
} */
|
||||
|
||||
processResult(result);
|
||||
await processResult(result);
|
||||
|
||||
////console.timeEnd('render history total');
|
||||
|
||||
|
@ -3081,119 +3250,8 @@ export default class ChatBubbles {
|
|||
if(--times) return;
|
||||
|
||||
this.messagesQueueOnRenderAdditional = undefined;
|
||||
if(!Object.keys(this.bubbles).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let sortedMids = getObjectKeysAndSort(this.bubbles, 'desc');
|
||||
|
||||
if(isAdditionRender && additionMsgIds.length) {
|
||||
sortedMids = sortedMids.filter(mid => !additionMsgIds.includes(mid));
|
||||
}
|
||||
|
||||
let targetMid: number;
|
||||
if(backLimit) {
|
||||
targetMid = maxId || Math.max(...sortedMids); // * on discussion enter
|
||||
} else {
|
||||
if(additionMsgId) {
|
||||
targetMid = additionMsgId;
|
||||
} else { // * if maxId === 0
|
||||
targetMid = Math.max(...sortedMids);
|
||||
}
|
||||
}
|
||||
|
||||
const topIds = sortedMids.slice(sortedMids.findIndex(mid => targetMid > mid));
|
||||
const middleIds = isAdditionRender ? [] : [targetMid];
|
||||
const bottomIds = isAdditionRender ? [] : sortedMids.slice(0, sortedMids.findIndex(mid => targetMid >= mid)).reverse();
|
||||
|
||||
if(DEBUG) {
|
||||
this.log('getHistory: targeting mid:', targetMid, maxId, additionMsgId,
|
||||
topIds.map(m => this.appMessagesManager.getServerMessageId(m)),
|
||||
bottomIds.map(m => this.appMessagesManager.getServerMessageId(m)));
|
||||
}
|
||||
|
||||
const setBubbles: HTMLElement[] = [];
|
||||
|
||||
this.chatInner.classList.add('zoom-fading');
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
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];
|
||||
|
||||
fastRaf(() => {
|
||||
setBubbles.forEach(contentWrapper => {
|
||||
contentWrapper.classList.remove('zoom-fade');
|
||||
});
|
||||
});
|
||||
|
||||
let promise: Promise<any>;
|
||||
if(topIds.length || middleIds.length || bottomIds.length) {
|
||||
promise = Promise.all(promises);
|
||||
|
||||
dispatchHeavyAnimationEvent(promise, Math.max(...delays) + 200) // * 200 - transition time
|
||||
.then(() => {
|
||||
fastRaf(() => {
|
||||
setBubbles.forEach(contentWrapper => {
|
||||
contentWrapper.style.transitionDelay = '';
|
||||
});
|
||||
|
||||
this.chatInner.classList.remove('zoom-fading');
|
||||
});
|
||||
|
||||
// ! в хроме, каким-то образом из-за zoom-fade класса начинает прыгать скролл при подгрузке сообщений вверх,
|
||||
// ! т.е. скролл не ставится, так же, как в сафари при translateZ на блок выше scrollable
|
||||
if(!isSafari) {
|
||||
this.needReflowScroll = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const promise = this.animateAsLadder(additionMsgId, additionMsgIds, isAdditionRender, backLimit, maxId);
|
||||
(promise || Promise.resolve()).then(() => {
|
||||
setTimeout(() => { // preload messages
|
||||
this.loadMoreHistory(reverse, true);
|
||||
|
|
|
@ -170,7 +170,7 @@ export default class Chat extends EventListenerBase<{
|
|||
this.initPeerId = peerId;
|
||||
|
||||
this.topbar = new ChatTopbar(this, appSidebarRight, this.appMessagesManager, this.appPeersManager, this.appChatsManager, this.appNotificationsManager);
|
||||
this.bubbles = new ChatBubbles(this, this.appMessagesManager, this.appStickersManager, this.appUsersManager, this.appInlineBotsManager, this.appPhotosManager, this.appPeersManager);
|
||||
this.bubbles = new ChatBubbles(this, this.appMessagesManager, this.appStickersManager, this.appUsersManager, this.appInlineBotsManager, this.appPhotosManager, this.appPeersManager, this.appProfileManager);
|
||||
this.input = new ChatInput(this, this.appMessagesManager, this.appDocsManager, this.appChatsManager, this.appPeersManager, this.appWebPagesManager, this.appImManager, this.appDraftsManager, this.serverTimeManager, this.appNotificationsManager, this.appEmojiManager);
|
||||
this.selection = new ChatSelection(this, this.bubbles, this.input, this.appMessagesManager);
|
||||
this.contextMenu = new ChatContextMenu(this.bubbles.bubblesContainer, this, this.appMessagesManager, this.appPeersManager, this.appPollsManager, this.appDocsManager);
|
||||
|
|
|
@ -61,7 +61,7 @@ export default class ChatContextMenu {
|
|||
} catch(e) {}
|
||||
|
||||
// ! context menu click by date bubble (there is no pointer-events)
|
||||
if(!bubble) return;
|
||||
if(!bubble || bubble.classList.contains('bubble-first')) return;
|
||||
|
||||
if(e instanceof MouseEvent || e.hasOwnProperty('preventDefault')) (e as any).preventDefault();
|
||||
if(this.element.classList.contains('active')) {
|
||||
|
|
|
@ -523,6 +523,6 @@ export default class ChatSelection {
|
|||
}
|
||||
|
||||
public canSelectBubble(bubble: HTMLElement) {
|
||||
return !bubble.classList.contains('service') && !bubble.classList.contains('is-sending');
|
||||
return !bubble.classList.contains('service') && !bubble.classList.contains('is-sending') && !bubble.classList.contains('bubble-first');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -493,6 +493,7 @@ const lang = {
|
|||
"PrivacyDeleteCloudDrafts": "Delete All Cloud Drafts",
|
||||
"AreYouSureClearDraftsTitle": "Delete cloud drafts",
|
||||
"AreYouSureClearDrafts": "Are you sure you want to delete all cloud drafts?",
|
||||
"BotInfoTitle": "What can this bot do?",
|
||||
|
||||
// * macos
|
||||
"AccountSettings.Filters": "Chat Folders",
|
||||
|
|
1
src/layer.d.ts
vendored
1
src/layer.d.ts
vendored
|
@ -833,6 +833,7 @@ export namespace Message {
|
|||
pinned?: true,
|
||||
unread?: true,
|
||||
is_outgoing?: true,
|
||||
bot_description?: true,
|
||||
}>,
|
||||
id: number,
|
||||
from_id?: Peer,
|
||||
|
|
|
@ -438,14 +438,15 @@ export class AppImManager {
|
|||
//const bubble = chat.bubbles.getBubbleByPoint('top');
|
||||
//if(bubble) {
|
||||
//const top = bubble.getBoundingClientRect().top;
|
||||
const top = chat.bubbles.scrollable.scrollTop;
|
||||
const chatBubbles = chat.bubbles;
|
||||
const top = chatBubbles.scrollable.scrollTop;
|
||||
|
||||
const key = chat.peerId + (chat.threadId ? '_' + chat.threadId : '');
|
||||
|
||||
const chatPositions = stateStorage.getFromCache('chatPositions');
|
||||
if(!(chat.bubbles.scrollable.getDistanceToEnd() <= 16 && chat.bubbles.scrollable.loadedAll.bottom) && Object.keys(chat.bubbles.bubbles).length) {
|
||||
if(!(chatBubbles.scrollable.getDistanceToEnd() <= 16 && chatBubbles.scrollable.loadedAll.bottom) && Object.keys(chatBubbles.bubbles).length) {
|
||||
const position = {
|
||||
mids: getObjectKeysAndSort(chat.bubbles.bubbles, 'desc'),
|
||||
mids: getObjectKeysAndSort(chatBubbles.bubbles, 'desc'),
|
||||
top
|
||||
};
|
||||
|
||||
|
|
|
@ -51,7 +51,8 @@
|
|||
{"name": "unread", "type": "true"},
|
||||
{"name": "is_outgoing", "type": "true"},
|
||||
{"name": "rReply", "type": "string"},
|
||||
{"name": "viaBotId", "type": "number"}
|
||||
{"name": "viaBotId", "type": "number"},
|
||||
{"name": "bot_description", "type": "true"}
|
||||
]
|
||||
}, {
|
||||
"predicate": "messageService",
|
||||
|
|
|
@ -721,6 +721,20 @@ $bubble-margin: .25rem;
|
|||
}
|
||||
}
|
||||
|
||||
&-first {
|
||||
order: -1;
|
||||
margin-top: .5rem;
|
||||
justify-content: center;
|
||||
|
||||
.time {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
&:before, &:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.web {
|
||||
padding-top: 1px;
|
||||
margin: 4px 0 -5px 1px;
|
||||
|
|
Loading…
Reference in New Issue
Block a user