From 492a4214a5ec76f768e292dfb5071b0044e94279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 25 Oct 2024 20:23:00 +0200 Subject: [PATCH] pl-fe: Split StatusActionBar into separate components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../src/components/status-action-bar.tsx | 696 +++++++++++------- 1 file changed, 449 insertions(+), 247 deletions(-) diff --git a/packages/pl-fe/src/components/status-action-bar.tsx b/packages/pl-fe/src/components/status-action-bar.tsx index f4588cfa9d..946752cf89 100644 --- a/packages/pl-fe/src/components/status-action-bar.tsx +++ b/packages/pl-fe/src/components/status-action-bar.tsx @@ -43,6 +43,7 @@ import type { UnauthorizedModalAction } from 'pl-fe/features/ui/components/modal import type { Account } from 'pl-fe/normalizers/account'; import type { Group } from 'pl-fe/normalizers/group'; import type { SelectedStatus } from 'pl-fe/selectors'; +import type { Me } from 'pl-fe/types/pl-fe'; const messages = defineMessages({ adminAccount: { id: 'status.admin_account', defaultMessage: 'Moderate @{name}' }, @@ -110,29 +111,358 @@ const messages = defineMessages({ hideTranslation: { id: 'status.hide_translation', defaultMessage: 'Hide translation' }, }); -interface IStatusActionBar { - status: SelectedStatus; +interface IActionButton extends Pick { + me: Me; + onOpenUnauthorizedModal: (action?: UnauthorizedModalAction) => void; +} + +interface IReplyButton extends IActionButton { rebloggedBy?: Account; - withLabels?: boolean; +} + +const ReplyButton: React.FC = ({ + status, + statusActionButtonTheme, + withLabels, + me, + onOpenUnauthorizedModal, + rebloggedBy, +}) => { + const dispatch = useAppDispatch(); + const intl = useIntl(); + + const { groupRelationship } = useGroupRelationship(status.group_id || undefined); + + let replyTitle; + let replyDisabled = false; + const replyCount = status.replies_count; + + if ((status.group as Group)?.membership_required && !groupRelationship?.member) { + replyDisabled = true; + replyTitle = intl.formatMessage(messages.replies_disabled_group); + } + + if (!status.in_reply_to_id) { + replyTitle = intl.formatMessage(messages.reply); + } else { + replyTitle = intl.formatMessage(messages.replyAll); + } + + const handleReplyClick: React.MouseEventHandler = (e) => { + if (me) { + dispatch(replyCompose(status, rebloggedBy)); + } else { + onOpenUnauthorizedModal('REPLY'); + } + }; + + const replyButton = ( + + ); + + return status.group ? ( + + {replyButton} + + ) : replyButton; +}; + +interface IReblogButton extends IActionButton { + publicStatus: boolean; +} + +const ReblogButton: React.FC = ({ + status, + statusActionButtonTheme, + withLabels, + me, + onOpenUnauthorizedModal, + publicStatus, +}) => { + const dispatch = useAppDispatch(); + const features = useFeatures(); + const intl = useIntl(); + + const { boostModal } = useSettings(); + const { openModal } = useModalsStore(); + + let reblogIcon = require('@tabler/icons/outline/repeat.svg'); + + if (status.visibility === 'direct') { + reblogIcon = require('@tabler/icons/outline/mail.svg'); + } else if (status.visibility === 'private' || status.visibility === 'mutuals_only') { + reblogIcon = require('@tabler/icons/outline/lock.svg'); + } + + const handleReblogClick: React.EventHandler = e => { + if (me) { + const modalReblog = () => dispatch(toggleReblog(status)); + if ((e && e.shiftKey) || !boostModal) { + modalReblog(); + } else { + openModal('BOOST', { statusId: status.id, onReblog: modalReblog }); + } + } else { + onOpenUnauthorizedModal('REBLOG'); + } + }; + + const handleReblogLongPress = status.reblogs_count ? () => { + openModal('REBLOGS', { statusId: status.id }); + } : undefined; + + const reblogButton = ( + + ); + + if (!features.quotePosts || !me) return reblogButton; + + const handleQuoteClick: React.EventHandler = (e) => { + if (me) { + dispatch(quoteCompose(status)); + } else { + onOpenUnauthorizedModal('REBLOG'); + } + }; + + const reblogMenu = [{ + text: intl.formatMessage(status.reblogged ? messages.cancel_reblog_private : messages.reblog), + action: handleReblogClick, + icon: require('@tabler/icons/outline/repeat.svg'), + }, { + text: intl.formatMessage(messages.quotePost), + action: handleQuoteClick, + icon: require('@tabler/icons/outline/quote.svg'), + }]; + + return ( + + {reblogButton} + + ); +}; + +const FavouriteButton: React.FC = ({ + status, + statusActionButtonTheme, + me, + withLabels, + onOpenUnauthorizedModal, +}) => { + const dispatch = useAppDispatch(); + const features = useFeatures(); + const intl = useIntl(); + + const { openModal } = useModalsStore(); + + const handleFavouriteClick: React.EventHandler = (e) => { + if (me) { + dispatch(toggleFavourite(status)); + } else { + onOpenUnauthorizedModal('FAVOURITE'); + } + }; + + const handleFavouriteLongPress = status.favourites_count ? () => { + openModal('FAVOURITES', { statusId: status.id }); + } : undefined; + + return ( + + ); +}; + +const DislikeButton: React.FC = ({ + status, + statusActionButtonTheme, + withLabels, + me, + onOpenUnauthorizedModal, +}) => { + const dispatch = useAppDispatch(); + const features = useFeatures(); + const intl = useIntl(); + + const { openModal } = useModalsStore(); + + if (!features.statusDislikes) return; + + const handleDislikeClick: React.EventHandler = (e) => { + if (me) { + dispatch(toggleDislike(status)); + } else { + onOpenUnauthorizedModal('DISLIKE'); + } + }; + + const handleDislikeLongPress = status.dislikes_count ? () => { + openModal('DISLIKES', { statusId: status.id }); + } : undefined; + + return ( + + ); +}; + +const WrenchButton: React.FC = ({ + status, + statusActionButtonTheme, + withLabels, + me, +}) => { + const dispatch = useAppDispatch(); + const intl = useIntl(); + const features = useFeatures(); + + const { openModal } = useModalsStore(); + const { showWrenchButton } = useSettings(); + + if (!me || withLabels || !features.emojiReacts || !showWrenchButton) return; + + const wrenches = showWrenchButton && status.emoji_reactions.find(emoji => emoji.name === '🔧') || undefined; + + const handleWrenchClick: React.EventHandler = (e) => { + if (wrenches?.me) { + dispatch(unEmojiReact(status, '🔧')); + } else { + dispatch(emojiReact(status, '🔧')); + } + }; + + const handleWrenchLongPress = wrenches?.count ? () => { + openModal('REACTIONS', { statusId: status.id, reaction: wrenches.name }); + } : undefined; + + return ( + + ); +}; + +const EmojiPickerButton: React.FC> = ({ + status, + statusActionButtonTheme, + withLabels, + me, +}) => { + const dispatch = useAppDispatch(); + + const features = useFeatures(); + + const handlePickEmoji = (emoji: EmojiType) => { + dispatch(emojiReact(status, emoji.custom ? emoji.id : emoji.native, emoji.custom ? emoji.imageUrl : undefined)); + }; + + return me && !withLabels && features.emojiReacts && ( + + ); +}; + +const ShareButton: React.FC> = ({ + status, + statusActionButtonTheme, +}) => { + const intl = useIntl(); + + const handleShareClick = () => { + navigator.share({ + text: status.search_index, + url: status.uri, + }).catch((e) => { + if (e.name !== 'AbortError') console.error(e); + }); + }; + + const canShare = ('share' in navigator) && (status.visibility === 'public' || status.visibility === 'group'); + + return canShare && ( + + ); +}; + +interface IMenuButton extends IActionButton { expandable?: boolean; - space?: 'sm' | 'md' | 'lg'; - statusActionButtonTheme?: 'default' | 'inverse'; fromBookmarks?: boolean; } -const StatusActionBar: React.FC = ({ +const MenuButton: React.FC = ({ status, - withLabels = false, + statusActionButtonTheme, + withLabels, + me, expandable, - space = 'sm', - statusActionButtonTheme = 'default', - fromBookmarks = false, - rebloggedBy, + fromBookmarks, }) => { const intl = useIntl(); const history = useHistory(); const dispatch = useAppDispatch(); const match = useRouteMatch<{ groupId: string }>('/groups/:groupId'); + const { boostModal } = useSettings(); const { openModal } = useModalsStore(); const { group } = useGroup((status.group as Group)?.id as string); @@ -140,13 +470,10 @@ const StatusActionBar: React.FC = ({ const blockGroupMember = useBlockGroupMember(group as Group, status.account); const { getOrCreateChatByAccountId } = useChats(); - const me = useAppSelector(state => state.me); const { groupRelationship } = useGroupRelationship(status.group_id || undefined); const features = useFeatures(); const instance = useInstance(); - const { autoTranslate, boostModal, deleteModal, knownLanguages, showWrenchButton } = useSettings(); - - const wrenches = showWrenchButton && status.emoji_reactions.find(emoji => emoji.name === '🔧') || undefined; + const { autoTranslate, deleteModal, knownLanguages } = useSettings(); const { translationLanguages } = useTranslationLanguages(); @@ -166,76 +493,6 @@ const StatusActionBar: React.FC = ({ const isStaff = account ? account.is_admin || account.is_moderator : false; const isAdmin = account ? account.is_admin : false; - if (!status) { - return null; - } - - const onOpenUnauthorizedModal = (action?: UnauthorizedModalAction) => { - openModal('UNAUTHORIZED', { - action, - ap_id: status.url, - }); - }; - - const handleReplyClick: React.MouseEventHandler = (e) => { - if (me) { - dispatch(replyCompose(status, rebloggedBy)); - } else { - onOpenUnauthorizedModal('REPLY'); - } - }; - - const handleShareClick = () => { - navigator.share({ - text: status.search_index, - url: status.uri, - }).catch((e) => { - if (e.name !== 'AbortError') console.error(e); - }); - }; - - const handleFavouriteClick: React.EventHandler = (e) => { - if (me) { - dispatch(toggleFavourite(status)); - } else { - onOpenUnauthorizedModal('FAVOURITE'); - } - }; - - const handleFavouriteLongPress = status.favourites_count ? () => { - openModal('FAVOURITES', { statusId: status.id }); - } : undefined; - - const handleDislikeClick: React.EventHandler = (e) => { - if (me) { - dispatch(toggleDislike(status)); - } else { - onOpenUnauthorizedModal('DISLIKE'); - } - }; - - const handleWrenchClick: React.EventHandler = (e) => { - if (!me) { - onOpenUnauthorizedModal('DISLIKE'); - } else if (wrenches?.me) { - dispatch(unEmojiReact(status, '🔧')); - } else { - dispatch(emojiReact(status, '🔧')); - } - }; - - const handleDislikeLongPress = status.dislikes_count ? () => { - openModal('DISLIKES', { statusId: status.id }); - } : undefined; - - const handleWrenchLongPress = wrenches?.count ? () => { - openModal('REACTIONS', { statusId: status.id, reaction: wrenches.name }); - } : undefined; - - const handlePickEmoji = (emoji: EmojiType) => { - dispatch(emojiReact(status, emoji.custom ? emoji.id : emoji.native, emoji.custom ? emoji.imageUrl : undefined)); - }; - const handleBookmarkClick: React.EventHandler = (e) => { dispatch(toggleBookmark(status)); }; @@ -246,31 +503,6 @@ const StatusActionBar: React.FC = ({ }); }; - const handleReblogClick: React.EventHandler = e => { - if (me) { - const modalReblog = () => dispatch(toggleReblog(status)); - if ((e && e.shiftKey) || !boostModal) { - modalReblog(); - } else { - openModal('BOOST', { statusId: status.id, onReblog: modalReblog }); - } - } else { - onOpenUnauthorizedModal('REBLOG'); - } - }; - - const handleReblogLongPress = status.reblogs_count ? () => { - openModal('REBLOGS', { statusId: status.id }); - } : undefined; - - const handleQuoteClick: React.EventHandler = (e) => { - if (me) { - dispatch(quoteCompose(status)); - } else { - onOpenUnauthorizedModal('REBLOG'); - } - }; - const doDeleteStatus = (withRedraft = false) => { if (!deleteModal) { dispatch(deleteStatus(status.id, withRedraft)); @@ -301,6 +533,15 @@ const StatusActionBar: React.FC = ({ dispatch(togglePin(status)); }; + const handleReblogClick: React.EventHandler = (e) => { + const modalReblog = () => dispatch(toggleReblog(status)); + if ((e && e.shiftKey) || !boostModal) { + modalReblog(); + } else { + openModal('BOOST', { statusId: status.id, onReblog: modalReblog }); + } + }; + const handleMentionClick: React.EventHandler = (e) => { dispatch(mentionCompose(status.account)); }; @@ -669,71 +910,55 @@ const StatusActionBar: React.FC = ({ const publicStatus = ['public', 'unlisted', 'group'].includes(status.visibility); - const replyCount = status.replies_count; - const reblogCount = status.reblogs_count; - const quoteCount = status.quotes_count; - const favouriteCount = status.favourites_count; - const menu = _makeMenu(publicStatus); - let reblogIcon = require('@tabler/icons/outline/repeat.svg'); - let replyTitle; - let replyDisabled = false; - if (status.visibility === 'direct') { - reblogIcon = require('@tabler/icons/outline/mail.svg'); - } else if (status.visibility === 'private' || status.visibility === 'mutuals_only') { - reblogIcon = require('@tabler/icons/outline/lock.svg'); - } - - if ((status.group as Group)?.membership_required && !groupRelationship?.member) { - replyDisabled = true; - replyTitle = intl.formatMessage(messages.replies_disabled_group); - } - - const replyButton = ( - + return ( + + + ); +}; - const reblogMenu = [{ - text: intl.formatMessage(status.reblogged ? messages.cancel_reblog_private : messages.reblog), - action: handleReblogClick, - icon: require('@tabler/icons/outline/repeat.svg'), - }, { - text: intl.formatMessage(messages.quotePost), - action: handleQuoteClick, - icon: require('@tabler/icons/outline/quote.svg'), - }]; +interface IStatusActionBar { + status: SelectedStatus; + rebloggedBy?: Account; + withLabels?: boolean; + expandable?: boolean; + space?: 'sm' | 'md' | 'lg'; + statusActionButtonTheme?: 'default' | 'inverse'; + fromBookmarks?: boolean; +} - const reblogButton = ( - - ); +const StatusActionBar: React.FC = ({ + status, + withLabels = false, + expandable, + space = 'sm', + statusActionButtonTheme = 'default', + fromBookmarks = false, + rebloggedBy, +}) => { - if (!status.in_reply_to_id) { - replyTitle = intl.formatMessage(messages.reply); - } else { - replyTitle = intl.formatMessage(messages.replyAll); + const { openModal } = useModalsStore(); + + const me = useAppSelector(state => state.me); + + if (!status) { + return null; } - const canShare = ('share' in navigator) && (status.visibility === 'public' || status.visibility === 'group'); + const onOpenUnauthorizedModal = (action?: UnauthorizedModalAction) => { + openModal('UNAUTHORIZED', { + action, + ap_id: status.url, + }); + }; + + const publicStatus = ['public', 'unlisted', 'group'].includes(status.visibility); const spacing: { [key: string]: React.ComponentProps['space']; @@ -752,92 +977,69 @@ const StatusActionBar: React.FC = ({ onClick={e => e.stopPropagation()} alignItems='center' > - {status.group ? ( - - {replyButton} - - ) : replyButton} - - {(features.quotePosts && me) ? ( - - {reblogButton} - - ) : ( - reblogButton - )} - - - {features.statusDislikes && ( - - )} + - {me && !withLabels && features.emojiReacts && showWrenchButton && ( - - )} + - {me && !withLabels && features.emojiReacts && ( - - )} + - {canShare && ( - - )} + - - - + + + + + );