Skip to content
This repository has been archived by the owner on Mar 13, 2024. It is now read-only.

Commit

Permalink
Cherry pick MM-53842 to v7.8 (#12417)
Browse files Browse the repository at this point in the history
  • Loading branch information
M-ZubairAhmed authored Aug 24, 2023
1 parent 99c0709 commit 2a04b46
Show file tree
Hide file tree
Showing 19 changed files with 3,063 additions and 1,359 deletions.
32 changes: 1 addition & 31 deletions components/admin_console/admin_definition.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6022,37 +6022,7 @@ const AdminDefinition = {
label: t('admin.customization.enableGifPickerTitle'),
label_default: 'Enable GIF Picker:',
help_text: t('admin.customization.enableGifPickerDesc'),
help_text_default: 'Allow users to select GIFs from the emoji picker via a Gfycat integration.',
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.INTEGRATIONS.GIF)),
},
{
type: Constants.SettingsTypes.TYPE_TEXT,
key: 'ServiceSettings.GfycatAPIKey',
label: t('admin.customization.gfycatApiKey'),
label_default: 'Gfycat API Key:',
help_text: t('admin.customization.gfycatApiKeyDescription'),
help_text_default: 'Request an API key at <link>https://developers.gfycat.com/signup/#</link>. Enter the client ID you receive via email to this field. When blank, uses the default API key provided by Gfycat.',
help_text_markdown: false,
help_text_values: {
link: (msg) => (
<a
href='https://developers.gfycat.com/signup/#'
target='_blank'
rel='noreferrer'
>
{msg}
</a>
),
},
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.INTEGRATIONS.GIF)),
},
{
type: Constants.SettingsTypes.TYPE_TEXT,
key: 'ServiceSettings.GfycatAPISecret',
label: t('admin.customization.gfycatApiSecret'),
label_default: 'Gfycat API Secret:',
help_text: t('admin.customization.gfycatApiSecretDescription'),
help_text_default: 'The API secret generated by Gfycat for your API key. When blank, uses the default API secret provided by Gfycat.',
help_text_default: 'Allows users to select GIFs from the emoji picker.',
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.INTEGRATIONS.GIF)),
},
],
Expand Down
5 changes: 1 addition & 4 deletions components/emoji_picker/emoji_picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,13 @@ import type {PropsFromRedux} from './index';

interface Props extends PropsFromRedux {
filter: string;
visible: boolean;
onEmojiClick: (emoji: Emoji) => void;
handleFilterChange: (filter: string) => void;
handleEmojiPickerClose: () => void;
}

const EmojiPicker = ({
filter,
visible,
onEmojiClick,
handleFilterChange,
handleEmojiPickerClose,
Expand Down Expand Up @@ -125,10 +123,9 @@ const EmojiPicker = ({
throttledSearchCustomEmoji.current(filter, customEmojisEnabled);
}, [filter, shouldRunCreateCategoryAndEmojiRows.current, customEmojisEnabled]);

// Hack for getting focus on search input when tab changes to emoji from gifs
useEffect(() => {
searchInputRef.current?.focus();
}, [visible]);
}, []);

