Add recently used section to emoji board (#373)

* Add recent section to emoji board

* Add section to emoji board sidebar

* Add emoji limit like element web has

* Ignore custom emojis

* Filter out invalid emojis

* Update heart icon with clock

Co-authored-by: Krishan <33421343+kfiven@users.noreply.github.com>
This commit is contained in:
ginnyTheCat 2022-03-17 12:49:14 +01:00 committed by GitHub
parent 5a299b21c5
commit b698982186
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 63 additions and 6 deletions

View file

@ -12,6 +12,7 @@ import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation'; import navigation from '../../../client/state/navigation';
import AsyncSearch from '../../../util/AsyncSearch'; import AsyncSearch from '../../../util/AsyncSearch';
import { addRecentEmoji, getRecentEmojis } from './recent';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
import RawIcon from '../../atoms/system-icons/RawIcon'; import RawIcon from '../../atoms/system-icons/RawIcon';
@ -20,6 +21,7 @@ import Input from '../../atoms/input/Input';
import ScrollView from '../../atoms/scroll/ScrollView'; import ScrollView from '../../atoms/scroll/ScrollView';
import SearchIC from '../../../../public/res/ic/outlined/search.svg'; import SearchIC from '../../../../public/res/ic/outlined/search.svg';
import RecentClockIC from '../../../../public/res/ic/outlined/recent-clock.svg';
import EmojiIC from '../../../../public/res/ic/outlined/emoji.svg'; import EmojiIC from '../../../../public/res/ic/outlined/emoji.svg';
import DogIC from '../../../../public/res/ic/outlined/dog.svg'; import DogIC from '../../../../public/res/ic/outlined/dog.svg';
import CupIC from '../../../../public/res/ic/outlined/cup.svg'; import CupIC from '../../../../public/res/ic/outlined/cup.svg';
@ -29,10 +31,11 @@ import BulbIC from '../../../../public/res/ic/outlined/bulb.svg';
import PeaceIC from '../../../../public/res/ic/outlined/peace.svg'; import PeaceIC from '../../../../public/res/ic/outlined/peace.svg';
import FlagIC from '../../../../public/res/ic/outlined/flag.svg'; import FlagIC from '../../../../public/res/ic/outlined/flag.svg';
const ROW_EMOJIS_COUNT = 7;
const EmojiGroup = React.memo(({ name, groupEmojis }) => { const EmojiGroup = React.memo(({ name, groupEmojis }) => {
function getEmojiBoard() { function getEmojiBoard() {
const emojiBoard = []; const emojiBoard = [];
const ROW_EMOJIS_COUNT = 7;
const totalEmojis = groupEmojis.length; const totalEmojis = groupEmojis.length;
for (let r = 0; r < totalEmojis; r += ROW_EMOJIS_COUNT) { for (let r = 0; r < totalEmojis; r += ROW_EMOJIS_COUNT) {
@ -147,8 +150,9 @@ function EmojiBoard({ onSelect, searchRef }) {
function selectEmoji(e) { function selectEmoji(e) {
if (isTargetNotEmoji(e.target)) return; if (isTargetNotEmoji(e.target)) return;
const emoji = e.target; const emoji = getEmojiDataFromTarget(e.target);
onSelect(getEmojiDataFromTarget(emoji)); onSelect(emoji);
if (emoji.hexcode) addRecentEmoji(emoji.unicode);
} }
function setEmojiInfo(emoji) { function setEmojiInfo(emoji) {
@ -188,6 +192,9 @@ function EmojiBoard({ onSelect, searchRef }) {
} }
const [availableEmojis, setAvailableEmojis] = useState([]); const [availableEmojis, setAvailableEmojis] = useState([]);
const [recentEmojis, setRecentEmojis] = useState([]);
const recentOffset = recentEmojis.length > 0 ? 1 : 0;
useEffect(() => { useEffect(() => {
const updateAvailableEmoji = (selectedRoomId) => { const updateAvailableEmoji = (selectedRoomId) => {
@ -215,6 +222,9 @@ function EmojiBoard({ onSelect, searchRef }) {
const onOpen = () => { const onOpen = () => {
searchRef.current.value = ''; searchRef.current.value = '';
handleSearchChange(); handleSearchChange();
// only update when board is getting opened to prevent shifting UI
setRecentEmojis(getRecentEmojis(3 * ROW_EMOJIS_COUNT));
}; };
navigation.on(cons.events.navigation.ROOM_SELECTED, updateAvailableEmoji); navigation.on(cons.events.navigation.ROOM_SELECTED, updateAvailableEmoji);
@ -230,7 +240,7 @@ function EmojiBoard({ onSelect, searchRef }) {
const $emojiContent = scrollEmojisRef.current.firstElementChild; const $emojiContent = scrollEmojisRef.current.firstElementChild;
const groupCount = $emojiContent.childElementCount; const groupCount = $emojiContent.childElementCount;
if (groupCount > emojiGroups.length) { if (groupCount > emojiGroups.length) {
tabIndex += groupCount - emojiGroups.length - availableEmojis.length; tabIndex += groupCount - emojiGroups.length - availableEmojis.length - recentOffset;
} }
$emojiContent.children[tabIndex].scrollIntoView(); $emojiContent.children[tabIndex].scrollIntoView();
} }
@ -246,6 +256,7 @@ function EmojiBoard({ onSelect, searchRef }) {
<ScrollView ref={scrollEmojisRef} autoHide> <ScrollView ref={scrollEmojisRef} autoHide>
<div onMouseMove={hoverEmoji} onClick={selectEmoji}> <div onMouseMove={hoverEmoji} onClick={selectEmoji}>
<SearchedEmoji /> <SearchedEmoji />
{recentEmojis.length > 0 && <EmojiGroup name="Recently used" groupEmojis={recentEmojis} />}
{ {
availableEmojis.map((pack) => ( availableEmojis.map((pack) => (
<EmojiGroup <EmojiGroup
@ -271,13 +282,21 @@ function EmojiBoard({ onSelect, searchRef }) {
</div> </div>
<ScrollView invisible> <ScrollView invisible>
<div className="emoji-board__nav"> <div className="emoji-board__nav">
{recentEmojis.length > 0 && (
<IconButton
onClick={() => openGroup(0)}
src={RecentClockIC}
tooltip="Recent"
tooltipPlacement="right"
/>
)}
<div className="emoji-board__nav-custom"> <div className="emoji-board__nav-custom">
{ {
availableEmojis.map((pack) => { availableEmojis.map((pack) => {
const src = initMatrix.matrixClient.mxcUrlToHttp(pack.avatar ?? pack.images[0].mxc); const src = initMatrix.matrixClient.mxcUrlToHttp(pack.avatar ?? pack.images[0].mxc);
return ( return (
<IconButton <IconButton
onClick={() => openGroup(pack.packIndex)} onClick={() => openGroup(recentOffset + pack.packIndex)}
src={src} src={src}
key={pack.packIndex} key={pack.packIndex}
tooltip={pack.displayName} tooltip={pack.displayName}
@ -301,7 +320,7 @@ function EmojiBoard({ onSelect, searchRef }) {
[7, FlagIC, 'Flags'], [7, FlagIC, 'Flags'],
].map(([indx, ico, name]) => ( ].map(([indx, ico, name]) => (
<IconButton <IconButton
onClick={() => openGroup(availableEmojis.length + indx)} onClick={() => openGroup(recentOffset + availableEmojis.length + indx)}
key={indx} key={indx}
src={ico} src={ico}
tooltip={name} tooltip={name}

View file

@ -0,0 +1,36 @@
import initMatrix from '../../../client/initMatrix';
import { emojis } from './emoji';
const eventType = 'io.element.recent_emoji';
function getRecentEmojisRaw() {
return initMatrix.matrixClient.getAccountData(eventType).getContent().recent_emoji ?? [];
}
export function getRecentEmojis(limit) {
const res = [];
getRecentEmojisRaw()
.sort((a, b) => b[1] - a[1])
.find(([unicode]) => {
const emoji = emojis.find((e) => e.unicode === unicode);
if (emoji) return res.push(emoji) >= limit;
return false;
});
return res;
}
export function addRecentEmoji(unicode) {
const recent = getRecentEmojisRaw();
const i = recent.findIndex(([u]) => u === unicode);
let entry;
if (i < 0) {
entry = [unicode, 1];
} else {
[entry] = recent.splice(i, 1);
entry[1] += 1;
}
recent.unshift(entry);
initMatrix.matrixClient.setAccountData(eventType, {
recent_emoji: recent.slice(0, 100),
});
}

View file

@ -21,6 +21,7 @@ import AsyncSearch from '../../../util/AsyncSearch';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
import ScrollView from '../../atoms/scroll/ScrollView'; import ScrollView from '../../atoms/scroll/ScrollView';
import FollowingMembers from '../../molecules/following-members/FollowingMembers'; import FollowingMembers from '../../molecules/following-members/FollowingMembers';
import { addRecentEmoji } from '../emoji-board/recent';
const commands = [{ const commands = [{
name: 'markdown', name: 'markdown',
@ -237,6 +238,7 @@ function RoomViewCmdBar({ roomId, roomTimeline, viewEvent }) {
viewEvent.emit('cmd_fired'); viewEvent.emit('cmd_fired');
} }
if (myCmd.prefix === ':') { if (myCmd.prefix === ':') {
if (!myCmd.result.mxc) addRecentEmoji(myCmd.result.unicode);
viewEvent.emit('cmd_fired', { viewEvent.emit('cmd_fired', {
replace: myCmd.result.mxc ? `:${myCmd.result.shortcode}: ` : myCmd.result.unicode, replace: myCmd.result.mxc ? `:${myCmd.result.shortcode}: ` : myCmd.result.unicode,
}); });