mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-04 22:40:29 +03:00
create emoji/sticker preview atom
This commit is contained in:
parent
e6a726b8fa
commit
f674482911
5 changed files with 124 additions and 110 deletions
|
|
@ -24,17 +24,6 @@ export const Header = style({
|
|||
paddingBottom: 0,
|
||||
});
|
||||
|
||||
export const Footer = style({
|
||||
padding: config.space.S200,
|
||||
margin: config.space.S300,
|
||||
marginTop: 0,
|
||||
minHeight: toRem(40),
|
||||
|
||||
borderRadius: config.radii.R400,
|
||||
backgroundColor: color.SurfaceVariant.Container,
|
||||
color: color.SurfaceVariant.OnContainer,
|
||||
});
|
||||
|
||||
export const EmojiGroup = style({
|
||||
padding: `${config.space.S300} 0`,
|
||||
});
|
||||
|
|
@ -58,16 +47,6 @@ export const EmojiGroupContent = style([
|
|||
},
|
||||
]);
|
||||
|
||||
export const EmojiPreview = style([
|
||||
DefaultReset,
|
||||
{
|
||||
width: toRem(32),
|
||||
height: toRem(32),
|
||||
fontSize: toRem(32),
|
||||
lineHeight: toRem(32),
|
||||
},
|
||||
]);
|
||||
|
||||
export const EmojiItem = style([
|
||||
DefaultReset,
|
||||
FocusOutline,
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ import {
|
|||
SidebarBtn,
|
||||
Sidebar,
|
||||
NoStickerPacks,
|
||||
createPreviewDataAtom,
|
||||
Preview,
|
||||
PreviewData,
|
||||
} from './components';
|
||||
import { EmojiBoardTab, EmojiItemInfo, EmojiType } from './types';
|
||||
|
||||
|
|
@ -68,35 +71,14 @@ const getEmojiItemInfo = (element: Element): EmojiItemInfo | undefined => {
|
|||
|
||||
const activeGroupIdAtom = atom<string | undefined>(undefined);
|
||||
|
||||
function Header({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<Box className={css.Header} direction="Column" shrink="No">
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function Content({ children }: { children: ReactNode }) {
|
||||
return <Box grow="Yes">{children}</Box>;
|
||||
}
|
||||
|
||||
function Footer({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<Box shrink="No" className={css.Footer} gap="300" alignItems="Center">
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const EmojiBoardLayout = as<
|
||||
'div',
|
||||
{
|
||||
header: ReactNode;
|
||||
sidebar?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
children: ReactNode;
|
||||
}
|
||||
>(({ className, header, sidebar, footer, children, ...props }, ref) => (
|
||||
>(({ className, header, sidebar, children, ...props }, ref) => (
|
||||
<Box
|
||||
display="InlineFlex"
|
||||
className={classNames(css.Base, className)}
|
||||
|
|
@ -105,9 +87,10 @@ const EmojiBoardLayout = as<
|
|||
ref={ref}
|
||||
>
|
||||
<Box direction="Column" grow="Yes">
|
||||
<Box className={css.Header} direction="Column" shrink="No">
|
||||
{header}
|
||||
</Box>
|
||||
{children}
|
||||
{footer}
|
||||
</Box>
|
||||
<Line size="300" direction="Vertical" />
|
||||
{sidebar}
|
||||
|
|
@ -439,10 +422,11 @@ export const StickerGroups = memo(
|
|||
mx: MatrixClient;
|
||||
groups: ImagePack[];
|
||||
useAuthentication?: boolean;
|
||||
}) => (
|
||||
<>
|
||||
{groups.length === 0 && <NoStickerPacks />}
|
||||
{groups.map((pack) => (
|
||||
}) =>
|
||||
groups.length === 0 ? (
|
||||
<NoStickerPacks />
|
||||
) : (
|
||||
groups.map((pack) => (
|
||||
<EmojiGroup key={pack.id} id={pack.id} label={pack.meta.name || 'Unknown'}>
|
||||
{pack
|
||||
.getImages(ImageUsage.Sticker)
|
||||
|
|
@ -464,8 +448,7 @@ export const StickerGroups = memo(
|
|||
</StickerItem>
|
||||
))}
|
||||
</EmojiGroup>
|
||||
))}
|
||||
</>
|
||||
))
|
||||
)
|
||||
);
|
||||
|
||||
|
|
@ -491,6 +474,8 @@ export const NativeEmojiGroups = memo(
|
|||
)
|
||||
);
|
||||
|
||||
const DefaultEmojiPreview: PreviewData = { key: '🙂', shortcode: 'slight_smile' };
|
||||
|
||||
const SEARCH_OPTIONS: UseAsyncSearchOptions = {
|
||||
limit: 1000,
|
||||
matchOptions: {
|
||||
|
|
@ -525,6 +510,11 @@ export function EmojiBoard({
|
|||
const stickerTab = tab === EmojiBoardTab.Sticker;
|
||||
const usage = emojiTab ? ImageUsage.Emoticon : ImageUsage.Sticker;
|
||||
|
||||
const previewAtom = useMemo(
|
||||
() => createPreviewDataAtom(emojiTab ? DefaultEmojiPreview : undefined),
|
||||
[emojiTab]
|
||||
);
|
||||
const setPreviewData = useSetAtom(previewAtom);
|
||||
const setActiveGroupId = useSetAtom(activeGroupIdAtom);
|
||||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
|
|
@ -534,8 +524,6 @@ export function EmojiBoard({
|
|||
const recentEmojis = useRecentEmoji(mx, 21);
|
||||
|
||||
const contentScrollRef = useRef<HTMLDivElement>(null);
|
||||
const emojiPreviewRef = useRef<HTMLDivElement>(null);
|
||||
const emojiPreviewTextRef = useRef<HTMLParagraphElement>(null);
|
||||
|
||||
const searchList = useMemo(() => {
|
||||
let list: Array<PackImageReader | IEmoji> = [];
|
||||
|
|
@ -615,23 +603,14 @@ export function EmojiBoard({
|
|||
const handleEmojiPreview = useCallback(
|
||||
(element: HTMLButtonElement) => {
|
||||
const emojiInfo = getEmojiItemInfo(element);
|
||||
if (!emojiInfo || !emojiPreviewTextRef.current) return;
|
||||
if (emojiInfo.type === EmojiType.Emoji && emojiPreviewRef.current) {
|
||||
emojiPreviewRef.current.textContent = emojiInfo.data;
|
||||
} else if (emojiInfo.type === EmojiType.CustomEmoji && emojiPreviewRef.current) {
|
||||
const img = document.createElement('img');
|
||||
img.className = css.CustomEmojiImg;
|
||||
img.setAttribute(
|
||||
'src',
|
||||
mxcUrlToHttp(mx, emojiInfo.data, useAuthentication) || emojiInfo.data
|
||||
);
|
||||
img.setAttribute('alt', emojiInfo.shortcode);
|
||||
emojiPreviewRef.current.textContent = '';
|
||||
emojiPreviewRef.current.appendChild(img);
|
||||
}
|
||||
emojiPreviewTextRef.current.textContent = `:${emojiInfo.shortcode}:`;
|
||||
if (!emojiInfo) return;
|
||||
|
||||
setPreviewData({
|
||||
key: emojiInfo.data,
|
||||
shortcode: emojiInfo.shortcode,
|
||||
});
|
||||
},
|
||||
[mx, useAuthentication]
|
||||
[setPreviewData]
|
||||
);
|
||||
|
||||
const throttleEmojiHover = useThrottle(handleEmojiPreview, {
|
||||
|
|
@ -675,7 +654,6 @@ export function EmojiBoard({
|
|||
>
|
||||
<EmojiBoardLayout
|
||||
header={
|
||||
<Header>
|
||||
<Box direction="Column" gap="200">
|
||||
{onTabChange && <EmojiBoardTabs tab={tab} onTabChange={onTabChange} />}
|
||||
<SearchInput
|
||||
|
|
@ -685,7 +663,6 @@ export function EmojiBoard({
|
|||
onTextCustomEmojiSelect={handleTextCustomEmojiSelect}
|
||||
/>
|
||||
</Box>
|
||||
</Header>
|
||||
}
|
||||
sidebar={
|
||||
<Sidebar>
|
||||
|
|
@ -711,34 +688,8 @@ export function EmojiBoard({
|
|||
)}
|
||||
</Sidebar>
|
||||
}
|
||||
footer={
|
||||
emojiTab ? (
|
||||
<Footer>
|
||||
<Box
|
||||
display="InlineFlex"
|
||||
ref={emojiPreviewRef}
|
||||
className={css.EmojiPreview}
|
||||
alignItems="Center"
|
||||
justifyContent="Center"
|
||||
>
|
||||
😃
|
||||
</Box>
|
||||
<Text ref={emojiPreviewTextRef} size="H5" truncate>
|
||||
:smiley:
|
||||
</Text>
|
||||
</Footer>
|
||||
) : (
|
||||
imagePacks.length > 0 && (
|
||||
<Footer>
|
||||
<Text ref={emojiPreviewTextRef} size="H5" truncate>
|
||||
:smiley:
|
||||
</Text>
|
||||
</Footer>
|
||||
)
|
||||
)
|
||||
}
|
||||
>
|
||||
<Content>
|
||||
<Box grow="Yes">
|
||||
<Scroll
|
||||
ref={contentScrollRef}
|
||||
size="400"
|
||||
|
|
@ -779,7 +730,8 @@ export function EmojiBoard({
|
|||
{emojiTab && <NativeEmojiGroups groups={emojiGroups} labels={emojiGroupLabels} />}
|
||||
</Box>
|
||||
</Scroll>
|
||||
</Content>
|
||||
</Box>
|
||||
<Preview previewAtom={previewAtom} />
|
||||
</EmojiBoardLayout>
|
||||
</FocusTrap>
|
||||
);
|
||||
|
|
|
|||
53
src/app/components/emoji-board/components/Preview.tsx
Normal file
53
src/app/components/emoji-board/components/Preview.tsx
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { Box, Text } from 'folds';
|
||||
import React from 'react';
|
||||
import { Atom, atom, useAtomValue } from 'jotai';
|
||||
import * as css from './styles.css';
|
||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||
import { mxcUrlToHttp } from '../../../utils/matrix';
|
||||
|
||||
export type PreviewData = {
|
||||
key: string;
|
||||
shortcode: string;
|
||||
};
|
||||
|
||||
export const createPreviewDataAtom = (initial?: PreviewData) =>
|
||||
atom<PreviewData | undefined>(initial);
|
||||
|
||||
type PreviewProps = {
|
||||
previewAtom: Atom<PreviewData | undefined>;
|
||||
};
|
||||
export function Preview({ previewAtom }: PreviewProps) {
|
||||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
|
||||
const { key, shortcode } = useAtomValue(previewAtom) ?? {};
|
||||
|
||||
if (!shortcode) return null;
|
||||
|
||||
return (
|
||||
<Box shrink="No" className={css.Preview} gap="300" alignItems="Center">
|
||||
{key && (
|
||||
<Box
|
||||
display="InlineFlex"
|
||||
className={css.PreviewEmoji}
|
||||
alignItems="Center"
|
||||
justifyContent="Center"
|
||||
>
|
||||
{key.startsWith('mxc://') ? (
|
||||
<img
|
||||
className={css.PreviewImg}
|
||||
src={mxcUrlToHttp(mx, key, useAuthentication) ?? key}
|
||||
alt={shortcode}
|
||||
/>
|
||||
) : (
|
||||
key
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
<Text size="H5" truncate>
|
||||
:{shortcode}:
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
|
@ -2,3 +2,4 @@ export * from './SearchInput';
|
|||
export * from './Tabs';
|
||||
export * from './Sidebar';
|
||||
export * from './NoStickerPacks';
|
||||
export * from './Preview';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { style } from '@vanilla-extract/css';
|
||||
import { toRem, color, config } from 'folds';
|
||||
import { toRem, color, config, DefaultReset } from 'folds';
|
||||
|
||||
export const Sidebar = style({
|
||||
width: toRem(54),
|
||||
|
|
@ -20,3 +20,32 @@ export const SidebarStack = style({
|
|||
export const SidebarDivider = style({
|
||||
width: toRem(18),
|
||||
});
|
||||
|
||||
export const Preview = style({
|
||||
padding: config.space.S200,
|
||||
margin: config.space.S300,
|
||||
marginTop: 0,
|
||||
minHeight: toRem(40),
|
||||
|
||||
borderRadius: config.radii.R400,
|
||||
backgroundColor: color.SurfaceVariant.Container,
|
||||
color: color.SurfaceVariant.OnContainer,
|
||||
});
|
||||
|
||||
export const PreviewEmoji = style([
|
||||
DefaultReset,
|
||||
{
|
||||
width: toRem(32),
|
||||
height: toRem(32),
|
||||
fontSize: toRem(32),
|
||||
lineHeight: toRem(32),
|
||||
},
|
||||
]);
|
||||
export const PreviewImg = style([
|
||||
DefaultReset,
|
||||
{
|
||||
width: toRem(32),
|
||||
height: toRem(32),
|
||||
objectFit: 'contain',
|
||||
},
|
||||
]);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue