Custom emoji interaction effect
This commit is contained in:
parent
7735e12e36
commit
4e9766a6ac
@ -25,7 +25,7 @@ import {IS_ANDROID, IS_APPLE, IS_MOBILE, IS_SAFARI} from '../../environment/user
|
||||
import I18n, {FormatterArguments, i18n, langPack, LangPackKey, UNSUPPORTED_LANG_PACK_KEY, _i18n} from '../../lib/langPack';
|
||||
import AvatarElement from '../avatar';
|
||||
import ripple from '../ripple';
|
||||
import {wrapAlbum, wrapPhoto, wrapVideo, wrapDocument, wrapSticker, wrapPoll, wrapGroupedDocuments} from '../wrappers';
|
||||
import {wrapAlbum, wrapPhoto, wrapVideo, wrapDocument, wrapSticker, wrapPoll, wrapGroupedDocuments, wrapStickerAnimation} from '../wrappers';
|
||||
import {MessageRender} from './messageRender';
|
||||
import LazyLoadQueue from '../lazyLoadQueue';
|
||||
import ListenerSetter from '../../helpers/listenerSetter';
|
||||
@ -97,7 +97,7 @@ import getPeerId from '../../lib/appManagers/utils/peers/getPeerId';
|
||||
import getServerMessageId from '../../lib/appManagers/utils/messageId/getServerMessageId';
|
||||
import generateMessageId from '../../lib/appManagers/utils/messageId/generateMessageId';
|
||||
import {AppManagers} from '../../lib/appManagers/managers';
|
||||
import {Awaited} from '../../types';
|
||||
import {Awaited, SendMessageEmojiInteractionData} from '../../types';
|
||||
import idleController from '../../helpers/idleController';
|
||||
import overlayCounter from '../../helpers/overlayCounter';
|
||||
import {cancelContextMenuOpening} from '../../helpers/dom/attachContextMenuListener';
|
||||
@ -114,6 +114,11 @@ import isInDOM from '../../helpers/dom/isInDOM';
|
||||
import getStickerEffectThumb from '../../lib/appManagers/utils/stickers/getStickerEffectThumb';
|
||||
import attachStickerViewerListeners from '../stickerViewer';
|
||||
import {makeMediaSize, MediaSize} from '../../helpers/mediaSize';
|
||||
import lottieLoader from '../../lib/rlottie/lottieLoader';
|
||||
import appDownloadManager from '../../lib/appManagers/appDownloadManager';
|
||||
import onMediaLoad from '../../helpers/onMediaLoad';
|
||||
import throttle from '../../helpers/schedulers/throttle';
|
||||
import {onEmojiStickerClick} from '../wrappers/sticker';
|
||||
|
||||
const USE_MEDIA_TAILS = false;
|
||||
const IGNORE_ACTIONS: Set<Message.messageService['action']['_']> = new Set([
|
||||
@ -1553,6 +1558,19 @@ export default class ChatBubbles {
|
||||
return;
|
||||
}
|
||||
|
||||
const stickerEmojiEl = findUpAttribute(target, 'data-sticker-emoji');
|
||||
if(stickerEmojiEl) {
|
||||
onEmojiStickerClick({
|
||||
event: e,
|
||||
container: stickerEmojiEl,
|
||||
managers: this.managers,
|
||||
middleware: this.getMiddleware(),
|
||||
peerId: this.peerId
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const commentsDiv: HTMLElement = findUpClassName(target, 'replies');
|
||||
if(commentsDiv) {
|
||||
const bubbleMid = +bubble.dataset.mid;
|
||||
|
@ -81,6 +81,10 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
|
||||
}) {
|
||||
div = Array.isArray(div) ? div : [div];
|
||||
|
||||
if(isCustomEmoji) {
|
||||
emoji = doc.stickerEmojiRaw;
|
||||
}
|
||||
|
||||
const stickerType = doc.sticker;
|
||||
if(stickerType === 1) {
|
||||
asStatic = true;
|
||||
@ -101,6 +105,10 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
|
||||
|
||||
div.forEach((div) => {
|
||||
div.dataset.docId = '' + doc.id;
|
||||
if(emoji) {
|
||||
div.dataset.stickerEmoji = emoji;
|
||||
}
|
||||
|
||||
div.classList.add('media-sticker-wrapper');
|
||||
});
|
||||
|
||||
@ -162,7 +170,7 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
|
||||
await getCacheContext(fullThumb?.type);
|
||||
}
|
||||
|
||||
const toneIndex = emoji ? getEmojiToneIndex(emoji) : -1;
|
||||
const toneIndex = emoji && !isCustomEmoji ? getEmojiToneIndex(emoji) : -1;
|
||||
const downloaded = cacheContext.downloaded && !needFadeIn;
|
||||
|
||||
const isThumbNeededForType = isAnimated;
|
||||
@ -357,7 +365,7 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
|
||||
|
||||
const animation = await lottieLoader.loadAnimationWorker({
|
||||
container: (div as HTMLElement[])[0],
|
||||
loop: loop && !emoji,
|
||||
loop: loop && (!emoji || isCustomEmoji),
|
||||
autoplay: play,
|
||||
animationData: blob,
|
||||
width,
|
||||
@ -427,109 +435,7 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
|
||||
}, {once: true});
|
||||
|
||||
if(emoji) {
|
||||
const data: SendMessageEmojiInteractionData = {
|
||||
a: [],
|
||||
v: 1
|
||||
};
|
||||
|
||||
let sendInteractionThrottled: () => void;
|
||||
|
||||
managers.appStickersManager.preloadAnimatedEmojiStickerAnimation(emoji);
|
||||
|
||||
const container = (div as HTMLElement[])[0];
|
||||
attachClickEvent(container, async(e) => {
|
||||
cancelEvent(e);
|
||||
const animation = lottieLoader.getAnimation(container);
|
||||
|
||||
if(animation.paused) {
|
||||
const doc = await managers.appStickersManager.getAnimatedEmojiSoundDocument(emoji);
|
||||
if(doc) {
|
||||
const audio = document.createElement('audio');
|
||||
audio.style.display = 'none';
|
||||
container.parentElement.append(audio);
|
||||
|
||||
try {
|
||||
const url = await appDownloadManager.downloadMediaURL({media: doc});
|
||||
|
||||
audio.src = url;
|
||||
audio.play();
|
||||
await onMediaLoad(audio, undefined, true);
|
||||
|
||||
audio.addEventListener('ended', () => {
|
||||
audio.src = '';
|
||||
audio.remove();
|
||||
}, {once: true});
|
||||
} catch(err) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
animation.autoplay = true;
|
||||
animation.restart();
|
||||
}
|
||||
|
||||
const peerId = appImManager.chat.peerId;
|
||||
if(!peerId.isUser()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const doc = await managers.appStickersManager.getAnimatedEmojiSticker(emoji, true);
|
||||
if(!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {animationDiv} = wrapStickerAnimation({
|
||||
doc,
|
||||
middleware,
|
||||
side: isOut ? 'right' : 'left',
|
||||
size: 280,
|
||||
target: container,
|
||||
play: true,
|
||||
withRandomOffset: true
|
||||
});
|
||||
|
||||
if(isOut !== undefined && !isOut) {
|
||||
animationDiv.classList.add('reflect-x');
|
||||
}
|
||||
|
||||
if(!sendInteractionThrottled) {
|
||||
sendInteractionThrottled = throttle(() => {
|
||||
const length = data.a.length;
|
||||
if(!length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstTime = data.a[0].t;
|
||||
|
||||
data.a.forEach((a) => {
|
||||
a.t = (a.t - firstTime) / 1000;
|
||||
});
|
||||
|
||||
const bubble = findUpClassName(container, 'bubble');
|
||||
managers.appMessagesManager.setTyping(appImManager.chat.peerId, {
|
||||
_: 'sendMessageEmojiInteraction',
|
||||
msg_id: getServerMessageId(+bubble.dataset.mid),
|
||||
emoticon: emoji,
|
||||
interaction: {
|
||||
_: 'dataJSON',
|
||||
data: JSON.stringify(data)
|
||||
}
|
||||
}, true);
|
||||
|
||||
data.a.length = 0;
|
||||
}, 1000, false);
|
||||
}
|
||||
|
||||
// using a trick here: simulated event from interlocutor's interaction won't fire ours
|
||||
if(e.isTrusted) {
|
||||
data.a.push({
|
||||
i: 1,
|
||||
t: Date.now()
|
||||
});
|
||||
|
||||
sendInteractionThrottled();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return animation;
|
||||
@ -722,3 +628,109 @@ function attachStickerEffectHandler({container, doc, managers, middleware, isOut
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function onEmojiStickerClick({event, container, managers, peerId, middleware}: {
|
||||
event: Event,
|
||||
container: HTMLElement,
|
||||
managers: AppManagers,
|
||||
peerId: PeerId,
|
||||
middleware: () => boolean
|
||||
}) {
|
||||
if(!peerId.isUser()) {
|
||||
return;
|
||||
}
|
||||
|
||||
cancelEvent(event);
|
||||
|
||||
const bubble = findUpClassName(container, 'bubble');
|
||||
const emoji = container.dataset.stickerEmoji;
|
||||
const data: SendMessageEmojiInteractionData = (container as any).emojiData ??= {
|
||||
a: [],
|
||||
v: 1
|
||||
};
|
||||
|
||||
const sendInteractionThrottled: () => void = (container as any).sendInteractionThrottled = throttle(() => {
|
||||
const length = data.a.length;
|
||||
if(!length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstTime = data.a[0].t;
|
||||
|
||||
data.a.forEach((a) => {
|
||||
a.t = (a.t - firstTime) / 1000;
|
||||
});
|
||||
|
||||
const bubble = findUpClassName(container, 'bubble');
|
||||
managers.appMessagesManager.setTyping(appImManager.chat.peerId, {
|
||||
_: 'sendMessageEmojiInteraction',
|
||||
msg_id: getServerMessageId(+bubble.dataset.mid),
|
||||
emoticon: emoji,
|
||||
interaction: {
|
||||
_: 'dataJSON',
|
||||
data: JSON.stringify(data)
|
||||
}
|
||||
}, true);
|
||||
|
||||
data.a.length = 0;
|
||||
}, 1000, false);
|
||||
|
||||
const animation = lottieLoader.getAnimation(container);
|
||||
if(animation.paused) {
|
||||
const doc = await managers.appStickersManager.getAnimatedEmojiSoundDocument(emoji);
|
||||
if(doc) {
|
||||
const audio = document.createElement('audio');
|
||||
audio.style.display = 'none';
|
||||
container.parentElement.append(audio);
|
||||
|
||||
try {
|
||||
const url = await appDownloadManager.downloadMediaURL({media: doc});
|
||||
|
||||
audio.src = url;
|
||||
audio.play();
|
||||
await onMediaLoad(audio, undefined, true);
|
||||
|
||||
audio.addEventListener('ended', () => {
|
||||
audio.src = '';
|
||||
audio.remove();
|
||||
}, {once: true});
|
||||
} catch(err) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
animation.autoplay = true;
|
||||
animation.restart();
|
||||
}
|
||||
|
||||
const doc = await managers.appStickersManager.getAnimatedEmojiSticker(emoji, true);
|
||||
if(!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isOut = bubble ? bubble.classList.contains('is-out') : undefined;
|
||||
const {animationDiv} = wrapStickerAnimation({
|
||||
doc,
|
||||
middleware,
|
||||
side: isOut ? 'right' : 'left',
|
||||
size: 280,
|
||||
target: container,
|
||||
play: true,
|
||||
withRandomOffset: true
|
||||
});
|
||||
|
||||
if(isOut !== undefined && !isOut) {
|
||||
animationDiv.classList.add('reflect-x');
|
||||
}
|
||||
|
||||
// using a trick here: simulated event from interlocutor's interaction won't fire ours
|
||||
if(event.isTrusted) {
|
||||
data.a.push({
|
||||
i: 1,
|
||||
t: Date.now()
|
||||
});
|
||||
|
||||
sendInteractionThrottled();
|
||||
}
|
||||
// });
|
||||
}
|
||||
|
@ -282,7 +282,7 @@ export class AppImManager extends EventListenerBase<{
|
||||
if(typing?.action?._ === 'sendMessageEmojiInteraction') {
|
||||
const action = typing.action;
|
||||
const bubble = chat.bubbles.bubbles[generateMessageId(typing.action.msg_id)];
|
||||
if(bubble && bubble.classList.contains('emoji-big') && bubble.classList.contains('sticker') && getVisibleRect(bubble, chat.bubbles.scrollable.container)) {
|
||||
if(bubble && bubble.classList.contains('emoji-big') && getVisibleRect(bubble, chat.bubbles.scrollable.container)) {
|
||||
const stickerWrapper: HTMLElement = bubble.querySelector('.media-sticker-wrapper:not(.bubble-hover-reaction-sticker):not(.reaction-sticker)');
|
||||
|
||||
const data: SendMessageEmojiInteractionData = JSON.parse(action.interaction.data);
|
||||
|
@ -23,7 +23,7 @@ import IS_CUSTOM_EMOJI_SUPPORTED from '../../environment/customEmojiSupport';
|
||||
import rootScope from '../rootScope';
|
||||
import mediaSizes from '../../helpers/mediaSizes';
|
||||
import {wrapSticker} from '../../components/wrappers';
|
||||
import RLottiePlayer from '../rlottie/rlottiePlayer';
|
||||
import RLottiePlayer, {getLottiePixelRatio} from '../rlottie/rlottiePlayer';
|
||||
import animationIntersector from '../../components/animationIntersector';
|
||||
import type {MyDocument} from '../appManagers/appDocsManager';
|
||||
import LazyLoadQueue from '../../components/lazyLoadQueue';
|
||||
@ -196,7 +196,7 @@ export class CustomEmojiRendererElement extends HTMLElement {
|
||||
|
||||
public setDimensionsFromRect(rect: DOMRect) {
|
||||
const {canvas} = this;
|
||||
const dpr = canvas.dpr ??= Math.min(2, window.devicePixelRatio);
|
||||
const dpr = canvas.dpr ??= getLottiePixelRatio(rect.width, rect.height);
|
||||
canvas.width = Math.round(rect.width * dpr);
|
||||
canvas.height = Math.round(rect.height * dpr);
|
||||
}
|
||||
|
@ -114,6 +114,21 @@ const cache = new RLottieCache();
|
||||
|
||||
export type RLottieColor = [number, number, number];
|
||||
|
||||
export function getLottiePixelRatio(width: number, height: number, needUpscale?: boolean) {
|
||||
let pixelRatio = clamp(window.devicePixelRatio, 1, 2);
|
||||
if(pixelRatio > 1 && !needUpscale) {
|
||||
if(width > 90 && height > 90) {
|
||||
if(!IS_APPLE && mediaSizes.isMobile) {
|
||||
pixelRatio = 1;
|
||||
}
|
||||
} else if(width > 60 && height > 60) {
|
||||
pixelRatio = Math.max(1.5, pixelRatio - 1.5);
|
||||
}
|
||||
}
|
||||
|
||||
return pixelRatio;
|
||||
}
|
||||
|
||||
export default class RLottiePlayer extends EventListenerBase<{
|
||||
enterFrame: (frameNo: number) => void,
|
||||
ready: () => void,
|
||||
@ -226,16 +241,7 @@ export default class RLottiePlayer extends EventListenerBase<{
|
||||
// options.needUpscale = true;
|
||||
|
||||
// * Pixel ratio
|
||||
let pixelRatio = clamp(window.devicePixelRatio, 1, 2);
|
||||
if(pixelRatio > 1 && !options.needUpscale) {
|
||||
if(this.width > 100 && this.height > 100) {
|
||||
if(!IS_APPLE && mediaSizes.isMobile) {
|
||||
pixelRatio = 1;
|
||||
}
|
||||
} else if(this.width > 60 && this.height > 60) {
|
||||
pixelRatio = Math.max(1.5, pixelRatio - 1.5);
|
||||
}
|
||||
}
|
||||
const pixelRatio = getLottiePixelRatio(this.width, this.height, options.needUpscale);
|
||||
|
||||
this.width = Math.round(this.width * pixelRatio);
|
||||
this.height = Math.round(this.height * pixelRatio);
|
||||
|
@ -645,12 +645,12 @@ $bubble-beside-button-width: 38px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat:not(.no-forwards) & {
|
||||
.attachment {
|
||||
cursor: text;
|
||||
user-select: text;
|
||||
}
|
||||
}
|
||||
// .chat:not(.no-forwards) & {
|
||||
// .attachment {
|
||||
// cursor: text;
|
||||
// user-select: text;
|
||||
// }
|
||||
// }
|
||||
|
||||
.message {
|
||||
margin-top: -1.125rem;
|
||||
|
Loading…
x
Reference in New Issue
Block a user