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 I18n, {FormatterArguments, i18n, langPack, LangPackKey, UNSUPPORTED_LANG_PACK_KEY, _i18n} from '../../lib/langPack';
|
||||||
import AvatarElement from '../avatar';
|
import AvatarElement from '../avatar';
|
||||||
import ripple from '../ripple';
|
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 {MessageRender} from './messageRender';
|
||||||
import LazyLoadQueue from '../lazyLoadQueue';
|
import LazyLoadQueue from '../lazyLoadQueue';
|
||||||
import ListenerSetter from '../../helpers/listenerSetter';
|
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 getServerMessageId from '../../lib/appManagers/utils/messageId/getServerMessageId';
|
||||||
import generateMessageId from '../../lib/appManagers/utils/messageId/generateMessageId';
|
import generateMessageId from '../../lib/appManagers/utils/messageId/generateMessageId';
|
||||||
import {AppManagers} from '../../lib/appManagers/managers';
|
import {AppManagers} from '../../lib/appManagers/managers';
|
||||||
import {Awaited} from '../../types';
|
import {Awaited, SendMessageEmojiInteractionData} from '../../types';
|
||||||
import idleController from '../../helpers/idleController';
|
import idleController from '../../helpers/idleController';
|
||||||
import overlayCounter from '../../helpers/overlayCounter';
|
import overlayCounter from '../../helpers/overlayCounter';
|
||||||
import {cancelContextMenuOpening} from '../../helpers/dom/attachContextMenuListener';
|
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 getStickerEffectThumb from '../../lib/appManagers/utils/stickers/getStickerEffectThumb';
|
||||||
import attachStickerViewerListeners from '../stickerViewer';
|
import attachStickerViewerListeners from '../stickerViewer';
|
||||||
import {makeMediaSize, MediaSize} from '../../helpers/mediaSize';
|
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 USE_MEDIA_TAILS = false;
|
||||||
const IGNORE_ACTIONS: Set<Message.messageService['action']['_']> = new Set([
|
const IGNORE_ACTIONS: Set<Message.messageService['action']['_']> = new Set([
|
||||||
@ -1553,6 +1558,19 @@ export default class ChatBubbles {
|
|||||||
return;
|
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');
|
const commentsDiv: HTMLElement = findUpClassName(target, 'replies');
|
||||||
if(commentsDiv) {
|
if(commentsDiv) {
|
||||||
const bubbleMid = +bubble.dataset.mid;
|
const bubbleMid = +bubble.dataset.mid;
|
||||||
|
@ -81,6 +81,10 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
|
|||||||
}) {
|
}) {
|
||||||
div = Array.isArray(div) ? div : [div];
|
div = Array.isArray(div) ? div : [div];
|
||||||
|
|
||||||
|
if(isCustomEmoji) {
|
||||||
|
emoji = doc.stickerEmojiRaw;
|
||||||
|
}
|
||||||
|
|
||||||
const stickerType = doc.sticker;
|
const stickerType = doc.sticker;
|
||||||
if(stickerType === 1) {
|
if(stickerType === 1) {
|
||||||
asStatic = true;
|
asStatic = true;
|
||||||
@ -101,6 +105,10 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
|
|||||||
|
|
||||||
div.forEach((div) => {
|
div.forEach((div) => {
|
||||||
div.dataset.docId = '' + doc.id;
|
div.dataset.docId = '' + doc.id;
|
||||||
|
if(emoji) {
|
||||||
|
div.dataset.stickerEmoji = emoji;
|
||||||
|
}
|
||||||
|
|
||||||
div.classList.add('media-sticker-wrapper');
|
div.classList.add('media-sticker-wrapper');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -162,7 +170,7 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
|
|||||||
await getCacheContext(fullThumb?.type);
|
await getCacheContext(fullThumb?.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
const toneIndex = emoji ? getEmojiToneIndex(emoji) : -1;
|
const toneIndex = emoji && !isCustomEmoji ? getEmojiToneIndex(emoji) : -1;
|
||||||
const downloaded = cacheContext.downloaded && !needFadeIn;
|
const downloaded = cacheContext.downloaded && !needFadeIn;
|
||||||
|
|
||||||
const isThumbNeededForType = isAnimated;
|
const isThumbNeededForType = isAnimated;
|
||||||
@ -357,7 +365,7 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
|
|||||||
|
|
||||||
const animation = await lottieLoader.loadAnimationWorker({
|
const animation = await lottieLoader.loadAnimationWorker({
|
||||||
container: (div as HTMLElement[])[0],
|
container: (div as HTMLElement[])[0],
|
||||||
loop: loop && !emoji,
|
loop: loop && (!emoji || isCustomEmoji),
|
||||||
autoplay: play,
|
autoplay: play,
|
||||||
animationData: blob,
|
animationData: blob,
|
||||||
width,
|
width,
|
||||||
@ -427,109 +435,7 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
|
|||||||
}, {once: true});
|
}, {once: true});
|
||||||
|
|
||||||
if(emoji) {
|
if(emoji) {
|
||||||
const data: SendMessageEmojiInteractionData = {
|
|
||||||
a: [],
|
|
||||||
v: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
let sendInteractionThrottled: () => void;
|
|
||||||
|
|
||||||
managers.appStickersManager.preloadAnimatedEmojiStickerAnimation(emoji);
|
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;
|
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') {
|
if(typing?.action?._ === 'sendMessageEmojiInteraction') {
|
||||||
const action = typing.action;
|
const action = typing.action;
|
||||||
const bubble = chat.bubbles.bubbles[generateMessageId(typing.action.msg_id)];
|
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 stickerWrapper: HTMLElement = bubble.querySelector('.media-sticker-wrapper:not(.bubble-hover-reaction-sticker):not(.reaction-sticker)');
|
||||||
|
|
||||||
const data: SendMessageEmojiInteractionData = JSON.parse(action.interaction.data);
|
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 rootScope from '../rootScope';
|
||||||
import mediaSizes from '../../helpers/mediaSizes';
|
import mediaSizes from '../../helpers/mediaSizes';
|
||||||
import {wrapSticker} from '../../components/wrappers';
|
import {wrapSticker} from '../../components/wrappers';
|
||||||
import RLottiePlayer from '../rlottie/rlottiePlayer';
|
import RLottiePlayer, {getLottiePixelRatio} from '../rlottie/rlottiePlayer';
|
||||||
import animationIntersector from '../../components/animationIntersector';
|
import animationIntersector from '../../components/animationIntersector';
|
||||||
import type {MyDocument} from '../appManagers/appDocsManager';
|
import type {MyDocument} from '../appManagers/appDocsManager';
|
||||||
import LazyLoadQueue from '../../components/lazyLoadQueue';
|
import LazyLoadQueue from '../../components/lazyLoadQueue';
|
||||||
@ -196,7 +196,7 @@ export class CustomEmojiRendererElement extends HTMLElement {
|
|||||||
|
|
||||||
public setDimensionsFromRect(rect: DOMRect) {
|
public setDimensionsFromRect(rect: DOMRect) {
|
||||||
const {canvas} = this;
|
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.width = Math.round(rect.width * dpr);
|
||||||
canvas.height = Math.round(rect.height * dpr);
|
canvas.height = Math.round(rect.height * dpr);
|
||||||
}
|
}
|
||||||
|
@ -114,6 +114,21 @@ const cache = new RLottieCache();
|
|||||||
|
|
||||||
export type RLottieColor = [number, number, number];
|
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<{
|
export default class RLottiePlayer extends EventListenerBase<{
|
||||||
enterFrame: (frameNo: number) => void,
|
enterFrame: (frameNo: number) => void,
|
||||||
ready: () => void,
|
ready: () => void,
|
||||||
@ -226,16 +241,7 @@ export default class RLottiePlayer extends EventListenerBase<{
|
|||||||
// options.needUpscale = true;
|
// options.needUpscale = true;
|
||||||
|
|
||||||
// * Pixel ratio
|
// * Pixel ratio
|
||||||
let pixelRatio = clamp(window.devicePixelRatio, 1, 2);
|
const pixelRatio = getLottiePixelRatio(this.width, this.height, options.needUpscale);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.width = Math.round(this.width * pixelRatio);
|
this.width = Math.round(this.width * pixelRatio);
|
||||||
this.height = Math.round(this.height * pixelRatio);
|
this.height = Math.round(this.height * pixelRatio);
|
||||||
|
@ -645,12 +645,12 @@ $bubble-beside-button-width: 38px;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat:not(.no-forwards) & {
|
// .chat:not(.no-forwards) & {
|
||||||
.attachment {
|
// .attachment {
|
||||||
cursor: text;
|
// cursor: text;
|
||||||
user-select: text;
|
// user-select: text;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
margin-top: -1.125rem;
|
margin-top: -1.125rem;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user