[Refactoring] Custom Emoji Manager: Do not run during chat opening, fix fasterdom phasing

This commit is contained in:
Alexander Zinchuk 2023-05-28 14:32:28 +02:00
parent 2f3671899c
commit 577d055d68
6 changed files with 68 additions and 78 deletions

View File

@ -62,7 +62,7 @@ export type OwnProps = {
attachments: ApiAttachment[]; attachments: ApiAttachment[];
getHtml: Signal<string>; getHtml: Signal<string>;
canShowCustomSendMenu?: boolean; canShowCustomSendMenu?: boolean;
isReady?: boolean; isReady: boolean;
shouldSchedule?: boolean; shouldSchedule?: boolean;
shouldSuggestCompression?: boolean; shouldSuggestCompression?: boolean;
shouldForceCompression?: boolean; shouldForceCompression?: boolean;
@ -595,6 +595,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
chatId={chatId} chatId={chatId}
threadId={threadId} threadId={threadId}
isAttachmentModalInput isAttachmentModalInput
isReady={isReady}
isActive={isOpen} isActive={isOpen}
getHtml={getHtml} getHtml={getHtml}
editableInputId={EDITABLE_INPUT_MODAL_ID} editableInputId={EDITABLE_INPUT_MODAL_ID}

View File

@ -1421,6 +1421,7 @@ const Composer: FC<OwnProps & StateProps> = ({
chatId={chatId} chatId={chatId}
canSendPlainText={!isComposerBlocked} canSendPlainText={!isComposerBlocked}
threadId={threadId} threadId={threadId}
isReady={isReady}
isActive={!hasAttachments} isActive={!hasAttachments}
getHtml={getHtml} getHtml={getHtml}
placeholder={ placeholder={

View File

@ -47,6 +47,7 @@ type OwnProps = {
threadId: number; threadId: number;
isAttachmentModalInput?: boolean; isAttachmentModalInput?: boolean;
editableInputId?: string; editableInputId?: string;
isReady: boolean;
isActive: boolean; isActive: boolean;
getHtml: Signal<string>; getHtml: Signal<string>;
placeholder: string; placeholder: string;
@ -101,6 +102,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
captionLimit, captionLimit,
isAttachmentModalInput, isAttachmentModalInput,
editableInputId, editableInputId,
isReady,
isActive, isActive,
getHtml, getHtml,
placeholder, placeholder,
@ -160,6 +162,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
absoluteContainerRef, absoluteContainerRef,
isAttachmentModalInput ? 'attachment' : 'composer', isAttachmentModalInput ? 'attachment' : 'composer',
canPlayAnimatedEmojis, canPlayAnimatedEmojis,
isReady,
isActive, isActive,
); );

View File

@ -1,7 +1,7 @@
import { import {
useCallback, useEffect, useRef, useCallback, useEffect, useLayoutEffect, useRef,
} from '../../../../lib/teact/teact'; } 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 { ensureRLottie } from '../../../../lib/rlottie/RLottie.async';
import type { ApiSticker } from '../../../../api/types'; import type { ApiSticker } from '../../../../api/types';
@ -12,7 +12,6 @@ import { selectIsAlwaysHighPriorityEmoji } from '../../../../global/selectors';
import { import {
addCustomEmojiInputRenderCallback, addCustomEmojiInputRenderCallback,
getCustomEmojiMediaDataForInput, getCustomEmojiMediaDataForInput,
removeCustomEmojiInputRenderCallback,
} from '../../../../util/customEmojiManager'; } from '../../../../util/customEmojiManager';
import { round } from '../../../../util/math'; import { round } from '../../../../util/math';
import AbsoluteVideo from '../../../../util/AbsoluteVideo'; import AbsoluteVideo from '../../../../util/AbsoluteVideo';
@ -25,6 +24,7 @@ import useBackgroundMode from '../../../../hooks/useBackgroundMode';
import useThrottledCallback from '../../../../hooks/useThrottledCallback'; import useThrottledCallback from '../../../../hooks/useThrottledCallback';
import useDynamicColorListener from '../../../../hooks/stickers/useDynamicColorListener'; import useDynamicColorListener from '../../../../hooks/stickers/useDynamicColorListener';
import useEffectWithPrevDeps from '../../../../hooks/useEffectWithPrevDeps'; import useEffectWithPrevDeps from '../../../../hooks/useEffectWithPrevDeps';
import { useLastCallback } from '../../../../hooks/useLastCallback';
const SIZE = 1.25 * REM; const SIZE = 1.25 * REM;
const THROTTLE_MS = 300; const THROTTLE_MS = 300;
@ -44,9 +44,10 @@ export default function useInputCustomEmojis(
absoluteContainerRef: React.RefObject<HTMLElement>, absoluteContainerRef: React.RefObject<HTMLElement>,
prefixId: string, prefixId: string,
canPlayAnimatedEmojis: boolean, canPlayAnimatedEmojis: boolean,
isReady?: boolean,
isActive?: boolean, isActive?: boolean,
) { ) {
const customColor = useDynamicColorListener(inputRef); const customColor = useDynamicColorListener(inputRef, !isReady);
const colorFilter = useColorFilter(customColor, true); const colorFilter = useColorFilter(customColor, true);
const playersById = useRef<Map<string, CustomEmojiPlayer>>(new Map()); const playersById = useRef<Map<string, CustomEmojiPlayer>>(new Map());
@ -60,12 +61,8 @@ export default function useInputCustomEmojis(
}); });
}, []); }, []);
const synchronizeElements = useCallback(() => { const synchronizeElements = useLastCallback(() => {
if (!inputRef.current || !sharedCanvasRef.current || !sharedCanvasHqRef.current) return; if (!isReady || !inputRef.current || !sharedCanvasRef.current || !sharedCanvasHqRef.current) return;
requestMutation(() => {
document.documentElement.style.setProperty('--input-custom-emoji-filter', colorFilter || 'none');
});
const global = getGlobal(); const global = getGlobal();
const playerIdsToClear = new Set(playersById.current.keys()); const playerIdsToClear = new Set(playersById.current.keys());
@ -85,65 +82,56 @@ export default function useInputCustomEmojis(
return; return;
} }
requestMeasure(() => { const canvasBounds = sharedCanvasRef.current!.getBoundingClientRect();
const canvasBounds = sharedCanvasRef.current!.getBoundingClientRect(); const elementBounds = element.getBoundingClientRect();
const elementBounds = element.getBoundingClientRect(); const x = round((elementBounds.left - canvasBounds.left) / canvasBounds.width, 4);
const x = round((elementBounds.left - canvasBounds.left) / canvasBounds.width, 4); const y = round((elementBounds.top - canvasBounds.top) / canvasBounds.height, 4);
const y = round((elementBounds.top - canvasBounds.top) / canvasBounds.height, 4);
if (playersById.current.has(playerId)) { if (playersById.current.has(playerId)) {
const player = playersById.current.get(playerId)!; const player = playersById.current.get(playerId)!;
player.updatePosition(x, y); player.updatePosition(x, y);
return; 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]; playersById.current.set(playerId, animation);
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);
});
}); });
}); });
clearPlayers(Array.from(playerIdsToClear)); clearPlayers(Array.from(playerIdsToClear));
}, [ });
inputRef, sharedCanvasRef, sharedCanvasHqRef, clearPlayers, prefixId, customColor, absoluteContainerRef,
canPlayAnimatedEmojis, colorFilter,
]);
useEffect(() => { useEffect(() => {
addCustomEmojiInputRenderCallback(synchronizeElements); return addCustomEmojiInputRenderCallback(synchronizeElements);
return () => {
removeCustomEmojiInputRenderCallback(synchronizeElements);
};
}, [synchronizeElements]); }, [synchronizeElements]);
useEffect(() => { useEffect(() => {
if (!getHtml() || !inputRef.current || !sharedCanvasRef.current || !isActive) { if (!getHtml() || !inputRef.current || !sharedCanvasRef.current || !isActive || !isReady) {
clearPlayers(Array.from(playersById.current.keys())); clearPlayers(Array.from(playersById.current.keys()));
return; return;
} }
@ -152,10 +140,14 @@ export default function useInputCustomEmojis(
requestMeasure(() => { requestMeasure(() => {
synchronizeElements(); 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]) => { useEffectWithPrevDeps(([prevCustomColor]) => {
if (customColor !== prevCustomColor) { if (prevCustomColor !== undefined && customColor !== prevCustomColor) {
synchronizeElements(); synchronizeElements();
} }
}, [customColor, synchronizeElements]); }, [customColor, synchronizeElements]);

View File

@ -1,7 +1,7 @@
export function createCallbackManager() { export function createCallbackManager<T extends AnyToVoidFunction = AnyToVoidFunction>() {
const callbacks = new Set<AnyToVoidFunction>(); const callbacks = new Set<T>();
function addCallback(cb: AnyToVoidFunction) { function addCallback(cb: T) {
callbacks.add(cb); callbacks.add(cb);
return () => { return () => {
@ -9,11 +9,11 @@ export function createCallbackManager() {
}; };
} }
function removeCallback(cb: AnyToVoidFunction) { function removeCallback(cb: T) {
callbacks.delete(cb); callbacks.delete(cb);
} }
function runCallbacks(...args: any[]) { function runCallbacks(...args: Parameters<T>) {
callbacks.forEach((callback) => { callbacks.forEach((callback) => {
callback(...args); callback(...args);
}); });
@ -31,4 +31,5 @@ export function createCallbackManager() {
}; };
} }
export type CallbackManager = ReturnType<typeof createCallbackManager>; export type CallbackManager<T extends AnyToVoidFunction = AnyToVoidFunction>
= ReturnType<typeof createCallbackManager<T>>;

View File

@ -12,6 +12,7 @@ import * as mediaLoader from './mediaLoader';
import { throttle } from './schedulers'; import { throttle } from './schedulers';
import generateIdFor from './generateIdFor'; import generateIdFor from './generateIdFor';
import { IS_WEBM_SUPPORTED } from './windowEnvironment'; import { IS_WEBM_SUPPORTED } from './windowEnvironment';
import { createCallbackManager } from './callbacks';
import placeholderSrc from '../assets/square.svg'; import placeholderSrc from '../assets/square.svg';
import blankSrc from '../assets/blank.png'; import blankSrc from '../assets/blank.png';
@ -25,7 +26,7 @@ const DOM_PROCESS_THROTTLE = 500;
const INPUT_WAITING_CUSTOM_EMOJI_IDS: Set<string> = new Set(); const INPUT_WAITING_CUSTOM_EMOJI_IDS: Set<string> = new Set();
const handlers = new Map<CustomEmojiLoadCallback, string>(); const handlers = new Map<CustomEmojiLoadCallback, string>();
const renderHandlers = new Set<CustomEmojiInputRenderCallback>(); const renderCallbacks = createCallbackManager<CustomEmojiInputRenderCallback>();
let prevGlobal: GlobalState | undefined; let prevGlobal: GlobalState | undefined;
@ -55,17 +56,8 @@ export function removeCustomEmojiCallback(handler: CustomEmojiLoadCallback) {
handlers.delete(handler); handlers.delete(handler);
} }
export function addCustomEmojiInputRenderCallback(handler: AnyToVoidFunction) { export const addCustomEmojiInputRenderCallback = renderCallbacks.addCallback;
renderHandlers.add(handler); const callInputRenderHandlers = throttle(renderCallbacks.runCallbacks, DOM_PROCESS_THROTTLE);
}
export function removeCustomEmojiInputRenderCallback(handler: AnyToVoidFunction) {
renderHandlers.delete(handler);
}
const callInputRenderHandlers = throttle((emojiId: string) => {
renderHandlers.forEach((handler) => handler(emojiId));
}, DOM_PROCESS_THROTTLE);
function processDomForCustomEmoji() { function processDomForCustomEmoji() {
const emojis = document.querySelectorAll<HTMLImageElement>('.custom-emoji.placeholder'); const emojis = document.querySelectorAll<HTMLImageElement>('.custom-emoji.placeholder');