separate emojis and sticker groups logic

This commit is contained in:
Ajay Bura 2025-09-13 15:10:48 +05:30
parent 778c3f2eb3
commit cd963d91d3

View file

@ -4,7 +4,6 @@ import React, {
MouseEventHandler, MouseEventHandler,
UIEventHandler, UIEventHandler,
ReactNode, ReactNode,
memo,
useCallback, useCallback,
useEffect, useEffect,
useMemo, useMemo,
@ -15,10 +14,10 @@ import FocusTrap from 'focus-trap-react';
import { isKeyHotkey } from 'is-hotkey'; import { isKeyHotkey } from 'is-hotkey';
import classNames from 'classnames'; import classNames from 'classnames';
import { MatrixClient, Room } from 'matrix-js-sdk'; import { MatrixClient, Room } from 'matrix-js-sdk';
import { atom, useAtomValue, useSetAtom } from 'jotai'; import { Atom, atom, useAtomValue, useSetAtom } from 'jotai';
import * as css from './EmojiBoard.css'; import * as css from './EmojiBoard.css';
import { EmojiGroupId, IEmoji, IEmojiGroup, emojiGroups, emojis } from '../../plugins/emoji'; import { IEmoji, IEmojiGroup, emojiGroups, emojis } from '../../plugins/emoji';
import { IEmojiGroupLabels, useEmojiGroupLabels } from './useEmojiGroupLabels'; import { IEmojiGroupLabels, useEmojiGroupLabels } from './useEmojiGroupLabels';
import { IEmojiGroupIcons, useEmojiGroupIcons } from './useEmojiGroupIcons'; import { IEmojiGroupIcons, useEmojiGroupIcons } from './useEmojiGroupIcons';
import { preventScrollWithArrowKey, stopPropagation } from '../../utils/keyboard'; import { preventScrollWithArrowKey, stopPropagation } from '../../utils/keyboard';
@ -129,109 +128,120 @@ export const EmojiGroup = as<
</Box> </Box>
)); ));
function RecentEmojiSidebarStack({ onItemClick }: { onItemClick: (id: string) => void }) { type EmojiSidebarProps = {
const activeGroupId = useAtomValue(activeGroupIdAtom); activeGroupAtom: Atom<string | undefined>;
handleOpenGroup: (groupId: string) => void;
return (
<SidebarStack>
<GroupIcon
active={activeGroupId === RECENT_GROUP_ID}
id={RECENT_GROUP_ID}
label="Recent"
icon={Icons.RecentClock}
onClick={onItemClick}
/>
</SidebarStack>
);
}
function ImagePackSidebarStack({
mx,
packs,
usage,
onItemClick,
useAuthentication,
}: {
mx: MatrixClient;
packs: ImagePack[]; packs: ImagePack[];
usage: ImageUsage;
onItemClick: (id: string) => void;
useAuthentication?: boolean;
}) {
const activeGroupId = useAtomValue(activeGroupIdAtom);
return (
<SidebarStack>
{usage === ImageUsage.Emoticon && <SidebarDivider />}
{packs.map((pack) => {
let label = pack.meta.name;
if (!label) label = isUserId(pack.id) ? 'Personal Pack' : mx.getRoom(pack.id)?.name;
const url =
mxcUrlToHttp(mx, pack.getAvatarUrl(usage) ?? '', useAuthentication) || pack.meta.avatar;
return (
<ImageGroupIcon
key={pack.id}
active={activeGroupId === pack.id}
id={pack.id}
label={label ?? 'Unknown Pack'}
url={url}
onClick={onItemClick}
/>
);
})}
</SidebarStack>
);
}
function NativeEmojiSidebarStack({
groups,
icons,
labels,
onItemClick,
}: {
groups: IEmojiGroup[]; groups: IEmojiGroup[];
icons: IEmojiGroupIcons; icons: IEmojiGroupIcons;
labels: IEmojiGroupLabels; labels: IEmojiGroupLabels;
onItemClick: (id: EmojiGroupId) => void; };
}) { function EmojiSidebar({
const activeGroupId = useAtomValue(activeGroupIdAtom); activeGroupAtom,
handleOpenGroup,
packs,
groups,
icons,
labels,
}: EmojiSidebarProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const activeGroupId = useAtomValue(activeGroupAtom);
const usage = ImageUsage.Emoticon;
return ( return (
<SidebarStack className={css.NativeEmojiSidebarStack}> <Sidebar>
<SidebarDivider /> <SidebarStack>
{groups.map((group) => (
<GroupIcon <GroupIcon
key={group.id} active={activeGroupId === RECENT_GROUP_ID}
active={activeGroupId === group.id} id={RECENT_GROUP_ID}
id={group.id} label="Recent"
label={labels[group.id]} icon={Icons.RecentClock}
icon={icons[group.id]} onClick={handleOpenGroup}
onClick={onItemClick}
/> />
))} </SidebarStack>
</SidebarStack> {packs.length > 0 && (
<SidebarStack>
<SidebarDivider />
{packs.map((pack) => {
let label = pack.meta.name;
if (!label) label = isUserId(pack.id) ? 'Personal Pack' : mx.getRoom(pack.id)?.name;
const url =
mxcUrlToHttp(mx, pack.getAvatarUrl(usage) ?? '', useAuthentication) ||
pack.meta.avatar;
return (
<ImageGroupIcon
key={pack.id}
active={activeGroupId === pack.id}
id={pack.id}
label={label ?? 'Unknown Pack'}
url={url}
onClick={handleOpenGroup}
/>
);
})}
</SidebarStack>
)}
<SidebarStack className={css.NativeEmojiSidebarStack}>
<SidebarDivider />
{groups.map((group) => (
<GroupIcon
key={group.id}
active={activeGroupId === group.id}
id={group.id}
label={labels[group.id]}
icon={icons[group.id]}
onClick={handleOpenGroup}
/>
))}
</SidebarStack>
</Sidebar>
); );
} }
export function RecentEmojiGroup({ type StickerSidebarProps = {
label, activeGroupAtom: Atom<string | undefined>;
id, handleOpenGroup: (groupId: string) => void;
emojis: recentEmojis, packs: ImagePack[];
}: { };
label: string; function StickerSidebar({ activeGroupAtom, handleOpenGroup, packs }: StickerSidebarProps) {
id: string; const mx = useMatrixClient();
emojis: IEmoji[]; const useAuthentication = useMediaAuthentication();
}) {
const activeGroupId = useAtomValue(activeGroupAtom);
const usage = ImageUsage.Sticker;
return ( return (
<EmojiGroup key={id} id={id} label={label}> <Sidebar>
{recentEmojis.map((emoji) => ( <SidebarStack>
<EmojiItem key={emoji.shortcode} emoji={emoji} /> <SidebarDivider />
))} {packs.map((pack) => {
</EmojiGroup> let label = pack.meta.name;
if (!label) label = isUserId(pack.id) ? 'Personal Pack' : mx.getRoom(pack.id)?.name;
const url =
mxcUrlToHttp(mx, pack.getAvatarUrl(usage) ?? '', useAuthentication) || pack.meta.avatar;
return (
<ImageGroupIcon
key={pack.id}
active={activeGroupId === pack.id}
id={pack.id}
label={label ?? 'Unknown Pack'}
url={url}
onClick={handleOpenGroup}
/>
);
})}
</SidebarStack>
</Sidebar>
); );
} }
export function SearchEmojiGroup({ export function SearchGroup({
mx, mx,
tab, tab,
label, label,
@ -275,18 +285,51 @@ export function SearchEmojiGroup({
); );
} }
export const CustomEmojiGroups = memo( type StickerGroupsProps = {
({ packs: ImagePack[];
mx, };
groups, function StickerGroups({ packs }: StickerGroupsProps) {
useAuthentication, const mx = useMatrixClient();
}: { const useAuthentication = useMediaAuthentication();
mx: MatrixClient;
groups: ImagePack[]; if (packs.length === 0) {
useAuthentication?: boolean; return <NoStickerPacks />;
}) => ( }
return packs.map((pack) => (
<EmojiGroup key={pack.id} id={pack.id} label={pack.meta.name || 'Unknown'}>
{pack
.getImages(ImageUsage.Sticker)
.sort((a, b) => a.shortcode.localeCompare(b.shortcode))
.map((image) => (
<StickerItem
key={image.shortcode}
mx={mx}
useAuthentication={useAuthentication}
image={image}
/>
))}
</EmojiGroup>
));
}
type EmojiGroupsProps = {
recentEmojis: IEmoji[];
packs: ImagePack[];
groups: IEmojiGroup[];
labels: IEmojiGroupLabels;
};
export function EmojiGroups({ recentEmojis, packs, groups, labels }: EmojiGroupsProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
return (
<> <>
{groups.map((pack) => ( <EmojiGroup id={RECENT_GROUP_ID} label="Recent">
{recentEmojis.map((emoji) => (
<EmojiItem key={emoji.shortcode} emoji={emoji} />
))}
</EmojiGroup>
{packs.map((pack) => (
<EmojiGroup key={pack.id} id={pack.id} label={pack.meta.name || 'Unknown'}> <EmojiGroup key={pack.id} id={pack.id} label={pack.meta.name || 'Unknown'}>
{pack {pack
.getImages(ImageUsage.Emoticon) .getImages(ImageUsage.Emoticon)
@ -301,44 +344,6 @@ export const CustomEmojiGroups = memo(
))} ))}
</EmojiGroup> </EmojiGroup>
))} ))}
</>
)
);
export const StickerGroups = memo(
({
mx,
groups,
useAuthentication,
}: {
mx: MatrixClient;
groups: ImagePack[];
useAuthentication?: boolean;
}) =>
groups.length === 0 ? (
<NoStickerPacks />
) : (
groups.map((pack) => (
<EmojiGroup key={pack.id} id={pack.id} label={pack.meta.name || 'Unknown'}>
{pack
.getImages(ImageUsage.Sticker)
.sort((a, b) => a.shortcode.localeCompare(b.shortcode))
.map((image) => (
<StickerItem
key={image.shortcode}
mx={mx}
useAuthentication={useAuthentication}
image={image}
/>
))}
</EmojiGroup>
))
)
);
export const NativeEmojiGroups = memo(
({ groups, labels }: { groups: IEmojiGroup[]; labels: IEmojiGroupLabels }) => (
<>
{groups.map((emojiGroup) => ( {groups.map((emojiGroup) => (
<EmojiGroup key={emojiGroup.id} id={emojiGroup.id} label={labels[emojiGroup.id]}> <EmojiGroup key={emojiGroup.id} id={emojiGroup.id} label={labels[emojiGroup.id]}>
{emojiGroup.emojis.map((emoji) => ( {emojiGroup.emojis.map((emoji) => (
@ -347,8 +352,8 @@ export const NativeEmojiGroups = memo(
</EmojiGroup> </EmojiGroup>
))} ))}
</> </>
) );
); }
const DefaultEmojiPreview: PreviewData = { key: '🙂', shortcode: 'slight_smile' }; const DefaultEmojiPreview: PreviewData = { key: '🙂', shortcode: 'slight_smile' };
@ -359,6 +364,19 @@ const SEARCH_OPTIONS: UseAsyncSearchOptions = {
}, },
}; };
type EmojiBoardProps = {
tab?: EmojiBoardTab;
onTabChange?: (tab: EmojiBoardTab) => void;
imagePackRooms: Room[];
requestClose: () => void;
returnFocusOnDeactivate?: boolean;
onEmojiSelect?: (unicode: string, shortcode: string) => void;
onCustomEmojiSelect?: (mxc: string, shortcode: string) => void;
onStickerSelect?: (mxc: string, shortcode: string, label: string) => void;
allowTextCustomEmoji?: boolean;
addToRecentEmoji?: boolean;
};
export function EmojiBoard({ export function EmojiBoard({
tab = EmojiBoardTab.Emoji, tab = EmojiBoardTab.Emoji,
onTabChange, onTabChange,
@ -370,20 +388,8 @@ export function EmojiBoard({
onStickerSelect, onStickerSelect,
allowTextCustomEmoji, allowTextCustomEmoji,
addToRecentEmoji = true, addToRecentEmoji = true,
}: { }: EmojiBoardProps) {
tab?: EmojiBoardTab;
onTabChange?: (tab: EmojiBoardTab) => void;
imagePackRooms: Room[];
requestClose: () => void;
returnFocusOnDeactivate?: boolean;
onEmojiSelect?: (unicode: string, shortcode: string) => void;
onCustomEmojiSelect?: (mxc: string, shortcode: string) => void;
onStickerSelect?: (mxc: string, shortcode: string, label: string) => void;
allowTextCustomEmoji?: boolean;
addToRecentEmoji?: boolean;
}) {
const emojiTab = tab === EmojiBoardTab.Emoji; const emojiTab = tab === EmojiBoardTab.Emoji;
const stickerTab = tab === EmojiBoardTab.Sticker;
const usage = emojiTab ? ImageUsage.Emoticon : ImageUsage.Sticker; const usage = emojiTab ? ImageUsage.Emoticon : ImageUsage.Sticker;
const previewAtom = useMemo( const previewAtom = useMemo(
@ -541,28 +547,22 @@ export function EmojiBoard({
</Box> </Box>
} }
sidebar={ sidebar={
<Sidebar> emojiTab ? (
{emojiTab && recentEmojis.length > 0 && ( <EmojiSidebar
<RecentEmojiSidebarStack onItemClick={handleScrollToGroup} /> activeGroupAtom={activeGroupIdAtom}
)} handleOpenGroup={handleScrollToGroup}
{imagePacks.length > 0 && ( packs={imagePacks}
<ImagePackSidebarStack groups={emojiGroups}
mx={mx} icons={emojiGroupIcons}
usage={usage} labels={emojiGroupLabels}
packs={imagePacks} />
onItemClick={handleScrollToGroup} ) : (
useAuthentication={useAuthentication} <StickerSidebar
/> activeGroupAtom={activeGroupIdAtom}
)} handleOpenGroup={handleScrollToGroup}
{emojiTab && ( packs={imagePacks}
<NativeEmojiSidebarStack />
groups={emojiGroups} )
icons={emojiGroupIcons}
labels={emojiGroupLabels}
onItemClick={handleScrollToGroup}
/>
)}
</Sidebar>
} }
> >
<Box grow="Yes"> <Box grow="Yes">
@ -581,7 +581,7 @@ export function EmojiBoard({
gap="200" gap="200"
> >
{searchedItems && ( {searchedItems && (
<SearchEmojiGroup <SearchGroup
mx={mx} mx={mx}
tab={tab} tab={tab}
id={SEARCH_GROUP_ID} id={SEARCH_GROUP_ID}
@ -590,20 +590,16 @@ export function EmojiBoard({
useAuthentication={useAuthentication} useAuthentication={useAuthentication}
/> />
)} )}
{emojiTab && recentEmojis.length > 0 && ( {emojiTab ? (
<RecentEmojiGroup id={RECENT_GROUP_ID} label="Recent" emojis={recentEmojis} /> <EmojiGroups
)} recentEmojis={recentEmojis}
{emojiTab && ( packs={imagePacks}
<CustomEmojiGroups groups={emojiGroups}
mx={mx} labels={emojiGroupLabels}
groups={imagePacks}
useAuthentication={useAuthentication}
/> />
) : (
<StickerGroups packs={imagePacks} />
)} )}
{stickerTab && (
<StickerGroups mx={mx} groups={imagePacks} useAuthentication={useAuthentication} />
)}
{emojiTab && <NativeEmojiGroups groups={emojiGroups} labels={emojiGroupLabels} />}
</Box> </Box>
</Scroll> </Scroll>
</Box> </Box>