Skip to content

Commit

Permalink
Gfycat integration (mattermost#1367)
Browse files Browse the repository at this point in the history
* Gfycat integration

* Added headers to resolve make check-style.

* Updated tests.

* Removed gfycat analytics.

* Added '(Beta)' to GIF system console.

* Added gfycat API credentials to admin console.

* Removed gfycatSdk.

* Only enable gif picker if gfycat credentials are specified.

* Updated gfycat credentials description on admin console.

* Ran check-style.

* Revert check for gfycat api credentials.

* Updated package.json to latest redux commit.

* Deleted unused stylesheets.

* Changed Components to PureComponents.

* Re-added emoji preview which might have been removed from merge conflict.

* Fixed make check-style errors.

* Minor updates to Gfycat System Console text

* Add missing strings to en.json

* Use image proxy.
  • Loading branch information
kennethjeremyau authored and hmhealey committed Jun 29, 2018
1 parent 4d27296 commit 87839c9
Show file tree
Hide file tree
Showing 68 changed files with 2,379 additions and 20 deletions.
6 changes: 6 additions & 0 deletions components/admin_console/admin_console.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ClientVersionsSettings from 'components/admin_console/client_versions_set
import ClusterSettings from 'components/admin_console/cluster_settings.jsx';
import CustomBrandSettings from 'components/admin_console/custom_brand_settings.jsx';
import CustomEmojiSettings from 'components/admin_console/custom_emoji_settings.jsx';
import CustomGifSettings from 'components/admin_console/custom_gif_settings.jsx';
import DataRetentionSettings from 'components/admin_console/data_retention_settings.jsx';
import DatabaseSettings from 'components/admin_console/database_settings.jsx';
import ElasticsearchSettings from 'components/admin_console/elasticsearch_settings.jsx';
Expand Down Expand Up @@ -471,6 +472,11 @@ export default class AdminConsole extends React.Component {
component={CustomEmojiSettings}
extraProps={extraProps}
/>
<SCRoute
path={`${props.match.url}/gif`}
component={CustomGifSettings}
extraProps={extraProps}
/>
<SCRoute
path={`${props.match.url}/posts`}
component={SchemaAdminSettings}
Expand Down
10 changes: 10 additions & 0 deletions components/admin_console/admin_sidebar/admin_sidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,16 @@ export default class AdminSidebar extends React.Component {

}
/>
<AdminSidebarSection
name='gif'
title={
<FormattedMessage
id='admin.sidebar.gif'
defaultMessage='GIF (Beta)'
/>

}
/>
<AdminSidebarSection
name='posts'
title={
Expand Down
107 changes: 107 additions & 0 deletions components/admin_console/custom_gif_settings.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React from 'react';
import {FormattedMessage} from 'react-intl';

import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import TextSetting from './text_setting.jsx';
import SettingsGroup from './settings_group.jsx';

export default class CustomGifSettings extends AdminSettings {
constructor(props) {
super(props);

this.getConfigFromState = this.getConfigFromState.bind(this);

this.renderSettings = this.renderSettings.bind(this);
}

getConfigFromState(config) {
config.ServiceSettings.EnableGifPicker = this.state.enableGifPicker;
config.ServiceSettings.GfycatApiKey = this.state.gfycatApiKey;
config.ServiceSettings.GfycatApiSecret = this.state.gfycatApiSecret;
return config;
}

getStateFromConfig(config) {
return {
enableGifPicker: config.ServiceSettings.EnableGifPicker,
gfycatApiKey: config.ServiceSettings.GfycatApiKey,
gfycatApiSecret: config.ServiceSettings.GfycatApiSecret,
};
}

renderTitle() {
return (
<FormattedMessage
id='admin.customization.gif'
defaultMessage='GIF (Beta)'
/>
);
}

renderSettings() {
return (
<SettingsGroup>
<BooleanSetting
id='enableGifPicker'
label={
<FormattedMessage
id='admin.customization.enableGifPickerTitle'
defaultMessage='Enable GIF Picker:'
/>
}
helpText={
<FormattedMessage
id='admin.customization.enableGifPickerDesc'
defaultMessage='Allow users to select GIFs from the emoji picker via a Gfycat integration.'
/>
}
value={this.state.enableGifPicker}
onChange={this.handleChange}
setByEnv={this.isSetByEnv('ServiceSettings.EnableGifPicker')}
/>
<TextSetting
id='gfycatApiKey'
label={
<FormattedMessage
id='admin.customization.gfycatApiKey'
defaultMessage='Gfycat API Key:'
/>
}
helpText={
<FormattedMessage
id='admin.customization.gfycatApiKeyDescription'
defaultMessage='Request an API key at https://developers.gfycat.com/signup/#/. Enter the client ID you receive via email to this field.'
/>
}
value={this.state.gfycatApiKey}
placeholder=''
onChange={this.handleChange}
setByEnv={this.isSetByEnv('ServiceSettings.GfycatAPIKey')}
/>
<TextSetting
id='gfycatApiSecret'
label={
<FormattedMessage
id='admin.customization.gfycatApiSecret'
defaultMessage='Gfycat API Secret:'
/>
}
helpText={
<FormattedMessage
id='admin.customization.gfycatApiSecretDescription'
defaultMessage='The API secret generated by Gfycat for your API key.'
/>
}
value={this.state.gfycatApiSecret}
placeholder=''
onChange={this.handleChange}
setByEnv={this.isSetByEnv('ServiceSettings.GfycatAPISecret')}
/>
</SettingsGroup>
);
}
}
30 changes: 30 additions & 0 deletions components/create_comment/create_comment.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ export default class CreateComment extends React.PureComponent {
*/
enableEmojiPicker: PropTypes.bool.isRequired,

/**
* Set if the gif picker is enabled.
*/
enableGifPicker: PropTypes.bool.isRequired,

/**
* The maximum length of a post
*/
Expand Down Expand Up @@ -250,6 +255,29 @@ export default class CreateComment extends React.PureComponent {
this.focusTextbox();
}

handleGifClick = (gif) => {
const {draft} = this.state;

let newMessage = '';
if (draft.message === '') {
newMessage = gif;
} else if (/\s+$/.test(draft.message)) {
// Check whether there is already a blank at the end of the current message
newMessage = `${draft.message}${gif} `;
} else {
newMessage = `${draft.message} ${gif} `;
}

this.props.onUpdateCommentDraft({...draft, message: newMessage});

this.setState({
showEmojiPicker: false,
draft: {...draft, message: newMessage},
});

this.focusTextbox();
}

handlePostError = (postError) => {
this.setState({postError});
}
Expand Down Expand Up @@ -607,6 +635,8 @@ export default class CreateComment extends React.PureComponent {
target={this.getCreateCommentControls}
onHide={this.hideEmojiPicker}
onEmojiClick={this.handleEmojiClick}
onGifClick={this.handleGifClick}
enableGifPicker={this.props.enableGifPicker}
rightOffset={15}
topOffset={55}
/>
Expand Down
2 changes: 2 additions & 0 deletions components/create_comment/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ function mapStateToProps(state, ownProps) {
const config = getConfig(state);
const enableConfirmNotificationsToChannel = config.EnableConfirmNotificationsToChannel === 'true';
const enableEmojiPicker = config.EnableEmojiPicker === 'true';
const enableGifPicker = config.EnableGifPicker === 'true';

return {
draft,
Expand All @@ -49,6 +50,7 @@ function mapStateToProps(state, ownProps) {
readOnlyChannel: !isCurrentUserSystemAdmin(state) && config.ExperimentalTownSquareIsReadOnly === 'true' && channel.name === Constants.DEFAULT_CHANNEL,
enableConfirmNotificationsToChannel,
enableEmojiPicker,
enableGifPicker,
maxPostSize: parseInt(config.MaxPostSize, 10) || Constants.DEFAULT_CHARACTER_LIMIT,
};
}
Expand Down
18 changes: 18 additions & 0 deletions components/create_post/create_post.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ export default class CreatePost extends React.Component {
*/
enableEmojiPicker: PropTypes.bool.isRequired,

/**
* Whether to show the gif picker.
*/
enableGifPicker: PropTypes.bool.isRequired,

/**
* Whether to check with the user before notifying the whole channel.
*/
Expand Down Expand Up @@ -739,6 +744,17 @@ export default class CreatePost extends React.Component {
this.focusTextbox();
}

handleGifClick = (gif) => {
if (this.state.message === '') {
this.setState({message: gif});
} else {
const newMessage = (/\s+$/.test(this.state.message)) ? this.state.message + gif : this.state.message + ' ' + gif;
this.setState({message: newMessage});
}
this.setState({showEmojiPicker: false});
this.focusTextbox();
}

createTutorialTip() {
const screens = [];

Expand Down Expand Up @@ -879,6 +895,8 @@ export default class CreatePost extends React.Component {
target={this.getCreatePostControls}
onHide={this.hideEmojiPicker}
onEmojiClick={this.handleEmojiClick}
onGifClick={this.handleGifClick}
enableGifPicker={this.props.enableGifPicker}
rightOffset={15}
topOffset={-7}
/>
Expand Down
2 changes: 2 additions & 0 deletions components/create_post/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ function mapStateToProps() {
const enableTutorial = config.EnableTutorial === 'true';
const tutorialStep = getInt(state, Preferences.TUTORIAL_STEP, getCurrentUserId(state), TutorialSteps.FINISHED);
const enableEmojiPicker = config.EnableEmojiPicker === 'true';
const enableGifPicker = config.EnableGifPicker === 'true';
const enableConfirmNotificationsToChannel = config.EnableConfirmNotificationsToChannel === 'true';
const currentUserId = getCurrentUserId(state);
const userIsOutOfOffice = getStatusForUserId(state, currentUserId) === UserStatuses.OUT_OF_OFFICE;
Expand All @@ -70,6 +71,7 @@ function mapStateToProps() {
readOnlyChannel: !isCurrentUserSystemAdmin(state) && config.ExperimentalTownSquareIsReadOnly === 'true' && currentChannel.name === Constants.DEFAULT_CHANNEL,
canUploadFiles: canUploadFiles(config),
enableEmojiPicker,
enableGifPicker,
enableConfirmNotificationsToChannel,
maxPostSize: parseInt(config.MaxPostSize, 10) || Constants.DEFAULT_CHARACTER_LIMIT,
userIsOutOfOffice,
Expand Down
13 changes: 13 additions & 0 deletions components/edit_post_modal/edit_post_modal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,17 @@ export default class EditPostModal extends React.PureComponent {
this.refs.editbox.focus();
}

handleGifClick = (gif) => {
if (this.state.editText === '') {
this.setState({editText: gif});
} else {
const newMessage = (/\s+$/.test(this.state.editText)) ? this.state.editText + gif : this.state.editText + ' ' + gif;
this.setState({editText: newMessage});
}
this.setState({showEmojiPicker: false});
this.refs.editbox.focus();
}

getEditPostControls = () => {
return this.refs.editPostEmoji;
}
Expand Down Expand Up @@ -264,6 +275,8 @@ export default class EditPostModal extends React.PureComponent {
target={this.getEditPostControls}
onHide={this.hideEmojiPicker}
onEmojiClick={this.handleEmojiClick}
onGifClick={this.handleGifClick}
enableGifPicker={this.props.config.EnableGifPicker === 'true'}
rightOffset={50}
topOffset={-20}
/>
Expand Down
7 changes: 4 additions & 3 deletions components/emoji_picker/emoji_picker.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import EmojiPickerPreview from './components/emoji_picker_preview';

const CATEGORY_SEARCH_RESULTS = 'searchResults';
const EMOJI_HEIGHT = 27;
const EMOJI_CONTAINER_HEIGHT = 300;
const EMOJI_CONTAINER_HEIGHT = 244;
const EMOJI_CONTAINER_STYLE = {
height: EMOJI_CONTAINER_HEIGHT,
};
Expand Down Expand Up @@ -112,6 +112,7 @@ export default class EmojiPicker extends React.PureComponent {
style: PropTypes.object,
rightOffset: PropTypes.number,
topOffset: PropTypes.number,
listHeight: PropTypes.number,
placement: PropTypes.oneOf(['top', 'bottom', 'left']),
onEmojiClick: PropTypes.func.isRequired,
customEmojisEnabled: PropTypes.bool,
Expand All @@ -125,6 +126,7 @@ export default class EmojiPicker extends React.PureComponent {
};

static defaultProps = {
listHeight: 245,
rightOffset: 0,
topOffset: 0,
customEmojiPage: 0,
Expand Down Expand Up @@ -588,11 +590,10 @@ export default class EmojiPicker extends React.PureComponent {
}
return (
<div
className='emoji-picker'
style={pickerStyle}
>
{this.emojiCategories()}
{this.emojiSearch()}
{this.emojiCategories()}
{this.emojiCurrentResults()}
<EmojiPickerPreview emoji={this.getCurrentEmojiByCursor(this.state.cursor)}/>
</div>
Expand Down
9 changes: 7 additions & 2 deletions components/emoji_picker/emoji_picker_overlay.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,28 @@ import PropTypes from 'prop-types';
import React from 'react';
import {Overlay} from 'react-bootstrap';

import EmojiPicker from './';
import EmojiPickerTabs from './emoji_picker_tabs.jsx';

export default class EmojiPickerOverlay extends React.PureComponent {
static propTypes = {
show: PropTypes.bool.isRequired,
container: PropTypes.func,
target: PropTypes.func.isRequired,
onEmojiClick: PropTypes.func.isRequired,
onGifClick: PropTypes.func,
onHide: PropTypes.func.isRequired,
rightOffset: PropTypes.number,
topOffset: PropTypes.number,
spaceRequiredAbove: PropTypes.number,
spaceRequiredBelow: PropTypes.number,
enableGifPicker: PropTypes.bool,
}

// Reasonable defaults calculated from from the center channel
static defaultProps = {
spaceRequiredAbove: 422,
spaceRequiredBelow: 436,
enableGifPicker: false,
}

constructor(props) {
Expand Down Expand Up @@ -62,8 +65,10 @@ export default class EmojiPickerOverlay extends React.PureComponent {
target={this.props.target}
animation={false}
>
<EmojiPicker
<EmojiPickerTabs
enableGifPicker={this.props.enableGifPicker}
onEmojiClick={this.props.onEmojiClick}
onGifClick={this.props.onGifClick}
rightOffset={this.props.rightOffset}
topOffset={this.props.topOffset}
/>
Expand Down
Loading

0 comments on commit 87839c9

Please sign in to comment.