129 lines
4.4 KiB
TypeScript
129 lines
4.4 KiB
TypeScript
import { addCallback } from '../../lib/teact/teactn';
|
|
import { getGlobal } from '../../global';
|
|
|
|
import type { ApiSticker } from '../../api/types';
|
|
import type { GlobalState } from '../../global/types';
|
|
import { ApiMediaFormat } from '../../api/types';
|
|
|
|
import { requestMutation } from '../../lib/fasterdom/fasterdom';
|
|
import { getStickerHashById } from '../../global/helpers';
|
|
import { selectCanPlayAnimatedEmojis, selectCustomEmoji } from '../../global/selectors';
|
|
import { IS_WEBM_SUPPORTED } from '../browser/windowEnvironment';
|
|
import { createCallbackManager } from '../callbacks';
|
|
import generateUniqueId from '../generateUniqueId';
|
|
import * as mediaLoader from '../mediaLoader';
|
|
import { throttle } from '../schedulers';
|
|
|
|
import blankSrc from '../../assets/blank.png';
|
|
import placeholderSrc from '../../assets/square.svg';
|
|
|
|
type CustomEmojiLoadCallback = (customEmojis: GlobalState['customEmojis']) => void;
|
|
type CustomEmojiInputRenderCallback = (emojiId: string) => void;
|
|
|
|
const DOM_PROCESS_THROTTLE = 500;
|
|
|
|
const INPUT_WAITING_CUSTOM_EMOJI_IDS = new Set<string>();
|
|
|
|
const handlers = new Map<CustomEmojiLoadCallback, string>();
|
|
const renderCallbacks = createCallbackManager<CustomEmojiInputRenderCallback>();
|
|
|
|
let prevGlobal: GlobalState | undefined;
|
|
|
|
addCallback((global: GlobalState) => {
|
|
if (
|
|
global.customEmojis.byId !== prevGlobal?.customEmojis.byId
|
|
|| selectCanPlayAnimatedEmojis(global) !== selectCanPlayAnimatedEmojis(prevGlobal)
|
|
) {
|
|
for (const entry of handlers) {
|
|
const [handler, id] = entry;
|
|
if (selectCustomEmoji(global, id)) {
|
|
handler(global.customEmojis);
|
|
}
|
|
}
|
|
|
|
checkInputCustomEmojiLoad(global.customEmojis);
|
|
}
|
|
|
|
prevGlobal = global;
|
|
});
|
|
|
|
export function addCustomEmojiCallback(handler: CustomEmojiLoadCallback, emojiId: string) {
|
|
handlers.set(handler, emojiId);
|
|
}
|
|
|
|
export function removeCustomEmojiCallback(handler: CustomEmojiLoadCallback) {
|
|
handlers.delete(handler);
|
|
}
|
|
|
|
export const addCustomEmojiInputRenderCallback = renderCallbacks.addCallback;
|
|
const callInputRenderHandlers = throttle(renderCallbacks.runCallbacks, DOM_PROCESS_THROTTLE);
|
|
|
|
function processDomForCustomEmoji() {
|
|
const emojis = document.querySelectorAll<HTMLImageElement>('.custom-emoji.placeholder');
|
|
emojis.forEach((emoji) => {
|
|
const customEmoji = selectCustomEmoji(getGlobal(), emoji.dataset.documentId!);
|
|
if (!customEmoji) {
|
|
INPUT_WAITING_CUSTOM_EMOJI_IDS.add(emoji.dataset.documentId!);
|
|
return;
|
|
}
|
|
const [isPlaceholder, src, uniqueId] = getInputCustomEmojiParams(customEmoji);
|
|
|
|
if (customEmoji.shouldUseTextColor && !emoji.classList.contains('colorable')) {
|
|
requestMutation(() => {
|
|
emoji.classList.add('colorable');
|
|
});
|
|
}
|
|
|
|
if (!isPlaceholder) {
|
|
requestMutation(() => {
|
|
emoji.src = src;
|
|
emoji.classList.remove('placeholder');
|
|
if (uniqueId) emoji.dataset.uniqueId = uniqueId;
|
|
|
|
callInputRenderHandlers(customEmoji.id);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
export const processMessageInputForCustomEmoji = throttle(processDomForCustomEmoji, DOM_PROCESS_THROTTLE);
|
|
|
|
function checkInputCustomEmojiLoad(customEmojis: GlobalState['customEmojis']) {
|
|
const loaded = Array.from(INPUT_WAITING_CUSTOM_EMOJI_IDS).filter((id) => Boolean(customEmojis.byId[id]));
|
|
if (loaded.length) {
|
|
loaded.forEach((id) => INPUT_WAITING_CUSTOM_EMOJI_IDS.delete(id));
|
|
processMessageInputForCustomEmoji();
|
|
}
|
|
}
|
|
|
|
export function getCustomEmojiMediaDataForInput(emojiId: string, isPreview?: boolean) {
|
|
const mediaHash = getStickerHashById(emojiId, isPreview);
|
|
const data = mediaLoader.getFromMemory(mediaHash);
|
|
if (data) {
|
|
return data;
|
|
}
|
|
|
|
void fetchAndProcess(mediaHash);
|
|
return undefined;
|
|
}
|
|
|
|
function fetchAndProcess(mediaHash: string) {
|
|
return mediaLoader.fetch(mediaHash, ApiMediaFormat.BlobUrl).then(() => {
|
|
processMessageInputForCustomEmoji();
|
|
});
|
|
}
|
|
|
|
export function getInputCustomEmojiParams(customEmoji?: ApiSticker) {
|
|
if (!customEmoji) return [true, placeholderSrc, undefined] as const;
|
|
const shouldUseStaticFallback = !IS_WEBM_SUPPORTED && customEmoji.isVideo;
|
|
const isUsingSharedCanvas = customEmoji.isLottie || (customEmoji.isVideo && !shouldUseStaticFallback);
|
|
if (isUsingSharedCanvas) {
|
|
void fetchAndProcess(`sticker${customEmoji.id}`);
|
|
return [false, blankSrc, generateUniqueId()] as const;
|
|
}
|
|
|
|
const mediaData = getCustomEmojiMediaDataForInput(customEmoji.id, shouldUseStaticFallback);
|
|
|
|
return [!mediaData, mediaData || placeholderSrc, undefined] as const;
|
|
}
|