mirror of
				https://github.com/cinnyapp/cinny.git
				synced 2025-11-04 14:30: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">
 | 
			
		||||
      {header}
 | 
			
		||||
      <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,9 +448,8 @@ export const StickerGroups = memo(
 | 
			
		|||
              </StickerItem>
 | 
			
		||||
            ))}
 | 
			
		||||
        </EmojiGroup>
 | 
			
		||||
      ))}
 | 
			
		||||
    </>
 | 
			
		||||
  )
 | 
			
		||||
      ))
 | 
			
		||||
    )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const NativeEmojiGroups = memo(
 | 
			
		||||
| 
						 | 
				
			
			@ -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,17 +654,15 @@ export function EmojiBoard({
 | 
			
		|||
    >
 | 
			
		||||
      <EmojiBoardLayout
 | 
			
		||||
        header={
 | 
			
		||||
          <Header>
 | 
			
		||||
            <Box direction="Column" gap="200">
 | 
			
		||||
              {onTabChange && <EmojiBoardTabs tab={tab} onTabChange={onTabChange} />}
 | 
			
		||||
              <SearchInput
 | 
			
		||||
                query={result?.query}
 | 
			
		||||
                onChange={handleOnChange}
 | 
			
		||||
                allowTextCustomEmoji={allowTextCustomEmoji}
 | 
			
		||||
                onTextCustomEmojiSelect={handleTextCustomEmojiSelect}
 | 
			
		||||
              />
 | 
			
		||||
            </Box>
 | 
			
		||||
          </Header>
 | 
			
		||||
          <Box direction="Column" gap="200">
 | 
			
		||||
            {onTabChange && <EmojiBoardTabs tab={tab} onTabChange={onTabChange} />}
 | 
			
		||||
            <SearchInput
 | 
			
		||||
              query={result?.query}
 | 
			
		||||
              onChange={handleOnChange}
 | 
			
		||||
              allowTextCustomEmoji={allowTextCustomEmoji}
 | 
			
		||||
              onTextCustomEmojiSelect={handleTextCustomEmojiSelect}
 | 
			
		||||
            />
 | 
			
		||||
          </Box>
 | 
			
		||||
        }
 | 
			
		||||
        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