From fd9f734de18c78981392beea58013c988133d1dc Mon Sep 17 00:00:00 2001 From: Emi Date: Wed, 29 Dec 2021 23:02:49 -0500 Subject: [PATCH] Add custom emoji to emoji board (#210) * Display custom emoji in picker Adds a single category at the start of the emoji picker to display the user's custom emoji * Show any amount of custom emoji packs in the Emoji Board * Use thumbnails in emoji picker + mark as emoji * Fix emoji picker stretching when too many packs are available * Sprinkle in a few comments for good measure * Remove emoji-less packs from the emoji picker --- src/app/organisms/emoji-board/EmojiBoard.jsx | 147 ++++++++++++++---- src/app/organisms/emoji-board/EmojiBoard.scss | 4 + src/app/organisms/emoji-board/custom-emoji.js | 4 +- 3 files changed, 128 insertions(+), 27 deletions(-) diff --git a/src/app/organisms/emoji-board/EmojiBoard.jsx b/src/app/organisms/emoji-board/EmojiBoard.jsx index 9175576b..fa28b4da 100644 --- a/src/app/organisms/emoji-board/EmojiBoard.jsx +++ b/src/app/organisms/emoji-board/EmojiBoard.jsx @@ -7,6 +7,10 @@ import './EmojiBoard.scss'; import parse from 'html-react-parser'; import twemoji from 'twemoji'; import { emojiGroups, emojis } from './emoji'; +import { getRelevantPacks } from './custom-emoji'; +import initMatrix from '../../../client/initMatrix'; +import cons from '../../../client/state/cons'; +import navigation from '../../../client/state/navigation'; import AsyncSearch from '../../../util/AsyncSearch'; import Text from '../../atoms/text/Text'; @@ -16,6 +20,7 @@ import Input from '../../atoms/input/Input'; import ScrollView from '../../atoms/scroll/ScrollView'; import SearchIC from '../../../../public/res/ic/outlined/search.svg'; +import StarIC from '../../../../public/res/ic/outlined/star.svg'; import EmojiIC from '../../../../public/res/ic/outlined/emoji.svg'; import DogIC from '../../../../public/res/ic/outlined/dog.svg'; import CupIC from '../../../../public/res/ic/outlined/cup.svg'; @@ -40,16 +45,30 @@ function EmojiGroup({ name, groupEmojis }) { emojiRow.push( { - parse(twemoji.parse( - emoji.unicode, - { - attributes: () => ({ - unicode: emoji.unicode, - shortcodes: emoji.shortcodes?.toString(), - hexcode: emoji.hexcode, - }), - }, - )) + emoji.hexcode + // This is a unicode emoji, and should be rendered with twemoji + ? parse(twemoji.parse( + emoji.unicode, + { + attributes: () => ({ + unicode: emoji.unicode, + shortcodes: emoji.shortcodes?.toString(), + hexcode: emoji.hexcode, + }), + }, + )) + // This is a custom emoji, and should be render as an mxc + : ( + {emoji.shortcode} + ) } , ); @@ -72,6 +91,8 @@ EmojiGroup.propTypes = { length: PropTypes.number, unicode: PropTypes.string, hexcode: PropTypes.string, + mxc: PropTypes.string, + shortcode: PropTypes.string, shortcodes: PropTypes.oneOfType([ PropTypes.string, PropTypes.arrayOf(PropTypes.string), @@ -133,8 +154,8 @@ function EmojiBoard({ onSelect }) { const infoEmoji = emojiInfo.current.firstElementChild.firstElementChild; const infoShortcode = emojiInfo.current.lastElementChild; - const emojiSrc = infoEmoji.src; - infoEmoji.src = `${emojiSrc.slice(0, emojiSrc.lastIndexOf('/') + 1)}${emoji.hexcode.toLowerCase()}.png`; + infoEmoji.src = emoji.src; + infoEmoji.alt = emoji.unicode; infoShortcode.textContent = `:${emoji.shortcode}:`; } @@ -142,16 +163,21 @@ function EmojiBoard({ onSelect }) { if (isTargetNotEmoji(e.target)) return; const emoji = e.target; - const { shortcodes, hexcode } = getEmojiDataFromTarget(emoji); + const { shortcodes, unicode } = getEmojiDataFromTarget(emoji); + const { src } = e.target; if (typeof shortcodes === 'undefined') { searchRef.current.placeholder = 'Search'; - setEmojiInfo({ hexcode: '1f643', shortcode: 'slight_smile' }); + setEmojiInfo({ + unicode: '🙂', + shortcode: 'slight_smile', + src: 'https://twemoji.maxcdn.com/v/13.1.0/72x72/1f642.png', + }); return; } if (searchRef.current.placeholder === shortcodes[0]) return; searchRef.current.setAttribute('placeholder', shortcodes[0]); - setEmojiInfo({ hexcode, shortcode: shortcodes[0] }); + setEmojiInfo({ shortcode: shortcodes[0], src, unicode }); } function handleSearchChange(e) { @@ -160,11 +186,44 @@ function EmojiBoard({ onSelect }) { scrollEmojisRef.current.scrollTop = 0; } + const [availableEmojis, setAvailableEmojis] = useState([]); + + // This should be called whenever the room changes, so that we can switch out the emoji + // for whatever packs are relevant to this room + function updateAvailableEmoji(selectedRoomId) { + // Retrieve the packs for the new room + const packs = getRelevantPacks( + initMatrix.matrixClient.getRoom(selectedRoomId), + ) + // Remove packs that aren't marked as emoji packs + .filter((pack) => pack.usage.indexOf('emoticon') !== -1) + // Remove packs without emojis + .filter((pack) => pack.getEmojis().length !== 0); + + // Set an index for each pack so that we know where to jump when the user uses the nav + for (let i = 0; i < packs.length; i += 1) { + packs[i].packIndex = i; + } + + // Update the component state + setAvailableEmojis(packs); + } + + // Register the above function as an event listener + useEffect(() => { + navigation.on(cons.events.navigation.ROOM_SELECTED, updateAvailableEmoji); + return () => { + navigation.removeListener(cons.events.navigation.ROOM_SELECTED, updateAvailableEmoji); + }; + }, []); + function openGroup(groupOrder) { let tabIndex = groupOrder; const $emojiContent = scrollEmojisRef.current.firstElementChild; const groupCount = $emojiContent.childElementCount; - if (groupCount > emojiGroups.length) tabIndex += groupCount - emojiGroups.length; + if (groupCount > emojiGroups.length) { + tabIndex += groupCount - emojiGroups.length - availableEmojis.length; + } $emojiContent.children[tabIndex].scrollIntoView(); } @@ -179,6 +238,16 @@ function EmojiBoard({ onSelect }) {
+ { + availableEmojis.map((pack) => ( + + )) + } { emojiGroups.map((group) => ( @@ -192,16 +261,42 @@ function EmojiBoard({ onSelect }) { :slight_smile:
-
- openGroup(0)} src={EmojiIC} tooltip="Smileys" tooltipPlacement="right" /> - openGroup(1)} src={DogIC} tooltip="Animals" tooltipPlacement="right" /> - openGroup(2)} src={CupIC} tooltip="Food" tooltipPlacement="right" /> - openGroup(3)} src={BallIC} tooltip="Activity" tooltipPlacement="right" /> - openGroup(4)} src={PhotoIC} tooltip="Travel" tooltipPlacement="right" /> - openGroup(5)} src={BulbIC} tooltip="Objects" tooltipPlacement="right" /> - openGroup(6)} src={PeaceIC} tooltip="Symbols" tooltipPlacement="right" /> - openGroup(7)} src={FlagIC} tooltip="Flags" tooltipPlacement="right" /> -
+ +
+ { + availableEmojis.map((pack) => ( + // TODO (future PR?): Use the pack icon, and only use StarIC as a fallback + openGroup(pack.packIndex)} + src={StarIC} + key={pack.packIndex} + tooltip={pack.displayName} + tooltipPlacement="right" + /> + )) + } + { + [ + [0, EmojiIC, 'Smilies'], + [1, DogIC, 'Animals'], + [2, CupIC, 'Food'], + [3, BallIC, 'Activities'], + [4, PhotoIC, 'Travel'], + [5, BulbIC, 'Objects'], + [6, PeaceIC, 'Symbols'], + [7, FlagIC, 'Flags'], + ].map(([indx, ico, name]) => ( + openGroup(availableEmojis.length + indx)} + key={indx} + src={ico} + tooltip={name} + tooltipPlacement="right" + /> + )) + } +
+
); } diff --git a/src/app/organisms/emoji-board/EmojiBoard.scss b/src/app/organisms/emoji-board/EmojiBoard.scss index 7d68ebf3..752b074e 100644 --- a/src/app/organisms/emoji-board/EmojiBoard.scss +++ b/src/app/organisms/emoji-board/EmojiBoard.scss @@ -10,6 +10,10 @@ height: 400px; width: 286px; } + & > .scrollbar { + width: initial; + height: 400px; + } &__nav { @extend .cp-fx__column; justify-content: center; diff --git a/src/app/organisms/emoji-board/custom-emoji.js b/src/app/organisms/emoji-board/custom-emoji.js index 650a9620..91ae5143 100644 --- a/src/app/organisms/emoji-board/custom-emoji.js +++ b/src/app/organisms/emoji-board/custom-emoji.js @@ -166,4 +166,6 @@ function getEmojiForCompletion(room) { .concat(Array.from(allEmoji.values())); } -export { getUserImagePack, getShortcodeToEmoji, getEmojiForCompletion }; +export { + getUserImagePack, getShortcodeToEmoji, getRelevantPacks, getEmojiForCompletion, +};