// Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { useCallback, useEffect, useRef, useState } from 'react'; import classNames from 'classnames'; import { usePopper } from 'react-popper'; import type { AttachmentType } from '../types/Attachment'; import type { BodyRangeType, LocalizerType } from '../types/Util'; import type { ContactNameColorType } from '../types/Colors'; import type { ConversationType } from '../state/ducks/conversations'; import type { EmojiPickDataType } from './emoji/EmojiPicker'; import type { InputApi } from './CompositionInput'; import type { PreferredBadgeSelectorType } from '../state/selectors/badges'; import type { RenderEmojiPickerProps } from './conversation/ReactionPicker'; import type { ReplyType } from '../types/Stories'; import { Avatar, AvatarSize } from './Avatar'; import { CompositionInput } from './CompositionInput'; import { ContactName } from './conversation/ContactName'; import { EmojiButton } from './emoji/EmojiButton'; import { Emojify } from './conversation/Emojify'; import { MessageBody } from './conversation/MessageBody'; import { MessageTimestamp } from './conversation/MessageTimestamp'; import { Modal } from './Modal'; import { Quote } from './conversation/Quote'; import { ReactionPicker } from './conversation/ReactionPicker'; import { Tabs } from './Tabs'; import { Theme } from '../util/theme'; import { ThemeType } from '../types/Util'; import { getAvatarColor } from '../types/Colors'; type ViewType = Pick< ConversationType, | 'acceptedMessageRequest' | 'avatarPath' | 'color' | 'isMe' | 'name' | 'profileName' | 'sharedGroupNames' | 'title' > & { contactNameColor?: ContactNameColorType; timestamp: number; }; enum Tab { Replies = 'Replies', Views = 'Views', } export type PropsType = { authorTitle: string; getPreferredBadge: PreferredBadgeSelectorType; i18n: LocalizerType; isGroupStory?: boolean; isMyStory?: boolean; onClose: () => unknown; onReact: (emoji: string) => unknown; onReply: ( message: string, mentions: Array, timestamp: number ) => unknown; onSetSkinTone: (tone: number) => unknown; onTextTooLong: () => unknown; onUseEmoji: (_: EmojiPickDataType) => unknown; preferredReactionEmoji: Array; recentEmojis?: Array; renderEmojiPicker: (props: RenderEmojiPickerProps) => JSX.Element; replies: Array; skinTone?: number; storyPreviewAttachment?: AttachmentType; views: Array; }; export const StoryViewsNRepliesModal = ({ authorTitle, getPreferredBadge, i18n, isGroupStory, isMyStory, onClose, onReact, onReply, onSetSkinTone, onTextTooLong, onUseEmoji, preferredReactionEmoji, recentEmojis, renderEmojiPicker, replies, skinTone, storyPreviewAttachment, views, }: PropsType): JSX.Element => { const inputApiRef = useRef(); const [bottom, setBottom] = useState(null); const [messageBodyText, setMessageBodyText] = useState(''); const [showReactionPicker, setShowReactionPicker] = useState(false); const focusComposer = useCallback(() => { if (inputApiRef.current) { inputApiRef.current.focus(); } }, [inputApiRef]); const insertEmoji = useCallback( (e: EmojiPickDataType) => { if (inputApiRef.current) { inputApiRef.current.insertEmoji(e); onUseEmoji(e); } }, [inputApiRef, onUseEmoji] ); const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState( null ); const { styles, attributes } = usePopper(referenceElement, popperElement, { placement: 'top-start', strategy: 'fixed', }); useEffect(() => { if (replies.length) { bottom?.scrollIntoView({ behavior: 'smooth' }); } }, [bottom, replies.length]); let composerElement: JSX.Element | undefined; if (!isMyStory) { composerElement = (
{!isGroupStory && ( )} { setMessageBodyText(messageText); }} onPickEmoji={insertEmoji} onSubmit={(...args) => { inputApiRef.current?.reset(); onReply(...args); }} onTextTooLong={onTextTooLong} placeholder={i18n('StoryViewsNRepliesModal__placeholder')} theme={ThemeType.dark} >
); } let repliesElement: JSX.Element | undefined; if (replies.length) { repliesElement = (
{replies.map(reply => reply.reactionEmoji ? (
{i18n('StoryViewsNRepliesModal__reacted')}
) : (
) )}
); } else if (isGroupStory) { repliesElement = (
{i18n('StoryViewsNRepliesModal__no-replies')}
); } const viewsElement = views.length ? (
{views.map(view => (
))}
) : undefined; const tabsElement = views.length && replies.length ? ( {({ selectedTab }) => ( <> {selectedTab === Tab.Views && viewsElement} {selectedTab === Tab.Replies && ( <> {repliesElement} {composerElement} )} )} ) : undefined; return (
{tabsElement || ( <> {viewsElement || repliesElement} {composerElement} )}
); };