Skip to content

Commit

Permalink
Insert gif from giphy (#390)
Browse files Browse the repository at this point in the history
* Insert gif from giphy

* Fix UT issue

* Fix send giphy media

* Add UT for send giphy message
  • Loading branch information
hkhanouchi committed Mar 13, 2023
1 parent 48967a8 commit 38ebc1b
Show file tree
Hide file tree
Showing 16 changed files with 1,354 additions and 67 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ REACT_APP_SENTRY_DSN=
REACT_APP_INVITE_CODE_DEJAVU = '29e012b1-9893-412d-997e-0c5c8296af2d'

REACT_APP_SENDBIRD_APP_ID=

REACT_APP_GIPHY_SDK_KEY=
1 change: 1 addition & 0 deletions .github/workflows/development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
REACT_APP_WEB3_AUTHENTICATE_MESSAGE: ${{secrets.WEB3_AUTHENTICATE_MESSAGE}}
REACT_APP_SENDBIRD_APP_ID: ${{secrets.SENDBIRD_APP_ID}}
REACT_APP_INVITE_CODE_DEJAVU: ${{secrets.INVITE_CODE_DEJAVU}}
REACT_APP_GIPHY_SDK_KEY: ${{secrets.GIPHY_SDK_KEY}}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ jobs:
REACT_APP_WEB3_AUTHENTICATE_MESSAGE: ${{secrets.WEB3_AUTHENTICATE_MESSAGE}}
REACT_APP_SENDBIRD_APP_ID: ${{secrets.SENDBIRD_APP_ID}}
REACT_APP_INVITE_CODE_DEJAVU: ${{secrets.INVITE_CODE_DEJAVU}}
REACT_APP_GIPHY_SDK_KEY: ${{secrets.GIPHY_SDK_KEY}}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
Expand Down
1,057 changes: 1,009 additions & 48 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
},
"dependencies": {
"@craco/craco": "^6.4.3",
"@giphy/js-fetch-api": "^4.7.1",
"@giphy/react-components": "^6.5.2",
"@reduxjs/toolkit": "^1.7.1",
"@sentry/react": "^7.13.0",
"@types/jest": "^24.9.1",
Expand Down
51 changes: 51 additions & 0 deletions src/components/message-input/giphy.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { SearchContextManager } from '@giphy/react-components';
import { shallow } from 'enzyme';
import React from 'react';
import { Giphy, GiphyComponents, Properties } from './giphy';

describe('Giphy', () => {
const subject = (props: Partial<Properties>) => {
const allProps: Properties = {
onClose: jest.fn(),
onClickGif: jest.fn(),
...props,
};

return shallow(<Giphy {...allProps} />);
};

const initialDocument = global.document;

beforeEach(() => {
// @ts-ignore
global.document = {
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
};
});

afterEach(() => {
global.document = initialDocument;
});

it('renders GiphyComponents', () => {
const wrapper = subject({});
const searchContextManager = wrapper.find(SearchContextManager).shallow();

expect(searchContextManager.find(GiphyComponents).exists()).toBe(true);
});

it('attach mousedown listener to document', () => {
subject({});

expect(global.document.addEventListener).toHaveBeenCalledWith('mousedown', expect.any(Function));
});

it('removeEventListener mousedown listener to document', () => {
const wrapper = subject({});

(wrapper.instance() as any).componentWillUnmount();

expect(global.document.removeEventListener).toHaveBeenCalledWith('mousedown', expect.any(Function));
});
});
62 changes: 62 additions & 0 deletions src/components/message-input/giphy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { useContext } from 'react';
import { Grid, SearchBar, SearchContext, SearchContextManager } from '@giphy/react-components';
import { config } from '../../config';

export interface ComponentProperties {
onClickGif: Grid['props']['onGifClick'];
}

export const GiphyComponents = ({ onClickGif }: Properties) => {
const { fetchGifs, searchKey } = useContext(SearchContext);
return (
<div className='giphy__container'>
<SearchBar
className='giphy__container-search'
autoFocus
/>
<div className='giphy__container-grid'>
<Grid
key={searchKey}
initialGifs={[]}
columns={2}
width={500}
fetchGifs={fetchGifs}
noLink
onGifClick={onClickGif}
/>
</div>
</div>
);
};

export interface Properties extends ComponentProperties {
onClose: () => void;
}

export class Giphy extends React.Component<Properties> {
componentDidMount = () => {
document.addEventListener('mousedown', this.clickOutsideGiphyCheck);
};

componentWillUnmount = () => {
document.removeEventListener('mousedown', this.clickOutsideGiphyCheck);
};

clickOutsideGiphyCheck = (event: MouseEvent) => {
const [giphy] = document.getElementsByClassName('giphy__container');

if (giphy && event && event.target) {
if (!giphy.contains(event.target as Node)) {
this.props.onClose();
}
}
};

render = () => {
return (
<SearchContextManager apiKey={config.giphySdkKey}>
<GiphyComponents {...this.props} />
</SearchContextManager>
);
};
}
40 changes: 40 additions & 0 deletions src/components/message-input/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import ReplyCard from '../reply-card/reply-card';
import { ViewModes } from '../../shared-components/theme-engine';
import { EmojiPicker } from './emoji-picker';
import MessageAudioRecorder from '../message-audio-recorder';
import { Giphy } from './giphy';
import ImageCards from '../../platform-apps/channels/image-cards';

describe('MessageInput', () => {
const subject = (props: Partial<Properties>, child: any = <div />) => {
Expand Down Expand Up @@ -187,6 +189,44 @@ describe('MessageInput', () => {
});
});

describe('Giphy', () => {
it('should render giphy component', function () {
const wrapper = subject({});
const dropzone = wrapper.find(Dropzone).shallow();

expect(dropzone.find(Giphy).exists()).toBe(false);

wrapper.find('.giphy__icon').simulate('click');

dropzone.setProps({});

expect(dropzone.find(Giphy).exists()).toBe(true);
});
it('should render giphy component and insert gif in media when giphy icon is clicked', function () {
const sampleGif = { id: '1', title: 'Hilarious gif', images: { preview_gif: { url: 'youfoundme.gif' } } };
const wrapper = subject({});
const dropzone = wrapper.find(Dropzone).shallow();

wrapper.find('.giphy__icon').simulate('click');

dropzone.setProps({});

dropzone.find(Giphy).simulate('clickGif', sampleGif);

dropzone.setProps({});

expect(dropzone.find(ImageCards).prop('images')[0]).toStrictEqual({
id: '1',
name: 'Hilarious gif',
url: 'youfoundme.gif',
mediaType: 'image',
giphy: sampleGif,
} as any);

expect(dropzone.find(Giphy).exists()).toBe(false);
});
});

async function userSearch(wrapper, search) {
const userMentionHandler = wrapper
.find(Dropzone)
Expand Down
45 changes: 44 additions & 1 deletion src/components/message-input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import Menu from './menu';
import ImageCards from '../../platform-apps/channels/image-cards';
import { config } from '../../config';
import ReplyCard from '../reply-card/reply-card';
import { IconFaceSmile } from '@zero-tech/zui/icons';
import { IconFaceSmile, IconStickerCircle } from '@zero-tech/zui/icons';
import { ViewModes } from '../../shared-components/theme-engine';
import { PublicProperties as PublicPropertiesContainer } from './container';
import { EmojiPicker } from './emoji-picker';
import { IconButton } from '../icon-button';
import { IconMicrophone2 } from '@zero-tech/zui/icons';
import AudioCards from '../../platform-apps/channels/audio-cards';
import MessageAudioRecorder from '../message-audio-recorder';
import { Giphy, Properties as GiphyProperties } from './giphy';

import './styles.scss';

Expand All @@ -36,6 +37,7 @@ interface State {
mentionedUserIds: string[];
media: any[];
isEmojisActive: boolean;
isGiphyActive: boolean;
isMicActive: boolean;
}

Expand All @@ -46,6 +48,7 @@ export class MessageInput extends React.Component<Properties, State> {
media: [],
isMicActive: false,
isEmojisActive: false,
isGiphyActive: false,
};

private textareaRef: RefObject<HTMLTextAreaElement>;
Expand Down Expand Up @@ -217,6 +220,32 @@ export class MessageInput extends React.Component<Properties, State> {
}
};

openGiphy = async () => {
this.setState({
isGiphyActive: true,
});
};

closeGiphy = () => {
this.setState({
isGiphyActive: false,
});
};

onInsertGiphy: GiphyProperties['onClickGif'] = (giphy) => {
this.mediaSelected([
{
id: giphy.id.toString(),
name: giphy.title,
url: giphy.images.preview_gif.url,
mediaType: 'image',
giphy,
},
]);
this.props.onMessageInputRendered(this.textareaRef);
this.closeGiphy();
};

openEmojis = async () => {
this.setState({
isEmojisActive: true,
Expand All @@ -241,6 +270,14 @@ export class MessageInput extends React.Component<Properties, State> {
renderInput() {
return (
<div className='message-input chat-message__new-message'>
<div className='message-input__icons'>
<IconButton
onClick={this.openGiphy}
Icon={IconStickerCircle}
size={16}
className='giphy__icon'
/>
</div>
<div className='message-input__icons'>
<Menu
onSelected={this.mediaSelected}
Expand Down Expand Up @@ -284,6 +321,12 @@ export class MessageInput extends React.Component<Properties, State> {
onSelect={this.onInsertEmoji}
/>
</div>
{this.state.isGiphyActive && (
<Giphy
onClickGif={this.onInsertGiphy}
onClose={this.closeGiphy}
/>
)}
{this.state.isMicActive && (
<MessageAudioRecorder
onClose={this.cancelRecording}
Expand Down
52 changes: 50 additions & 2 deletions src/components/message-input/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,8 @@

.message-input__input-wrapper {
flex-grow: 99;
margin-left: 8px;
margin-left: 10px;
position: relative;
margin-right: 8px;
}

.mentions-text-area {
Expand Down Expand Up @@ -177,6 +176,7 @@
justify-content: center;
align-items: center;
padding-bottom: 2px;
margin-left: 10px;

&-action {
position: absolute;
Expand All @@ -194,6 +194,54 @@
}
}

.giphy__container {
width: 500px;
position: absolute;
bottom: 40px;
right: 0px;
background-color: theme.$background-color-tertiary;
padding: 5px;
border-radius: 5px;
border: 1px solid theme.$input-arrow-color;

&-grid {
height: 440px;
overflow-y: overlay;
margin-top: 5px;
.loader {
margin-top: 40px;
opacity: 1 !important;
}

.giphy-gif {
cursor: pointer;
}
}

&-search {
background-color: theme.$input-background-primary-color !important;
border: 1px solid theme.$input-arrow-color !important;
box-sizing: content-box;
padding-left: 5px;
border-radius: 5px !important;
height: 30px !important;

> div {
height: 32px !important;
width: 32px !important;
border-radius: 5px !important;
margin: -1px -1px 0 0 !important;
}

input {
font-size: 14px !important;
color: theme.$font-color-primary !important;
line-height: 1.15 !important;
padding: 5px 25px 6px 10px !important;
background: none;
}
}
}
.image-send__icon {
cursor: pointer;
}
5 changes: 5 additions & 0 deletions src/components/message/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ export class Message extends React.Component<Properties, State> {
return this.props.isOwner;
};

isMediaMessage = (): boolean => {
return !!this.props.media;
};

deleteMessage = (): void => this.props.onDelete(this.props.messageId);
toggleEdit = () => this.setState((state) => ({ isEditing: !state.isEditing }));
editMessage = (content: string, mentionedUserIds: string[]) => {
Expand Down Expand Up @@ -154,6 +158,7 @@ export class Message extends React.Component<Properties, State> {
onDelete={this.deleteMessage}
onEdit={this.toggleEdit}
onReply={this.onReply}
isMediaMessage={this.isMediaMessage()}
/>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ export const config = {
inviteCode: {
dejaVu: process.env.REACT_APP_INVITE_CODE_DEJAVU,
},
giphySdkKey: process.env.REACT_APP_GIPHY_SDK_KEY,
};
Loading

0 comments on commit 38ebc1b

Please sign in to comment.