| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- import { GiphyFetch, TrendingOptions, setServerUrl } from '@giphy/js-fetch-api';
- import { Grid } from '@giphy/react-components';
- import React, { useCallback, useEffect, useState } from 'react';
- import { useTranslation } from 'react-i18next';
- import { batch, useDispatch, useSelector } from 'react-redux';
- import { makeStyles } from 'tss-react/mui';
-
- import { createGifSentEvent } from '../../../analytics/AnalyticsEvents';
- import { sendAnalytics } from '../../../analytics/functions';
- import { IReduxState } from '../../../app/types';
- import Input from '../../../base/ui/components/web/Input';
- import { sendMessage } from '../../../chat/actions.any';
- import { SCROLL_SIZE } from '../../../filmstrip/constants';
- import { toggleReactionsMenuVisibility } from '../../../reactions/actions.web';
- import { IReactionsMenuParent } from '../../../reactions/types';
- import Drawer from '../../../toolbox/components/web/Drawer';
- import JitsiPortal from '../../../toolbox/components/web/JitsiPortal';
- import { setGifMenuVisibility } from '../../actions';
- import {
- formatGifUrlMessage,
- getGifAPIKey,
- getGifRating,
- getGifUrl,
- getGiphyProxyUrl
- } from '../../function.any';
-
- const OVERFLOW_DRAWER_PADDING = 16;
-
- const useStyles = makeStyles()(theme => {
- return {
- gifsMenu: {
- width: '100%',
- marginBottom: theme.spacing(2),
- display: 'flex',
- flexDirection: 'column',
-
- '& div:focus': {
- border: '1px solid red !important',
- boxSizing: 'border-box'
- }
- },
-
- searchField: {
- marginBottom: theme.spacing(3)
- },
-
- gifContainer: {
- height: '245px',
- overflowY: 'auto'
- },
-
- logoContainer: {
- width: `calc(100% - ${SCROLL_SIZE}px)`,
- backgroundColor: '#121119',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- color: '#fff',
- marginTop: theme.spacing(1)
- },
-
- overflowDrawerMenu: {
- padding: theme.spacing(3),
- width: '100%',
- boxSizing: 'border-box',
- height: '100%'
- },
-
- overflowMenu: {
- height: '200px',
- width: '201px',
- marginBottom: '0px'
- },
-
- gifContainerOverflow: {
- flexGrow: 1
- },
-
- drawer: {
- display: 'flex',
- height: '100%'
- }
- };
- });
-
- interface IProps {
- columns?: number;
- parent: IReactionsMenuParent;
- }
-
- /**
- * Gifs menu.
- *
- * @returns {ReactElement}
- */
- function GifsMenu({ columns = 2, parent }: IProps) {
- const API_KEY = useSelector(getGifAPIKey);
- const giphyFetch = new GiphyFetch(API_KEY);
- const [ searchKey, setSearchKey ] = useState<string>();
- const { classes: styles, cx } = useStyles();
- const dispatch = useDispatch();
- const { t } = useTranslation();
- const isInOverflowMenu
- = parent === IReactionsMenuParent.OverflowDrawer || parent === IReactionsMenuParent.OverflowMenu;
- const { clientWidth } = useSelector((state: IReduxState) => state['features/base/responsive-ui']);
- const rating = useSelector(getGifRating);
- const proxyUrl = useSelector(getGiphyProxyUrl);
-
- const fetchGifs = useCallback(async (offset = 0) => {
- const options: TrendingOptions = {
- limit: 20,
- offset,
- rating
- };
-
- if (!searchKey) {
- return await giphyFetch.trending(options);
- }
-
- return await giphyFetch.search(searchKey, options);
- }, [ searchKey ]);
-
- const onDrawerClose = useCallback(() => {
- dispatch(setGifMenuVisibility(false));
- }, []);
-
- const handleGifClick = useCallback((gif, e) => {
- e?.stopPropagation();
- const url = getGifUrl(gif, proxyUrl);
-
- sendAnalytics(createGifSentEvent());
- batch(() => {
- dispatch(sendMessage(formatGifUrlMessage(url), true));
- dispatch(toggleReactionsMenuVisibility());
- isInOverflowMenu && onDrawerClose();
- });
- }, [ dispatch, isInOverflowMenu ]);
-
- const handleGifKeyPress = useCallback((gif, e) => {
- if (e.nativeEvent.keyCode === 13) {
- handleGifClick(gif, null);
- }
- }, [ handleGifClick ]);
-
- const handleSearchKeyChange = useCallback(value => {
- setSearchKey(value);
- }, []);
-
- const handleKeyDown = useCallback(e => {
- if (!document.activeElement) {
- return;
- }
- if (e.keyCode === 38) { // up arrow
- e.preventDefault();
-
- // if the first gif is focused move focus to the input
- if (document.activeElement.previousElementSibling === null) {
- const element = document.querySelector('.gif-input') as HTMLElement;
-
- element?.focus();
- } else {
- const element = document.activeElement.previousElementSibling as HTMLElement;
-
- element?.focus();
- }
- } else if (e.keyCode === 40) { // down arrow
- e.preventDefault();
-
- // if the input is focused move focus to the first gif
- if (document.activeElement.classList.contains('gif-input')) {
- const element = document.querySelector('.giphy-gif') as HTMLElement;
-
- element?.focus();
- } else {
- const element = document.activeElement.nextElementSibling as HTMLElement;
-
- element?.focus();
- }
- }
- }, []);
-
- useEffect(() => {
- document.addEventListener('keydown', handleKeyDown);
-
- return () => document.removeEventListener('keydown', handleKeyDown);
- }, []);
-
- // For some reason, the Grid component does not do an initial call on mobile.
- // This fixes that.
- useEffect(() => setSearchKey(''), []);
-
- useEffect(() => {
- if (proxyUrl) {
- setServerUrl(proxyUrl);
- }
- }, []);
-
- const onInputKeyPress = useCallback((e: React.KeyboardEvent) => {
- e.stopPropagation();
- }, []);
-
- const gifMenu = (
- <div
- className = { cx(styles.gifsMenu,
- parent === IReactionsMenuParent.OverflowDrawer && styles.overflowDrawerMenu,
- parent === IReactionsMenuParent.OverflowMenu && styles.overflowMenu
- ) }>
- <Input
- autoFocus = { true }
- className = { cx(styles.searchField, 'gif-input') }
- id = 'gif-search-input'
- onChange = { handleSearchKeyChange }
- onKeyPress = { onInputKeyPress }
- placeholder = { t('giphy.search') }
- // eslint-disable-next-line react/jsx-no-bind
- ref = { inputElement => {
- inputElement?.focus();
- setTimeout(() => inputElement?.focus(), 200);
- } }
- type = 'text'
- value = { searchKey ?? '' } />
- <div
- className = { cx(styles.gifContainer,
- parent === IReactionsMenuParent.OverflowDrawer && styles.gifContainerOverflow) }>
- <Grid
- columns = { columns }
- fetchGifs = { fetchGifs }
- gutter = { 6 }
- hideAttribution = { true }
- key = { searchKey }
- noLink = { true }
- noResultsMessage = { t('giphy.noResults') }
- onGifClick = { handleGifClick }
- onGifKeyPress = { handleGifKeyPress }
- width = { parent === IReactionsMenuParent.OverflowDrawer
- ? clientWidth - (2 * OVERFLOW_DRAWER_PADDING) - SCROLL_SIZE
- : parent === IReactionsMenuParent.OverflowMenu ? 201 : 320
- } />
- </div>
- <div className = { styles.logoContainer }>
- <span>Powered by</span>
- <img
- alt = 'GIPHY Logo'
- src = 'images/GIPHY_logo.png' />
- </div>
- </div>
- );
-
- return parent === IReactionsMenuParent.OverflowDrawer ? (
- <JitsiPortal>
- <Drawer
- className = { styles.drawer }
- isOpen = { true }
- onClose = { onDrawerClose }>
- {gifMenu}
- </Drawer>
- </JitsiPortal>
- ) : gifMenu;
- }
-
- export default GifsMenu;
|