Message / Emoji, Emoji Button: Fade-in animation (#1802)

This commit is contained in:
Alexander Zinchuk 2022-04-19 15:11:57 +02:00
parent d44d465520
commit f132faf7cd
6 changed files with 50 additions and 11 deletions

View File

@ -3,7 +3,9 @@ import EMOJI_REGEX, { removeVS16s } from '../../../lib/twemojiRegex';
import { RE_LINK_TEMPLATE, RE_MENTION_TEMPLATE } from '../../../config'; import { RE_LINK_TEMPLATE, RE_MENTION_TEMPLATE } from '../../../config';
import { IS_EMOJI_SUPPORTED } from '../../../util/environment'; 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 buildClassName from '../../../util/buildClassName';
import { compact } from '../../../util/iteratees'; import { compact } from '../../../util/iteratees';
@ -14,7 +16,7 @@ type TextPart = string | Element;
export type TextFilter = ( export type TextFilter = (
'escape_html' | 'hq_emoji' | 'emoji' | 'emoji_html' | 'br' | 'br_html' | 'highlight' | 'links' | 'escape_html' | 'hq_emoji' | 'emoji' | 'emoji_html' | 'br' | 'br_html' | 'highlight' | 'links' |
'simple_markdown' | 'simple_markdown_html' 'simple_markdown' | 'simple_markdown_html'
); );
const RE_LETTER_OR_DIGIT = /^[\d\wа-яё]$/i; const RE_LETTER_OR_DIGIT = /^[\d\wа-яё]$/i;
const SIMPLE_MARKDOWN_REGEX = /(\*\*|__).+?\1/g; 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) => { return emojis.reduce((emojiResult: TextPart[], emoji, i) => {
const code = nativeToUnified(removeVS16s(emoji)); const code = nativeToUnified(removeVS16s(emoji));
if (!code) return emojiResult; if (!code) return emojiResult;
const src = `./img-apple-${size === 'big' ? '160' : '64'}/${code}.png`;
const className = buildClassName( const className = buildClassName(
'emoji', 'emoji',
size === 'small' && 'emoji-small', size === 'small' && 'emoji-small',
); );
if (type === 'jsx') { if (type === 'jsx') {
const isLoaded = LOADED_EMOJIS.has(src);
emojiResult.push( emojiResult.push(
<img <img
className={className} src={src}
src={`./img-apple-${size === 'big' ? '160' : '64'}/${code}.png`} className={`${className}${!isLoaded ? ' opacity-transition slow shown' : ''}`}
alt={emoji} alt={emoji}
data-path={src}
onLoad={!isLoaded ? handleEmojiLoad : undefined}
/>, />,
); );
} }

View File

@ -9,6 +9,7 @@
cursor: pointer; cursor: pointer;
font-size: 1.75rem; font-size: 1.75rem;
line-height: 2.5rem; line-height: 2.5rem;
background-color: transparent; background-color: transparent;
transition: background-color 0.15s ease; transition: background-color 0.15s ease;

View File

@ -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 { IS_EMOJI_SUPPORTED } from '../../../util/environment';
import { handleEmojiLoad, LOADED_EMOJIS } from '../../../util/emoji';
import buildClassName from '../../../util/buildClassName';
import './EmojiButton.scss'; import './EmojiButton.scss';
@ -18,15 +22,30 @@ const EmojiButton: FC<OwnProps> = ({ emoji, focus, onClick }) => {
onClick(emoji.native, emoji.id); onClick(emoji.native, emoji.id);
}, [emoji, onClick]); }, [emoji, onClick]);
const className = buildClassName(
'EmojiButton',
focus && 'focus',
);
const src = `./img-apple-64/${emoji.image}.png`;
const isLoaded = LOADED_EMOJIS.has(src);
return ( return (
<div <div
className={`EmojiButton ${focus ? 'focus' : ''}`} className={className}
onMouseDown={handleClick} onMouseDown={handleClick}
title={`:${emoji.names[0]}:`} title={`:${emoji.names[0]}:`}
> >
{IS_EMOJI_SUPPORTED {IS_EMOJI_SUPPORTED ? emoji.native : (
? emoji.native <img
: <img src={`./img-apple-64/${emoji.image}.png`} alt={emoji.native} loading="lazy" />} src={src}
className={!isLoaded ? 'opacity-transition shown' : undefined}
alt={emoji.native}
loading="lazy"
data-path={src}
onLoad={!isLoaded ? handleEmojiLoad : undefined}
/>
)}
</div> </div>
); );
}; };

View File

@ -219,7 +219,7 @@ const EmojiPicker: FC<OwnProps & StateProps> = ({
async function ensureEmojiData() { async function ensureEmojiData() {
if (!emojiDataPromise) { if (!emojiDataPromise) {
emojiDataPromise = import('emoji-data-ios/emoji-data.json') as unknown as Promise<EmojiModule>; emojiDataPromise = import('emoji-data-ios/emoji-data.json');
emojiRawData = (await emojiDataPromise).default; emojiRawData = (await emojiDataPromise).default;
emojiData = uncompressEmoji(emojiRawData); emojiData = uncompressEmoji(emojiRawData);

View File

@ -64,6 +64,7 @@ body.cursor-ew-resize {
#root { #root {
height: 100%; height: 100%;
@media (max-width: 600px) { @media (max-width: 600px) {
height: calc(var(--vh, 1vh) * 100); height: calc(var(--vh, 1vh) * 100);
} }
@ -265,10 +266,12 @@ div[role="button"] {
transform: scale(0.5); transform: scale(0.5);
opacity: 0.8; opacity: 0.8;
} }
50% { 50% {
transform: scale(1.1); transform: scale(1.1);
opacity: 1; opacity: 1;
} }
100% { 100% {
transform: scale(1); transform: scale(1);
} }
@ -279,6 +282,7 @@ div[role="button"] {
transform: scale(1); transform: scale(1);
opacity: 0.4; opacity: 0.4;
} }
to { to {
transform: scale(0.5); transform: scale(0.5);
opacity: 0; opacity: 0;

View File

@ -28,6 +28,13 @@ function unifiedToNative(unified: string) {
return String.fromCodePoint(...codePoints); return String.fromCodePoint(...codePoints);
} }
export const LOADED_EMOJIS = new Set<string>();
export function handleEmojiLoad(event: React.SyntheticEvent<HTMLImageElement>) {
event.currentTarget.classList.add('open');
LOADED_EMOJIS.add(event.currentTarget.dataset.path!);
}
export function fixNonStandardEmoji(text: string) { export function fixNonStandardEmoji(text: string) {
// Non-standard sequences typically parsed as separate emojis, so no need to fix text without any // Non-standard sequences typically parsed as separate emojis, so no need to fix text without any
if (!text.match(EMOJI_REGEX)) return text; 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) { if (emoji.charCodeAt(i + 1) >= 0xdc00 && emoji.charCodeAt(i + 1) <= 0xdfff) {
pairs.push( pairs.push(
(emoji.charCodeAt(i) - 0xd800) * 0x400 (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) { } else if (emoji.charCodeAt(i) < 0xd800 || emoji.charCodeAt(i) > 0xdfff) {