// clear out the active category on search input
useEffect(() => {
Expand Down
126 changes: 70 additions & 56 deletions components/emoji_picker/emoji_picker_tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React, {CSSProperties, PureComponent} from 'react';
import React, {CSSProperties, PureComponent, createRef, RefObject} from 'react';
import {Tab, Tabs} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';

import EmojiIcon from 'components/widgets/icons/emoji_icon';
import GfycatIcon from 'components/widgets/icons/gfycat_icon';
import {makeAsyncComponent} from 'components/async_load';
import EmojiPicker from 'components/emoji_picker';
import EmojiPickerHeader from 'components/emoji_picker/components/emoji_picker_header';
import EmojiIcon from 'components/widgets/icons/emoji_icon';
import GifIcon from 'components/widgets/icons/giphy_icon';

import {Emoji} from '@mattermost/types/emojis';

Expand All @@ -32,6 +33,8 @@ type State = {
}

export default class EmojiPickerTabs extends PureComponent<Props, State> {
private rootPickerNodeRef: RefObject<HTMLDivElement>;

static defaultProps = {
rightOffset: 0,
topOffset: 0,
Expand All @@ -45,19 +48,9 @@ export default class EmojiPickerTabs extends PureComponent<Props, State> {
emojiTabVisible: true,
filter: '',
};
}

handleEnterEmojiTab = () => {
this.setState({
emojiTabVisible: true,
});
};

handleExitEmojiTab = () => {
this.setState({
emojiTabVisible: false,
});
};
this.rootPickerNodeRef = createRef();
}

handleEmojiPickerClose = () => {
this.props.onEmojiClose();
Expand All @@ -67,6 +60,10 @@ export default class EmojiPickerTabs extends PureComponent<Props, State> {
this.setState({filter});
};

getRootPickerNode = () => {
return this.rootPickerNodeRef.current;
};

render() {
let pickerStyle;
if (this.props.style && !(this.props.style.left === 0 && this.props.style.top === 0)) {
Expand Down Expand Up @@ -99,51 +96,69 @@ export default class EmojiPickerTabs extends PureComponent<Props, State> {

if (this.props.enableGifPicker && typeof this.props.onGifClick != 'undefined') {
return (
<Tabs
defaultActiveKey={1}
id='emoji-picker-tabs'
<div
ref={this.rootPickerNodeRef}
style={pickerStyle}
className={pickerClass}
justified={true}
>
<EmojiPickerHeader handleEmojiPickerClose={this.handleEmojiPickerClose}/>
<Tab
eventKey={1}
onEnter={this.handleEnterEmojiTab}
onExit={this.handleExitEmojiTab}
title={
<div className={'custom-emoji-tab__icon__text'}>
<EmojiIcon
className='custom-emoji-tab__icon'
/>
<div>
{'Emojis'}
</div>
</div>
}
tabClassName={'custom-emoji-tab'}
>
<EmojiPicker
filter={this.state.filter}
visible={this.state.emojiTabVisible}
onEmojiClick={this.props.onEmojiClick}
handleFilterChange={this.handleFilterChange}
handleEmojiPickerClose={this.handleEmojiPickerClose}
/>
</Tab>
<Tab
eventKey={2}
title={<GfycatIcon/>}
<Tabs
id='emoji-picker-tabs'
defaultActiveKey={1}
justified={true}
mountOnEnter={true}
unmountOnExit={true}
tabClassName={'custom-emoji-tab'}
>
<GifPicker
onGifClick={this.props.onGifClick}
defaultSearchText={this.state.filter}
handleSearchTextChange={this.handleFilterChange}
/>
</Tab>
</Tabs>
<EmojiPickerHeader handleEmojiPickerClose={this.handleEmojiPickerClose}/>
<Tab
eventKey={1}
title={
<div className={'custom-emoji-tab__icon__text'}>
<EmojiIcon
className='custom-emoji-tab__icon'
aria-hidden={true}
/>
<FormattedMessage
id='emoji_gif_picker.tabs.emojis'
defaultMessage='Emojis'
/>
</div>
}
unmountOnExit={true}
tabClassName={'custom-emoji-tab'}
>
<EmojiPicker
filter={this.state.filter}
onEmojiClick={this.props.onEmojiClick}
handleFilterChange={this.handleFilterChange}
handleEmojiPickerClose={this.handleEmojiPickerClose}
/>
</Tab>
<Tab
eventKey={2}
title={
<div className={'custom-emoji-tab__icon__text'}>
<GifIcon
className='custom-emoji-tab__icon'
aria-hidden={true}
/>
<FormattedMessage
id='emoji_gif_picker.tabs.gifs'
defaultMessage='GIFs'
/>
</div>
}
unmountOnExit={true}
tabClassName={'custom-emoji-tab'}
>
<GifPicker
filter={this.state.filter}
getRootPickerNode={this.getRootPickerNode}
onGifClick={this.props.onGifClick}
handleFilterChange={this.handleFilterChange}
/>
</Tab>
</Tabs>
</div>
);
}

Expand All @@ -156,7 +171,6 @@ export default class EmojiPickerTabs extends PureComponent<Props, State> {
<EmojiPickerHeader handleEmojiPickerClose={this.handleEmojiPickerClose}/>
<EmojiPicker
filter={this.state.filter}
visible={this.state.emojiTabVisible}
onEmojiClick={this.props.onEmojiClick}
handleFilterChange={this.handleFilterChange}
handleEmojiPickerClose={this.handleEmojiPickerClose}
Expand Down
2 changes: 1 addition & 1 deletion components/gif_picker/components/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export class Header extends PureComponent<Props, State> {
renderTabs(props: Props, style: Style) {
const {appProps, onTrending, onCategories} = props;
const {header} = appProps;
return header.tabs.map((tab, index) => {
return header.tabs.map((tab: any, index: any) => {
let link;
if (tab === constants.Tab.TRENDING) {
link = this.renderTab({name: 'trending', callback: onTrending, Icon: GifTrendingIcon, index, style});
Expand Down
62 changes: 62 additions & 0 deletions components/gif_picker/components/gif_picker_items.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React, {memo, useCallback} from 'react';
import {useSelector} from 'react-redux';
import {EmojiVariationsListProps, Grid} from '@giphy/react-components';
import {GifsResult} from '@giphy/js-fetch-api';

import {getGiphyFetchInstance} from 'mattermost-redux/selectors/entities/general';

import NoResultsIndicator from 'components/no_results_indicator';
import {NoResultsVariant} from 'components/no_results_indicator/types';

const GUTTER_BETWEEN_GIFS = 8;
const NUM_OF_GIFS_COLUMNS = 2;

interface Props {
width: number;
filter: string;
onClick: EmojiVariationsListProps['onGifClick'];
}

function GifPickerItems(props: Props) {
const giphyFetch = useSelector(getGiphyFetchInstance);

const fetchGifs = useCallback(async (offset: number) => {
if (!giphyFetch) {
return {} as GifsResult;
}

// We dont have to throttled the fetching as the library does it for us
if (props.filter.length > 0) {
const filteredResult = await giphyFetch.search(props.filter, {offset, limit: 10});
return filteredResult;
}

const trendingResult = await giphyFetch.trending({offset, limit: 10});
return trendingResult;
}, [props.filter, giphyFetch]);

return (
<div className='emoji-picker__items gif-picker__items'>
<Grid
key={props.filter.length === 0 ? 'trending' : props.filter}
columns={NUM_OF_GIFS_COLUMNS}
gutter={GUTTER_BETWEEN_GIFS}
hideAttribution={true}
width={props.width}
noResultsMessage={
<NoResultsIndicator
variant={NoResultsVariant.ChannelSearch}
titleValues={{channelName: `"${props.filter}"`}}
/>
}
fetchGifs={fetchGifs}
onGifClick={props.onClick}
/>
</div>
);
}

export default memo(GifPickerItems);
70 changes: 70 additions & 0 deletions components/gif_picker/components/gif_picker_search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React, {ChangeEvent, memo, useCallback, useMemo} from 'react';
import {useIntl} from 'react-intl';
import {useSelector} from 'react-redux';
import tinycolor from 'tinycolor2';

import {getTheme} from 'mattermost-redux/selectors/entities/preferences';

import giphyWhiteImage from 'images/gif_picker/powered-by-giphy-white.png';
import giphyBlackImage from 'images/gif_picker/powered-by-giphy-black.png';

interface Props {
value: string;
onChange: (value: string) => void;
}

function GifPickerSearch(props: Props) {
const theme = useSelector(getTheme);

const {formatMessage} = useIntl();

const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
event.preventDefault();

// remove trailing and leading colons
const value = event.target?.value?.replace(/^:|:$/g, '') ?? '';
props.onChange(value);
}, [props.onChange]);

const shouldUseWhiteLogo = useMemo(() => {
const WHITE_COLOR = '#FFFFFF';
const BLACK_COLOR = '#000000';

const mostReadableColor = tinycolor.mostReadable(theme.centerChannelBg, [WHITE_COLOR, BLACK_COLOR], {includeFallbackColors: false});

if (mostReadableColor.isLight()) {
return true;
}
return false;
}, [theme.centerChannelBg]);

return (
<div className='emoji-picker__search-container'>
<div className='emoji-picker__text-container'>
<span className='icon-magnify icon emoji-picker__search-icon'/>
<input
id='emojiPickerSearch'
className='emoji-picker__search'
aria-label={formatMessage({id: 'gif_picker.input.label', defaultMessage: 'Search for GIFs'})}
placeholder={formatMessage({id: 'gif_picker.input.placeholder', defaultMessage: 'Search GIPHY'})}
type='text'
autoFocus={true}
autoComplete='off'
onChange={handleChange}
value={props.value}
/>
</div>
<div className='gif-attribution'>
<img
src={shouldUseWhiteLogo ? giphyWhiteImage : giphyBlackImage}
alt={formatMessage({id: 'gif_picker.attribution.alt', defaultMessage: 'Powered by GIPHY'})}
/>
</div>
</div>
);
}

export default memo(GifPickerSearch);
Loading

0 comments on commit 2a04b46

Please sign in to comment.