From f132faf7cdc159f66ef0b1ad088b05414d7641cd Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Tue, 19 Apr 2022 15:11:57 +0200 Subject: [PATCH] Message / Emoji, Emoji Button: Fade-in animation (#1802) --- src/components/common/helpers/renderText.tsx | 16 +++++++--- .../middle/composer/EmojiButton.scss | 1 + .../middle/composer/EmojiButton.tsx | 29 +++++++++++++++---- .../middle/composer/EmojiPicker.tsx | 2 +- src/styles/index.scss | 4 +++ src/util/emoji.ts | 9 +++++- 6 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/components/common/helpers/renderText.tsx b/src/components/common/helpers/renderText.tsx index 4c16d3198..b4b89031c 100644 --- a/src/components/common/helpers/renderText.tsx +++ b/src/components/common/helpers/renderText.tsx @@ -3,7 +3,9 @@ import EMOJI_REGEX, { removeVS16s } from '../../../lib/twemojiRegex'; import { RE_LINK_TEMPLATE, RE_MENTION_TEMPLATE } from '../../../config'; import { IS_EMOJI_SUPPORTED } from '../../../util/environment'; -import { fixNonStandardEmoji, nativeToUnified } from '../../../util/emoji'; +import { + fixNonStandardEmoji, handleEmojiLoad, LOADED_EMOJIS, nativeToUnified, +} from '../../../util/emoji'; import buildClassName from '../../../util/buildClassName'; import { compact } from '../../../util/iteratees'; @@ -14,7 +16,7 @@ type TextPart = string | Element; export type TextFilter = ( 'escape_html' | 'hq_emoji' | 'emoji' | 'emoji_html' | 'br' | 'br_html' | 'highlight' | 'links' | 'simple_markdown' | 'simple_markdown_html' -); + ); const RE_LETTER_OR_DIGIT = /^[\d\wа-яё]$/i; const SIMPLE_MARKDOWN_REGEX = /(\*\*|__).+?\1/g; @@ -102,16 +104,22 @@ function replaceEmojis(textParts: TextPart[], size: 'big' | 'small', type: 'jsx' return emojis.reduce((emojiResult: TextPart[], emoji, i) => { const code = nativeToUnified(removeVS16s(emoji)); if (!code) return emojiResult; + const src = `./img-apple-${size === 'big' ? '160' : '64'}/${code}.png`; const className = buildClassName( 'emoji', size === 'small' && 'emoji-small', ); + if (type === 'jsx') { + const isLoaded = LOADED_EMOJIS.has(src); + emojiResult.push( {emoji}, ); } diff --git a/src/components/middle/composer/EmojiButton.scss b/src/components/middle/composer/EmojiButton.scss index de6944488..58ef8837f 100644 --- a/src/components/middle/composer/EmojiButton.scss +++ b/src/components/middle/composer/EmojiButton.scss @@ -9,6 +9,7 @@ cursor: pointer; font-size: 1.75rem; line-height: 2.5rem; + background-color: transparent; transition: background-color 0.15s ease; diff --git a/src/components/middle/composer/EmojiButton.tsx b/src/components/middle/composer/EmojiButton.tsx index af7a4def1..0cf7c7643 100644 --- a/src/components/middle/composer/EmojiButton.tsx +++ b/src/components/middle/composer/EmojiButton.tsx @@ -1,6 +1,10 @@ -import React, { FC, memo, useCallback } from '../../../lib/teact/teact'; +import React, { + FC, memo, useCallback, +} from '../../../lib/teact/teact'; import { IS_EMOJI_SUPPORTED } from '../../../util/environment'; +import { handleEmojiLoad, LOADED_EMOJIS } from '../../../util/emoji'; +import buildClassName from '../../../util/buildClassName'; import './EmojiButton.scss'; @@ -18,15 +22,30 @@ const EmojiButton: FC = ({ emoji, focus, onClick }) => { onClick(emoji.native, emoji.id); }, [emoji, onClick]); + const className = buildClassName( + 'EmojiButton', + focus && 'focus', + ); + + const src = `./img-apple-64/${emoji.image}.png`; + const isLoaded = LOADED_EMOJIS.has(src); + return (
- {IS_EMOJI_SUPPORTED - ? emoji.native - : {emoji.native}} + {IS_EMOJI_SUPPORTED ? emoji.native : ( + {emoji.native} + )}
); }; diff --git a/src/components/middle/composer/EmojiPicker.tsx b/src/components/middle/composer/EmojiPicker.tsx index a49a8c4dd..a3c07b8c7 100644 --- a/src/components/middle/composer/EmojiPicker.tsx +++ b/src/components/middle/composer/EmojiPicker.tsx @@ -219,7 +219,7 @@ const EmojiPicker: FC = ({ async function ensureEmojiData() { if (!emojiDataPromise) { - emojiDataPromise = import('emoji-data-ios/emoji-data.json') as unknown as Promise; + emojiDataPromise = import('emoji-data-ios/emoji-data.json'); emojiRawData = (await emojiDataPromise).default; emojiData = uncompressEmoji(emojiRawData); diff --git a/src/styles/index.scss b/src/styles/index.scss index 400d85b47..3e987c9b3 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -64,6 +64,7 @@ body.cursor-ew-resize { #root { height: 100%; + @media (max-width: 600px) { height: calc(var(--vh, 1vh) * 100); } @@ -265,10 +266,12 @@ div[role="button"] { transform: scale(0.5); opacity: 0.8; } + 50% { transform: scale(1.1); opacity: 1; } + 100% { transform: scale(1); } @@ -279,6 +282,7 @@ div[role="button"] { transform: scale(1); opacity: 0.4; } + to { transform: scale(0.5); opacity: 0; diff --git a/src/util/emoji.ts b/src/util/emoji.ts index 431f4e820..46c45a0ac 100644 --- a/src/util/emoji.ts +++ b/src/util/emoji.ts @@ -28,6 +28,13 @@ function unifiedToNative(unified: string) { return String.fromCodePoint(...codePoints); } +export const LOADED_EMOJIS = new Set(); + +export function handleEmojiLoad(event: React.SyntheticEvent) { + event.currentTarget.classList.add('open'); + LOADED_EMOJIS.add(event.currentTarget.dataset.path!); +} + export function fixNonStandardEmoji(text: string) { // Non-standard sequences typically parsed as separate emojis, so no need to fix text without any if (!text.match(EMOJI_REGEX)) return text; @@ -51,7 +58,7 @@ export function nativeToUnified(emoji: string) { if (emoji.charCodeAt(i + 1) >= 0xdc00 && emoji.charCodeAt(i + 1) <= 0xdfff) { pairs.push( (emoji.charCodeAt(i) - 0xd800) * 0x400 - + (emoji.charCodeAt(i + 1) - 0xdc00) + 0x10000, + + (emoji.charCodeAt(i + 1) - 0xdc00) + 0x10000, ); } } else if (emoji.charCodeAt(i) < 0xd800 || emoji.charCodeAt(i) > 0xdfff) {