diff --git a/app/application.js b/app/application.js index 910d2af8d..c0f54ff1d 100644 --- a/app/application.js +++ b/app/application.js @@ -4,9 +4,6 @@ require('fork-awesome/css/fork-awesome.css'); require.context('./images/', true); -require('@fonticonpicker/react-fonticonpicker/dist/fonticonpicker.base-theme.react.css'); -require('@fonticonpicker/react-fonticonpicker/dist/fonticonpicker.material-theme.react.css'); - loadPolyfills().then(() => { require('./soapbox/main').default(); }).catch(e => { diff --git a/app/soapbox/features/soapbox_config/components/icon_picker.js b/app/soapbox/features/soapbox_config/components/icon_picker.js new file mode 100644 index 000000000..0b16e1792 --- /dev/null +++ b/app/soapbox/features/soapbox_config/components/icon_picker.js @@ -0,0 +1,5 @@ +import Picker from 'emoji-mart/dist-es/components/picker/picker'; + +export { + Picker, +}; diff --git a/app/soapbox/features/soapbox_config/components/icon_picker_dropdown.js b/app/soapbox/features/soapbox_config/components/icon_picker_dropdown.js new file mode 100644 index 000000000..57bff9934 --- /dev/null +++ b/app/soapbox/features/soapbox_config/components/icon_picker_dropdown.js @@ -0,0 +1,237 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { defineMessages, injectIntl } from 'react-intl'; +import { IconPicker as IconPickerAsync } from '../utils/async'; +import Overlay from 'react-overlays/lib/Overlay'; +import classNames from 'classnames'; +import detectPassiveEvents from 'detect-passive-events'; +import forkAwesomeIcons from '../forkawesome.json'; +import Icon from 'soapbox/components/icon'; + +const messages = defineMessages({ + emoji: { id: 'icon_button.label', defaultMessage: 'Select icon' }, + emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' }, + emoji_not_found: { id: 'emoji_button.not_found', defaultMessage: 'No emojos!! (╯°□°)╯︵ ┻━┻' }, + custom: { id: 'icon_button.icons', defaultMessage: 'Icons' }, + search_results: { id: 'emoji_button.search_results', defaultMessage: 'Search results' }, +}); + +let IconPicker; // load asynchronously + +const backgroundImageFn = () => ''; +const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false; + +const categoriesSort = 'custom'; + +@injectIntl +class IconPickerMenu extends React.PureComponent { + + static propTypes = { + custom_emojis: PropTypes.object, + loading: PropTypes.bool, + onClose: PropTypes.func.isRequired, + onPick: PropTypes.func.isRequired, + style: PropTypes.object, + placement: PropTypes.string, + arrowOffsetLeft: PropTypes.string, + arrowOffsetTop: PropTypes.string, + intl: PropTypes.object.isRequired, + }; + + static defaultProps = { + style: {}, + loading: true, + }; + + state = { + modifierOpen: false, + placement: null, + }; + + handleDocumentClick = e => { + if (this.node && !this.node.contains(e.target)) { + this.props.onClose(); + } + } + + componentDidMount() { + document.addEventListener('click', this.handleDocumentClick, false); + document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); + } + + componentWillUnmount() { + document.removeEventListener('click', this.handleDocumentClick, false); + document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); + } + + setRef = c => { + this.node = c; + } + + getI18n = () => { + const { intl } = this.props; + + return { + search: intl.formatMessage(messages.emoji_search), + notfound: intl.formatMessage(messages.emoji_not_found), + categories: { + search: intl.formatMessage(messages.search_results), + custom: intl.formatMessage(messages.custom), + }, + }; + } + + handleClick = emoji => { + emoji.native = emoji.colons; + + this.props.onClose(); + this.props.onPick(emoji); + } + + buildIcons = (customEmojis, autoplay = false) => { + const emojis = []; + + Object.values(customEmojis).forEach(category => { + category.forEach(function(icon) { + const name = icon.replace('fa fa-', ''); + emojis.push({ + id: icon, + name, + short_names: [name], + emoticons: [], + keywords: [name], + imageUrl: '', + custom: true, + }); + }); + }); + + return emojis; + }; + + render() { + const { loading, style, intl, custom_emojis } = this.props; + + if (loading) { + return
; + } + + const title = intl.formatMessage(messages.emoji); + const { modifierOpen } = this.state; + + return ( +
+ +
+ ); + } + +} + +export default @injectIntl +class IconPickerDropdown extends React.PureComponent { + + static propTypes = { + frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string), + intl: PropTypes.object.isRequired, + onPickEmoji: PropTypes.func.isRequired, + value: PropTypes.string, + }; + + state = { + icons: forkAwesomeIcons, + active: false, + loading: false, + }; + + setRef = (c) => { + this.dropdown = c; + } + + onShowDropdown = ({ target }) => { + this.setState({ active: true }); + + if (!IconPicker) { + this.setState({ loading: true }); + + IconPickerAsync().then(EmojiMart => { + IconPicker = EmojiMart.Picker; + + this.setState({ loading: false }); + }).catch(() => { + this.setState({ loading: false }); + }); + } + + const { top } = target.getBoundingClientRect(); + this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' }); + } + + onHideDropdown = () => { + this.setState({ active: false }); + } + + onToggle = (e) => { + if (!this.state.loading && (!e.key || e.key === 'Enter')) { + if (this.state.active) { + this.onHideDropdown(); + } else { + this.onShowDropdown(e); + } + } + } + + handleKeyDown = e => { + if (e.key === 'Escape') { + this.onHideDropdown(); + } + } + + setTargetRef = c => { + this.target = c; + } + + findTarget = () => { + return this.target; + } + + render() { + const { intl, onPickEmoji, value } = this.props; + const title = intl.formatMessage(messages.emoji); + const { active, loading, placement, icons } = this.state; + + return ( +
+
+ +
+ + + + +
+ ); + } + +} diff --git a/app/soapbox/features/soapbox_config/containers/icon_picker_dropdown_container.js b/app/soapbox/features/soapbox_config/containers/icon_picker_dropdown_container.js new file mode 100644 index 000000000..abd8bd9cf --- /dev/null +++ b/app/soapbox/features/soapbox_config/containers/icon_picker_dropdown_container.js @@ -0,0 +1,20 @@ +import { connect } from 'react-redux'; +import IconPickerDropdown from '../components/icon_picker_dropdown'; +import { useEmoji } from '../../../actions/emojis'; + +const mapStateToProps = state => ({ + frequentlyUsedEmojis: '', +}); + +const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({ + + onPickEmoji: emoji => { + dispatch(useEmoji(emoji)); // eslint-disable-line react-hooks/rules-of-hooks + + if (onPickEmoji) { + onPickEmoji(emoji); + } + }, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(IconPickerDropdown); diff --git a/app/soapbox/features/soapbox_config/index.js b/app/soapbox/features/soapbox_config/index.js index cb10bb268..d4c360140 100644 --- a/app/soapbox/features/soapbox_config/index.js +++ b/app/soapbox/features/soapbox_config/index.js @@ -26,8 +26,8 @@ import Accordion from '../ui/components/accordion'; import SitePreview from './components/site_preview'; import ThemeToggle from 'soapbox/features/ui/components/theme_toggle'; import { defaultSettings } from 'soapbox/actions/settings'; -import FontIconPicker from '@fonticonpicker/react-fonticonpicker'; import forkAwesomeIcons from './forkawesome.json'; +import IconPickerDropdown from './components/icon_picker_dropdown'; const messages = defineMessages({ heading: { id: 'column.soapbox_config', defaultMessage: 'Soapbox config' }, @@ -249,9 +249,8 @@ class SoapboxConfig extends ImmutablePureComponent {
val.substring(6))} + onChange={this.handlePromoItemChange(i, 'icon', field, val => val.id)} /> -
+
+
{label && ()} -
- +
+
diff --git a/app/soapbox/features/soapbox_config/utils/async.js b/app/soapbox/features/soapbox_config/utils/async.js new file mode 100644 index 000000000..42866579b --- /dev/null +++ b/app/soapbox/features/soapbox_config/utils/async.js @@ -0,0 +1,3 @@ +export function IconPicker() { + return import(/* webpackChunkName: "icon_picker" */'../components/icon_picker'); +} diff --git a/app/styles/forms.scss b/app/styles/forms.scss index bab1f33c6..c1ca83470 100644 --- a/app/styles/forms.scss +++ b/app/styles/forms.scss @@ -179,6 +179,10 @@ code { } } + .input.font_icon_picker { + width: 52px; + } + .input.with_block_label { max-width: none; @@ -255,10 +259,6 @@ code { } } - .input.popup { - overflow: visible; - } - .input.radio_buttons .radio label { margin-bottom: 5px; font-family: inherit; @@ -511,92 +511,18 @@ code { } } - &__icon_picker { + &__font_icon_picker { font-size: 14px; - .rfipbtn { - display: inline-flex; - line-height: 19px; - min-height: 19px; - padding: 0; + .font-icon-button { + padding: 9px; + border: 1px solid var(--highlight-text-color); + border-radius: 4px; - &__icon { - margin: 3.5px 0 3.5px 10px; + .fa { + font-size: 18px; color: var(--primary-text-color); } - - &--default { - .rfipbtn__icon { - border-color: var(--primary-text-color); - } - - .rfipbtn__del { - background-color: var(--primary-text-color--faint); - color: var(--foreground-color); - } - - .rfipbtn__button { - border-left: 1px solid var(--highlight-text-color); - background-color: var(--foreground-color); - color: var(--primary-text-color); - } - } - - &__icon--empty { - text-transform: none; - font-style: normal; - color: var(--primary-text-color--faint); - font-size: 16px; - line-height: 15px; - padding: 10px; - } - - &__current { - flex: 0; - } - - &--default:active, - &--default:focus { - box-shadow: none; - } - - &__button { - border-top-right-radius: inherit; - border-bottom-right-radius: inherit; - } - } - - .rfipdropdown { - &--default { - background-color: var(--background-color); - border: none; - color: var(--primary-text-color); - width: 325px; - - input, - select { - color: var(--primary-text-color); - background-color: var(--foreground-color); - } - - .rfipsearch input:focus, - .rfipsearch input:active { - box-shadow: none; - } - - .rfipicons__ibox, - .rfipicons__left, - .rfipicons__right { - border: none; - background-color: var(--foreground-color); - color: var(--primary-text-color); - } - - .rfipicons__cp, - .rfipcategory select { - border-bottom: 1px solid var(--highlight-text-color); - } - } } } diff --git a/package.json b/package.json index 48ca85def..f19985841 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "@babel/preset-env": "^7.3.4", "@babel/preset-react": "^7.0.0", "@babel/runtime": "^7.3.4", - "@fonticonpicker/react-fonticonpicker": "^1.2.0", "@popperjs/core": "^2.4.4", "array-includes": "^3.0.3", "autoprefixer": "^10.0.0", @@ -114,7 +113,6 @@ "react-swipeable-views": "^0.13.0", "react-textarea-autosize": "^8.0.0", "react-toggle": "^4.0.1", - "react-transition-group": "^2.3.0", "redux": "^4.0.5", "redux-immutable": "^4.0.0", "redux-thunk": "^2.2.0", diff --git a/yarn.lock b/yarn.lock index b2c70a69e..6076f50ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1345,11 +1345,6 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@fonticonpicker/react-fonticonpicker@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@fonticonpicker/react-fonticonpicker/-/react-fonticonpicker-1.2.0.tgz#cbcb898f3d788ef50d7090fe623afe2e8e5eb1d7" - integrity sha512-I7U4VNIpwJHnOaWq1v2MSuaUTxPiur/MYG4B1hr+FWI1O7k+tTym1ysdAyfp7YbEdF4Q8TYoY5Q2hbTmhQeTSQ== - "@formatjs/ecma402-abstract@^1.2.3": version "1.2.3" resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.2.3.tgz#ca94911dd8e9c89eeaabba0f00e2f692979fc27b" @@ -4342,7 +4337,7 @@ dom-converter@^0.2: dependencies: utila "~0.4" -dom-helpers@^3.2.1, dom-helpers@^3.3.1, dom-helpers@^3.4.0: +dom-helpers@^3.2.1, dom-helpers@^3.3.1: version "3.4.0" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA== @@ -10074,16 +10069,6 @@ react-transition-group@^2.2.1: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" -react-transition-group@^2.3.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" - integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg== - dependencies: - dom-helpers "^3.4.0" - loose-envify "^1.4.0" - prop-types "^15.6.2" - react-lifecycles-compat "^3.0.4" - react@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"