import { connect } from 'react-redux'; import { changeSetting } from 'flavours/glitch/actions/settings'; import { createSelector } from 'reselect'; import { Map as ImmutableMap } from 'immutable'; import { useEmoji } from 'flavours/glitch/actions/emojis'; import React from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; import { EmojiPicker as EmojiPickerAsync } from 'flavours/glitch/util/async-components'; import Overlay from 'react-overlays/lib/Overlay'; import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import detectPassiveEvents from 'detect-passive-events'; import { buildCustomEmojis } from 'flavours/glitch/util/emoji'; const messages = defineMessages({ emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' }, emoji_not_found: { id: 'emoji_button.not_found', defaultMessage: 'No emojos!! (╯°□°)╯︵ ┻━┻' }, custom: { id: 'emoji_button.custom', defaultMessage: 'Custom' }, recent: { id: 'emoji_button.recent', defaultMessage: 'Frequently used' }, search_results: { id: 'emoji_button.search_results', defaultMessage: 'Search results' }, people: { id: 'emoji_button.people', defaultMessage: 'People' }, nature: { id: 'emoji_button.nature', defaultMessage: 'Nature' }, food: { id: 'emoji_button.food', defaultMessage: 'Food & Drink' }, activity: { id: 'emoji_button.activity', defaultMessage: 'Activity' }, travel: { id: 'emoji_button.travel', defaultMessage: 'Travel & Places' }, objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' }, symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' }, flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' }, }); const perLine = 8; const lines = 2; const DEFAULTS = [ '+1', 'grinning', 'kissing_heart', 'heart_eyes', 'laughing', 'stuck_out_tongue_winking_eye', 'sweat_smile', 'joy', 'yum', 'disappointed', 'thinking_face', 'weary', 'sob', 'sunglasses', 'heart', 'ok_hand', ]; const getFrequentlyUsedEmojis = createSelector([ state => state.getIn(['settings', 'frequentlyUsedEmojis'], ImmutableMap()), ], emojiCounters => { let emojis = emojiCounters .keySeq() .sort((a, b) => emojiCounters.get(a) - emojiCounters.get(b)) .reverse() .slice(0, perLine * lines) .toArray(); if (emojis.length < DEFAULTS.length) { emojis = emojis.concat(DEFAULTS.slice(0, DEFAULTS.length - emojis.length)); } return emojis; }); const getCustomEmojis = createSelector([ state => state.get('custom_emojis'), ], emojis => emojis.filter(e => e.get('visible_in_picker')).sort((a, b) => { const aShort = a.get('shortcode').toLowerCase(); const bShort = b.get('shortcode').toLowerCase(); if (aShort < bShort) { return -1; } else if (aShort > bShort ) { return 1; } else { return 0; } })); const mapStateToProps = state => ({ custom_emojis: getCustomEmojis(state), skinTone: state.getIn(['settings', 'skinTone']), frequentlyUsedEmojis: getFrequentlyUsedEmojis(state), }); const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({ onSkinTone: skinTone => { dispatch(changeSetting(['skinTone'], skinTone)); }, onPickEmoji: emoji => { dispatch(useEmoji(emoji)); if (onPickEmoji) { onPickEmoji(emoji); } }, }); const assetHost = process.env.CDN_HOST || ''; let EmojiPicker, Emoji; // load asynchronously const backgroundImageFn = () => `${assetHost}/emoji/sheet.png`; const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false; const categoriesSort = [ 'recent', 'custom', 'people', 'nature', 'foods', 'activity', 'places', 'objects', 'symbols', 'flags', ]; class ModifierPickerMenu extends React.PureComponent { static propTypes = { active: PropTypes.bool, onSelect: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired, }; handleClick = e => { this.props.onSelect(e.currentTarget.getAttribute('data-index') * 1); } componentWillReceiveProps (nextProps) { if (nextProps.active) { this.attachListeners(); } else { this.removeListeners(); } } componentWillUnmount () { this.removeListeners(); } handleDocumentClick = e => { if (this.node && !this.node.contains(e.target)) { this.props.onClose(); } } attachListeners () { document.addEventListener('click', this.handleDocumentClick, false); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); } removeListeners () { document.removeEventListener('click', this.handleDocumentClick, false); document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); } setRef = c => { this.node = c; } render () { const { active } = this.props; return (