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,
|
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({
|
export const EmojiGroup = style({
|
||||||
padding: `${config.space.S300} 0`,
|
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([
|
export const EmojiItem = style([
|
||||||
DefaultReset,
|
DefaultReset,
|
||||||
FocusOutline,
|
FocusOutline,
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,9 @@ import {
|
||||||
SidebarBtn,
|
SidebarBtn,
|
||||||
Sidebar,
|
Sidebar,
|
||||||
NoStickerPacks,
|
NoStickerPacks,
|
||||||
|
createPreviewDataAtom,
|
||||||
|
Preview,
|
||||||
|
PreviewData,
|
||||||
} from './components';
|
} from './components';
|
||||||
import { EmojiBoardTab, EmojiItemInfo, EmojiType } from './types';
|
import { EmojiBoardTab, EmojiItemInfo, EmojiType } from './types';
|
||||||
|
|
||||||
|
|
@ -68,35 +71,14 @@ const getEmojiItemInfo = (element: Element): EmojiItemInfo | undefined => {
|
||||||
|
|
||||||
const activeGroupIdAtom = atom<string | undefined>(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<
|
const EmojiBoardLayout = as<
|
||||||
'div',
|
'div',
|
||||||
{
|
{
|
||||||
header: ReactNode;
|
header: ReactNode;
|
||||||
sidebar?: ReactNode;
|
sidebar?: ReactNode;
|
||||||
footer?: ReactNode;
|
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
>(({ className, header, sidebar, footer, children, ...props }, ref) => (
|
>(({ className, header, sidebar, children, ...props }, ref) => (
|
||||||
<Box
|
<Box
|
||||||
display="InlineFlex"
|
display="InlineFlex"
|
||||||
className={classNames(css.Base, className)}
|
className={classNames(css.Base, className)}
|
||||||
|
|
@ -105,9 +87,10 @@ const EmojiBoardLayout = as<
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
<Box direction="Column" grow="Yes">
|
<Box direction="Column" grow="Yes">
|
||||||
|
<Box className={css.Header} direction="Column" shrink="No">
|
||||||
{header}
|
{header}
|
||||||
|
</Box>
|
||||||
{children}
|
{children}
|
||||||
{footer}
|
|
||||||
</Box>
|
</Box>
|
||||||
<Line size="300" direction="Vertical" />
|
<Line size="300" direction="Vertical" />
|
||||||
{sidebar}
|
{sidebar}
|
||||||
|
|
@ -439,10 +422,11 @@ export const StickerGroups = memo(
|
||||||
mx: MatrixClient;
|
mx: MatrixClient;
|
||||||
groups: ImagePack[];
|
groups: ImagePack[];
|
||||||
useAuthentication?: boolean;
|
useAuthentication?: boolean;
|
||||||
}) => (
|
}) =>
|
||||||
<>
|
groups.length === 0 ? (
|
||||||
{groups.length === 0 && <NoStickerPacks />}
|
<NoStickerPacks />
|
||||||
{groups.map((pack) => (
|
) : (
|
||||||
|
groups.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.Sticker)
|
.getImages(ImageUsage.Sticker)
|
||||||
|
|
@ -464,8 +448,7 @@ export const StickerGroups = memo(
|
||||||
</StickerItem>
|
</StickerItem>
|
||||||
))}
|
))}
|
||||||
</EmojiGroup>
|
</EmojiGroup>
|
||||||
))}
|
))
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -491,6 +474,8 @@ export const NativeEmojiGroups = memo(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const DefaultEmojiPreview: PreviewData = { key: '🙂', shortcode: 'slight_smile' };
|
||||||
|
|
||||||
const SEARCH_OPTIONS: UseAsyncSearchOptions = {
|
const SEARCH_OPTIONS: UseAsyncSearchOptions = {
|
||||||
limit: 1000,
|
limit: 1000,
|
||||||
matchOptions: {
|
matchOptions: {
|
||||||
|
|
@ -525,6 +510,11 @@ export function EmojiBoard({
|
||||||
const stickerTab = tab === EmojiBoardTab.Sticker;
|
const stickerTab = tab === EmojiBoardTab.Sticker;
|
||||||
const usage = emojiTab ? ImageUsage.Emoticon : ImageUsage.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 setActiveGroupId = useSetAtom(activeGroupIdAtom);
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const useAuthentication = useMediaAuthentication();
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
@ -534,8 +524,6 @@ export function EmojiBoard({
|
||||||
const recentEmojis = useRecentEmoji(mx, 21);
|
const recentEmojis = useRecentEmoji(mx, 21);
|
||||||
|
|
||||||
const contentScrollRef = useRef<HTMLDivElement>(null);
|
const contentScrollRef = useRef<HTMLDivElement>(null);
|
||||||
const emojiPreviewRef = useRef<HTMLDivElement>(null);
|
|
||||||
const emojiPreviewTextRef = useRef<HTMLParagraphElement>(null);
|
|
||||||
|
|
||||||
const searchList = useMemo(() => {
|
const searchList = useMemo(() => {
|
||||||
let list: Array<PackImageReader | IEmoji> = [];
|
let list: Array<PackImageReader | IEmoji> = [];
|
||||||
|
|
@ -615,23 +603,14 @@ export function EmojiBoard({
|
||||||
const handleEmojiPreview = useCallback(
|
const handleEmojiPreview = useCallback(
|
||||||
(element: HTMLButtonElement) => {
|
(element: HTMLButtonElement) => {
|
||||||
const emojiInfo = getEmojiItemInfo(element);
|
const emojiInfo = getEmojiItemInfo(element);
|
||||||
if (!emojiInfo || !emojiPreviewTextRef.current) return;
|
if (!emojiInfo) return;
|
||||||
if (emojiInfo.type === EmojiType.Emoji && emojiPreviewRef.current) {
|
|
||||||
emojiPreviewRef.current.textContent = emojiInfo.data;
|
setPreviewData({
|
||||||
} else if (emojiInfo.type === EmojiType.CustomEmoji && emojiPreviewRef.current) {
|
key: emojiInfo.data,
|
||||||
const img = document.createElement('img');
|
shortcode: emojiInfo.shortcode,
|
||||||
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}:`;
|
|
||||||
},
|
},
|
||||||
[mx, useAuthentication]
|
[setPreviewData]
|
||||||
);
|
);
|
||||||
|
|
||||||
const throttleEmojiHover = useThrottle(handleEmojiPreview, {
|
const throttleEmojiHover = useThrottle(handleEmojiPreview, {
|
||||||
|
|
@ -675,7 +654,6 @@ export function EmojiBoard({
|
||||||
>
|
>
|
||||||
<EmojiBoardLayout
|
<EmojiBoardLayout
|
||||||
header={
|
header={
|
||||||
<Header>
|
|
||||||
<Box direction="Column" gap="200">
|
<Box direction="Column" gap="200">
|
||||||
{onTabChange && <EmojiBoardTabs tab={tab} onTabChange={onTabChange} />}
|
{onTabChange && <EmojiBoardTabs tab={tab} onTabChange={onTabChange} />}
|
||||||
<SearchInput
|
<SearchInput
|
||||||
|
|
@ -685,7 +663,6 @@ export function EmojiBoard({
|
||||||
onTextCustomEmojiSelect={handleTextCustomEmojiSelect}
|
onTextCustomEmojiSelect={handleTextCustomEmojiSelect}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Header>
|
|
||||||
}
|
}
|
||||||
sidebar={
|
sidebar={
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
|
|
@ -711,34 +688,8 @@ export function EmojiBoard({
|
||||||
)}
|
)}
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
}
|
}
|
||||||
footer={
|
|
||||||
emojiTab ? (
|
|
||||||
<Footer>
|
|
||||||
<Box
|
|
||||||
display="InlineFlex"
|
|
||||||
ref={emojiPreviewRef}
|
|
||||||
className={css.EmojiPreview}
|
|
||||||
alignItems="Center"
|
|
||||||
justifyContent="Center"
|
|
||||||
>
|
>
|
||||||
😃
|
<Box grow="Yes">
|
||||||
</Box>
|
|
||||||
<Text ref={emojiPreviewTextRef} size="H5" truncate>
|
|
||||||
:smiley:
|
|
||||||
</Text>
|
|
||||||
</Footer>
|
|
||||||
) : (
|
|
||||||
imagePacks.length > 0 && (
|
|
||||||
<Footer>
|
|
||||||
<Text ref={emojiPreviewTextRef} size="H5" truncate>
|
|
||||||
:smiley:
|
|
||||||
</Text>
|
|
||||||
</Footer>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Content>
|
|
||||||
<Scroll
|
<Scroll
|
||||||
ref={contentScrollRef}
|
ref={contentScrollRef}
|
||||||
size="400"
|
size="400"
|
||||||
|
|
@ -779,7 +730,8 @@ export function EmojiBoard({
|
||||||
{emojiTab && <NativeEmojiGroups groups={emojiGroups} labels={emojiGroupLabels} />}
|
{emojiTab && <NativeEmojiGroups groups={emojiGroups} labels={emojiGroupLabels} />}
|
||||||
</Box>
|
</Box>
|
||||||
</Scroll>
|
</Scroll>
|
||||||
</Content>
|
</Box>
|
||||||
|
<Preview previewAtom={previewAtom} />
|
||||||
</EmojiBoardLayout>
|
</EmojiBoardLayout>
|
||||||
</FocusTrap>
|
</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 './Tabs';
|
||||||
export * from './Sidebar';
|
export * from './Sidebar';
|
||||||
export * from './NoStickerPacks';
|
export * from './NoStickerPacks';
|
||||||
|
export * from './Preview';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { style } from '@vanilla-extract/css';
|
import { style } from '@vanilla-extract/css';
|
||||||
import { toRem, color, config } from 'folds';
|
import { toRem, color, config, DefaultReset } from 'folds';
|
||||||
|
|
||||||
export const Sidebar = style({
|
export const Sidebar = style({
|
||||||
width: toRem(54),
|
width: toRem(54),
|
||||||
|
|
@ -20,3 +20,32 @@ export const SidebarStack = style({
|
||||||
export const SidebarDivider = style({
|
export const SidebarDivider = style({
|
||||||
width: toRem(18),
|
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