From 577d055d68dbad2eb5518d00df35ae8d7fd663a9 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Sun, 28 May 2023 14:32:28 +0200 Subject: [PATCH] [Refactoring] Custom Emoji Manager: Do not run during chat opening, fix fasterdom phasing --- .../middle/composer/AttachmentModal.tsx | 3 +- src/components/middle/composer/Composer.tsx | 1 + .../middle/composer/MessageInput.tsx | 3 + .../composer/hooks/useInputCustomEmojis.ts | 110 ++++++++---------- src/util/callbacks.ts | 13 ++- src/util/customEmojiManager.ts | 16 +-- 6 files changed, 68 insertions(+), 78 deletions(-) diff --git a/src/components/middle/composer/AttachmentModal.tsx b/src/components/middle/composer/AttachmentModal.tsx index 9cff8a3e0..a914c1c3c 100644 --- a/src/components/middle/composer/AttachmentModal.tsx +++ b/src/components/middle/composer/AttachmentModal.tsx @@ -62,7 +62,7 @@ export type OwnProps = { attachments: ApiAttachment[]; getHtml: Signal; canShowCustomSendMenu?: boolean; - isReady?: boolean; + isReady: boolean; shouldSchedule?: boolean; shouldSuggestCompression?: boolean; shouldForceCompression?: boolean; @@ -595,6 +595,7 @@ const AttachmentModal: FC = ({ chatId={chatId} threadId={threadId} isAttachmentModalInput + isReady={isReady} isActive={isOpen} getHtml={getHtml} editableInputId={EDITABLE_INPUT_MODAL_ID} diff --git a/src/components/middle/composer/Composer.tsx b/src/components/middle/composer/Composer.tsx index 5e9ea01ca..263e45e46 100644 --- a/src/components/middle/composer/Composer.tsx +++ b/src/components/middle/composer/Composer.tsx @@ -1421,6 +1421,7 @@ const Composer: FC = ({ chatId={chatId} canSendPlainText={!isComposerBlocked} threadId={threadId} + isReady={isReady} isActive={!hasAttachments} getHtml={getHtml} placeholder={ diff --git a/src/components/middle/composer/MessageInput.tsx b/src/components/middle/composer/MessageInput.tsx index 11d019b7a..f1302a6b2 100644 --- a/src/components/middle/composer/MessageInput.tsx +++ b/src/components/middle/composer/MessageInput.tsx @@ -47,6 +47,7 @@ type OwnProps = { threadId: number; isAttachmentModalInput?: boolean; editableInputId?: string; + isReady: boolean; isActive: boolean; getHtml: Signal; placeholder: string; @@ -101,6 +102,7 @@ const MessageInput: FC = ({ captionLimit, isAttachmentModalInput, editableInputId, + isReady, isActive, getHtml, placeholder, @@ -160,6 +162,7 @@ const MessageInput: FC = ({ absoluteContainerRef, isAttachmentModalInput ? 'attachment' : 'composer', canPlayAnimatedEmojis, + isReady, isActive, ); diff --git a/src/components/middle/composer/hooks/useInputCustomEmojis.ts b/src/components/middle/composer/hooks/useInputCustomEmojis.ts index 15cf31d94..25a0be7e4 100644 --- a/src/components/middle/composer/hooks/useInputCustomEmojis.ts +++ b/src/components/middle/composer/hooks/useInputCustomEmojis.ts @@ -1,7 +1,7 @@ import { - useCallback, useEffect, useRef, + useCallback, useEffect, useLayoutEffect, useRef, } from '../../../../lib/teact/teact'; -import { requestMeasure, requestMutation } from '../../../../lib/fasterdom/fasterdom'; +import { requestMeasure } from '../../../../lib/fasterdom/fasterdom'; import { ensureRLottie } from '../../../../lib/rlottie/RLottie.async'; import type { ApiSticker } from '../../../../api/types'; @@ -12,7 +12,6 @@ import { selectIsAlwaysHighPriorityEmoji } from '../../../../global/selectors'; import { addCustomEmojiInputRenderCallback, getCustomEmojiMediaDataForInput, - removeCustomEmojiInputRenderCallback, } from '../../../../util/customEmojiManager'; import { round } from '../../../../util/math'; import AbsoluteVideo from '../../../../util/AbsoluteVideo'; @@ -25,6 +24,7 @@ import useBackgroundMode from '../../../../hooks/useBackgroundMode'; import useThrottledCallback from '../../../../hooks/useThrottledCallback'; import useDynamicColorListener from '../../../../hooks/stickers/useDynamicColorListener'; import useEffectWithPrevDeps from '../../../../hooks/useEffectWithPrevDeps'; +import { useLastCallback } from '../../../../hooks/useLastCallback'; const SIZE = 1.25 * REM; const THROTTLE_MS = 300; @@ -44,9 +44,10 @@ export default function useInputCustomEmojis( absoluteContainerRef: React.RefObject, prefixId: string, canPlayAnimatedEmojis: boolean, + isReady?: boolean, isActive?: boolean, ) { - const customColor = useDynamicColorListener(inputRef); + const customColor = useDynamicColorListener(inputRef, !isReady); const colorFilter = useColorFilter(customColor, true); const playersById = useRef>(new Map()); @@ -60,12 +61,8 @@ export default function useInputCustomEmojis( }); }, []); - const synchronizeElements = useCallback(() => { - if (!inputRef.current || !sharedCanvasRef.current || !sharedCanvasHqRef.current) return; - - requestMutation(() => { - document.documentElement.style.setProperty('--input-custom-emoji-filter', colorFilter || 'none'); - }); + const synchronizeElements = useLastCallback(() => { + if (!isReady || !inputRef.current || !sharedCanvasRef.current || !sharedCanvasHqRef.current) return; const global = getGlobal(); const playerIdsToClear = new Set(playersById.current.keys()); @@ -85,65 +82,56 @@ export default function useInputCustomEmojis( return; } - requestMeasure(() => { - const canvasBounds = sharedCanvasRef.current!.getBoundingClientRect(); - const elementBounds = element.getBoundingClientRect(); - const x = round((elementBounds.left - canvasBounds.left) / canvasBounds.width, 4); - const y = round((elementBounds.top - canvasBounds.top) / canvasBounds.height, 4); + const canvasBounds = sharedCanvasRef.current!.getBoundingClientRect(); + const elementBounds = element.getBoundingClientRect(); + const x = round((elementBounds.left - canvasBounds.left) / canvasBounds.width, 4); + const y = round((elementBounds.top - canvasBounds.top) / canvasBounds.height, 4); - if (playersById.current.has(playerId)) { - const player = playersById.current.get(playerId)!; - player.updatePosition(x, y); - return; + if (playersById.current.has(playerId)) { + const player = playersById.current.get(playerId)!; + player.updatePosition(x, y); + return; + } + + const customEmoji = global.customEmojis.byId[documentId]; + if (!customEmoji) { + return; + } + const isHq = customEmoji?.stickerSetInfo && selectIsAlwaysHighPriorityEmoji(global, customEmoji.stickerSetInfo); + const renderId = [ + prefixId, documentId, customColor, + ].filter(Boolean).join('_'); + + createPlayer({ + customEmoji, + sharedCanvasRef, + sharedCanvasHqRef, + absoluteContainerRef, + renderId, + viewId: playerId, + mediaUrl, + isHq, + position: { x, y }, + textColor: customColor, + colorFilter, + }).then((animation) => { + if (canPlayAnimatedEmojis) { + animation.play(); } - const customEmoji = global.customEmojis.byId[documentId]; - if (!customEmoji) { - return; - } - const isHq = customEmoji?.stickerSetInfo && selectIsAlwaysHighPriorityEmoji(global, customEmoji.stickerSetInfo); - const renderId = [ - prefixId, documentId, customColor, - ].filter(Boolean).join('_'); - - createPlayer({ - customEmoji, - sharedCanvasRef, - sharedCanvasHqRef, - absoluteContainerRef, - renderId, - viewId: playerId, - mediaUrl, - isHq, - position: { x, y }, - textColor: customColor, - colorFilter, - }).then((animation) => { - if (canPlayAnimatedEmojis) { - animation.play(); - } - - playersById.current.set(playerId, animation); - }); + playersById.current.set(playerId, animation); }); }); clearPlayers(Array.from(playerIdsToClear)); - }, [ - inputRef, sharedCanvasRef, sharedCanvasHqRef, clearPlayers, prefixId, customColor, absoluteContainerRef, - canPlayAnimatedEmojis, colorFilter, - ]); + }); useEffect(() => { - addCustomEmojiInputRenderCallback(synchronizeElements); - - return () => { - removeCustomEmojiInputRenderCallback(synchronizeElements); - }; + return addCustomEmojiInputRenderCallback(synchronizeElements); }, [synchronizeElements]); useEffect(() => { - if (!getHtml() || !inputRef.current || !sharedCanvasRef.current || !isActive) { + if (!getHtml() || !inputRef.current || !sharedCanvasRef.current || !isActive || !isReady) { clearPlayers(Array.from(playersById.current.keys())); return; } @@ -152,10 +140,14 @@ export default function useInputCustomEmojis( requestMeasure(() => { synchronizeElements(); }); - }, [getHtml, synchronizeElements, inputRef, clearPlayers, sharedCanvasRef, isActive]); + }, [getHtml, synchronizeElements, inputRef, clearPlayers, sharedCanvasRef, isActive, isReady]); + + useLayoutEffect(() => { + document.documentElement.style.setProperty('--input-custom-emoji-filter', colorFilter || 'none'); + }, [colorFilter]); useEffectWithPrevDeps(([prevCustomColor]) => { - if (customColor !== prevCustomColor) { + if (prevCustomColor !== undefined && customColor !== prevCustomColor) { synchronizeElements(); } }, [customColor, synchronizeElements]); diff --git a/src/util/callbacks.ts b/src/util/callbacks.ts index f7b226bdd..70d79a71a 100644 --- a/src/util/callbacks.ts +++ b/src/util/callbacks.ts @@ -1,7 +1,7 @@ -export function createCallbackManager() { - const callbacks = new Set(); +export function createCallbackManager() { + const callbacks = new Set(); - function addCallback(cb: AnyToVoidFunction) { + function addCallback(cb: T) { callbacks.add(cb); return () => { @@ -9,11 +9,11 @@ export function createCallbackManager() { }; } - function removeCallback(cb: AnyToVoidFunction) { + function removeCallback(cb: T) { callbacks.delete(cb); } - function runCallbacks(...args: any[]) { + function runCallbacks(...args: Parameters) { callbacks.forEach((callback) => { callback(...args); }); @@ -31,4 +31,5 @@ export function createCallbackManager() { }; } -export type CallbackManager = ReturnType; +export type CallbackManager + = ReturnType>; diff --git a/src/util/customEmojiManager.ts b/src/util/customEmojiManager.ts index 4ad091d02..6982e5b51 100644 --- a/src/util/customEmojiManager.ts +++ b/src/util/customEmojiManager.ts @@ -12,6 +12,7 @@ import * as mediaLoader from './mediaLoader'; import { throttle } from './schedulers'; import generateIdFor from './generateIdFor'; import { IS_WEBM_SUPPORTED } from './windowEnvironment'; +import { createCallbackManager } from './callbacks'; import placeholderSrc from '../assets/square.svg'; import blankSrc from '../assets/blank.png'; @@ -25,7 +26,7 @@ const DOM_PROCESS_THROTTLE = 500; const INPUT_WAITING_CUSTOM_EMOJI_IDS: Set = new Set(); const handlers = new Map(); -const renderHandlers = new Set(); +const renderCallbacks = createCallbackManager(); let prevGlobal: GlobalState | undefined; @@ -55,17 +56,8 @@ export function removeCustomEmojiCallback(handler: CustomEmojiLoadCallback) { handlers.delete(handler); } -export function addCustomEmojiInputRenderCallback(handler: AnyToVoidFunction) { - renderHandlers.add(handler); -} - -export function removeCustomEmojiInputRenderCallback(handler: AnyToVoidFunction) { - renderHandlers.delete(handler); -} - -const callInputRenderHandlers = throttle((emojiId: string) => { - renderHandlers.forEach((handler) => handler(emojiId)); -}, DOM_PROCESS_THROTTLE); +export const addCustomEmojiInputRenderCallback = renderCallbacks.addCallback; +const callInputRenderHandlers = throttle(renderCallbacks.runCallbacks, DOM_PROCESS_THROTTLE); function processDomForCustomEmoji() { const emojis = document.querySelectorAll('.custom-emoji.placeholder');