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

View File

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

View File

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

View File

@ -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<HTMLElement>,
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<Map<string, CustomEmojiPlayer>>(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]);

View File

@ -1,7 +1,7 @@
export function createCallbackManager() {
const callbacks = new Set<AnyToVoidFunction>();
export function createCallbackManager<T extends AnyToVoidFunction = AnyToVoidFunction>() {
const callbacks = new Set<T>();
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<T>) {
callbacks.forEach((callback) => {
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 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<string> = new Set();
const handlers = new Map<CustomEmojiLoadCallback, string>();
const renderHandlers = new Set<CustomEmojiInputRenderCallback>();
const renderCallbacks = createCallbackManager<CustomEmojiInputRenderCallback>();
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<HTMLImageElement>('.custom-emoji.placeholder');