diff --git a/app/soapbox/features/status/components/detailed-status.tsx b/app/soapbox/features/status/components/detailed-status.tsx index 8ff1b6ff8..d1560c125 100644 --- a/app/soapbox/features/status/components/detailed-status.tsx +++ b/app/soapbox/features/status/components/detailed-status.tsx @@ -19,7 +19,7 @@ import scheduleIdleTask from '../../ui/util/schedule_idle_task'; import Video from '../../video'; import Card from './card'; -import StatusInteractionBar from './status_interaction_bar'; +import StatusInteractionBar from './status-interaction-bar'; import type { List as ImmutableList } from 'immutable'; import type { Attachment as AttachmentEntity, Status as StatusEntity } from 'soapbox/types/entities'; diff --git a/app/soapbox/features/status/components/status-interaction-bar.tsx b/app/soapbox/features/status/components/status-interaction-bar.tsx new file mode 100644 index 000000000..11bd0916a --- /dev/null +++ b/app/soapbox/features/status/components/status-interaction-bar.tsx @@ -0,0 +1,186 @@ +import classNames from 'classnames'; +import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import React from 'react'; +import { FormattedNumber } from 'react-intl'; +import { useDispatch } from 'react-redux'; + +import { openModal } from 'soapbox/actions/modals'; +import emojify from 'soapbox/features/emoji/emoji'; +import { useAppSelector, useSoapboxConfig, useFeatures } from 'soapbox/hooks'; +import { reduceEmoji } from 'soapbox/utils/emoji_reacts'; + +import { HStack, IconButton, Text } from '../../../components/ui'; + +import type { Status } from 'soapbox/types/entities'; + +interface IStatusInteractionBar { + status: Status, +} + +const StatusInteractionBar: React.FC = ({ status }): JSX.Element | null => { + + const me = useAppSelector(({ me }) => me); + const { allowedEmoji } = useSoapboxConfig(); + const dispatch = useDispatch(); + const features = useFeatures(); + const { account } = status; + + if (!account || typeof account !== 'object') return null; + + const onOpenUnauthorizedModal = () => { + dispatch(openModal('UNAUTHORIZED')); + }; + + const onOpenReblogsModal = (username: string, statusId: string): void => { + dispatch(openModal('REBLOGS', { + username, + statusId, + })); + }; + + const onOpenFavouritesModal = (username: string, statusId: string): void => { + dispatch(openModal('FAVOURITES', { + username, + statusId, + })); + }; + + const onOpenReactionsModal = (username: string, statusId: string, reaction: string): void => { + dispatch(openModal('REACTIONS', { + username, + statusId, + reaction, + })); + }; + + const getNormalizedReacts = () => { + return reduceEmoji( + ImmutableList(status.getIn(['pleroma', 'emoji_reactions']) as any), + status.favourites_count, + status.favourited, + allowedEmoji, + ).reverse(); + }; + + const handleOpenReblogsModal: React.EventHandler = (e) => { + e.preventDefault(); + + if (!me) onOpenUnauthorizedModal(); + else onOpenReblogsModal(account.acct, status.id); + }; + + const getReposts = () => { + if (status.reblogs_count) { + return ( + + + + + + + + ); + } + + return ''; + }; + + const handleOpenFavouritesModal: React.EventHandler> = (e) => { + e.preventDefault(); + + if (!me) onOpenUnauthorizedModal(); + else onOpenFavouritesModal(account.acct, status.id); + }; + + const getFavourites = () => { + if (status.favourites_count) { + return ( + + + + + + + + ); + } + + return ''; + }; + + const handleOpenReactionsModal = (reaction: ImmutableMap) => () => { + if (!me) onOpenUnauthorizedModal(); + else onOpenReactionsModal(account.acct, status.id, String(reaction.get('name'))); + }; + + const getEmojiReacts = () => { + const emojiReacts = getNormalizedReacts(); + const count = emojiReacts.reduce((acc, cur) => ( + acc + cur.get('count') + ), 0); + + if (count > 0) { + return ( +
+
+ {emojiReacts.map((e, i) => { + const emojiReact = ( + <> + + {e.get('count')} + + ); + + if (features.exposableReactions) { + return ( + + {emojiReact} + + ); + } + + return {emojiReact}; + })} +
+
+ {count} +
+
+ ); + } + + return ''; + }; + + return ( + + {features.emojiReacts ? getEmojiReacts() : getFavourites()} + + {getReposts()} + + ); +}; + +export default StatusInteractionBar; diff --git a/app/soapbox/features/status/components/status_interaction_bar.js b/app/soapbox/features/status/components/status_interaction_bar.js deleted file mode 100644 index e1f931865..000000000 --- a/app/soapbox/features/status/components/status_interaction_bar.js +++ /dev/null @@ -1,213 +0,0 @@ -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { FormattedNumber } from 'react-intl'; -import { connect } from 'react-redux'; - -import { openModal } from 'soapbox/actions/modals'; -import { getSoapboxConfig } from 'soapbox/actions/soapbox'; -import emojify from 'soapbox/features/emoji/emoji'; -import { reduceEmoji } from 'soapbox/utils/emoji_reacts'; -import { getFeatures } from 'soapbox/utils/features'; -import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types'; - -import { HStack, IconButton, Text } from '../../../components/ui'; - -const mapStateToProps = state => { - const me = state.get('me'); - const instance = state.get('instance'); - - return { - me, - allowedEmoji: getSoapboxConfig(state).get('allowedEmoji'), - features: getFeatures(instance), - }; -}; - -const mapDispatchToProps = (dispatch) => ({ - onOpenUnauthorizedModal() { - dispatch(openModal('UNAUTHORIZED')); - }, - onOpenReblogsModal(username, statusId) { - dispatch(openModal('REBLOGS', { - username, - statusId, - })); - }, - onOpenFavouritesModal(username, statusId) { - dispatch(openModal('FAVOURITES', { - username, - statusId, - })); - }, - onOpenReactionsModal(username, statusId, reaction) { - dispatch(openModal('REACTIONS', { - username, - statusId, - reaction, - })); - }, -}); - -export default @connect(mapStateToProps, mapDispatchToProps) -class StatusInteractionBar extends ImmutablePureComponent { - - static propTypes = { - status: ImmutablePropTypes.record, - me: SoapboxPropTypes.me, - allowedEmoji: ImmutablePropTypes.list, - features: PropTypes.object.isRequired, - onOpenReblogsModal: PropTypes.func, - onOpenReactionsModal: PropTypes.func, - } - - getNormalizedReacts = () => { - const { status } = this.props; - return reduceEmoji( - status.getIn(['pleroma', 'emoji_reactions']), - status.get('favourites_count'), - status.get('favourited'), - this.props.allowedEmoji, - ).reverse(); - } - - handleOpenReblogsModal = (event) => { - const { me, status, onOpenUnauthorizedModal, onOpenReblogsModal } = this.props; - - event.preventDefault(); - - if (!me) onOpenUnauthorizedModal(); - else onOpenReblogsModal(status.getIn(['account', 'acct']), status.get('id')); - } - - getReposts = () => { - const { status } = this.props; - - if (status.get('reblogs_count')) { - return ( - - - - - - - - ); - } - - return ''; - } - - handleOpenFavouritesModal = (event) => { - const { me, status, onOpenUnauthorizedModal, onOpenFavouritesModal } = this.props; - - event.preventDefault(); - - if (!me) onOpenUnauthorizedModal(); - else onOpenFavouritesModal(status.getIn(['account', 'acct']), status.get('id')); - } - - getFavourites = () => { - const { features, status } = this.props; - - if (status.get('favourites_count')) { - return ( - - - - - - - - ); - } - - return ''; - } - - handleOpenReactionsModal = (reaction) => () => { - const { me, status, onOpenUnauthorizedModal, onOpenReactionsModal } = this.props; - - if (!me) onOpenUnauthorizedModal(); - else onOpenReactionsModal(status.getIn(['account', 'acct']), status.get('id'), reaction.get('name')); - } - - getEmojiReacts = () => { - const { features } = this.props; - - const emojiReacts = this.getNormalizedReacts(); - const count = emojiReacts.reduce((acc, cur) => ( - acc + cur.get('count') - ), 0); - - if (count > 0) { - return ( -
-
- {emojiReacts.map((e, i) => { - const emojiReact = ( - <> - - {e.get('count')} - - ); - - if (features.exposableReactions) { - return ( - - {emojiReact} - - ); - } - - return {emojiReact}; - })} -
-
- {count} -
-
- ); - } - - return ''; - }; - - render() { - const { features } = this.props; - - return ( - - {features.emojiReacts ? this.getEmojiReacts() : this.getFavourites()} - - {this.getReposts()} - - ); - } - -}