// Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { useState, useEffect, useCallback, useRef } from 'react'; import { usePopper } from 'react-popper'; import { isEqual, noop } from 'lodash'; import type { LocalizerType } from '../types/Util'; import { Modal } from './Modal'; import { Button, ButtonVariant } from './Button'; import { ReactionPickerPicker, ReactionPickerPickerEmojiButton, ReactionPickerPickerStyle, } from './ReactionPickerPicker'; import { EmojiPicker } from './emoji/EmojiPicker'; import { DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES } from '../reactions/constants'; import { convertShortName } from './emoji/lib'; import { offsetDistanceModifier } from '../util/popperUtil'; import { handleOutsideClick } from '../util/handleOutsideClick'; import { EmojiSkinTone, getEmojiVariantByKey } from './fun/data/emojis'; import { FunEmojiPicker } from './fun/FunEmojiPicker'; import type { FunEmojiSelection } from './fun/panels/FunPanelEmojis'; import { isFunPickerEnabled } from './fun/isFunPickerEnabled'; export type PropsType = { draftPreferredReactions: ReadonlyArray; hadSaveError: boolean; i18n: LocalizerType; isSaving: boolean; originalPreferredReactions: ReadonlyArray; recentEmojis: ReadonlyArray; selectedDraftEmojiIndex: undefined | number; emojiSkinToneDefault: EmojiSkinTone | null; cancelCustomizePreferredReactionsModal(): unknown; deselectDraftEmoji(): unknown; onEmojiSkinToneDefaultChange: (emojiSkinToneDefault: EmojiSkinTone) => void; replaceSelectedDraftEmoji(newEmoji: string): unknown; resetDraftEmoji(): unknown; savePreferredReactions(): unknown; selectDraftEmojiToBeReplaced(index: number): unknown; }; export function CustomizingPreferredReactionsModal({ cancelCustomizePreferredReactionsModal, deselectDraftEmoji, draftPreferredReactions, emojiSkinToneDefault, hadSaveError, i18n, isSaving, onEmojiSkinToneDefaultChange, originalPreferredReactions, recentEmojis, replaceSelectedDraftEmoji, resetDraftEmoji, savePreferredReactions, selectDraftEmojiToBeReplaced, selectedDraftEmojiIndex, }: Readonly): JSX.Element { const [referenceElement, setReferenceElement] = useState(null); const pickerRef = useRef(null); const [popperElement, setPopperElement] = useState( null ); const emojiPickerPopper = usePopper(referenceElement, popperElement, { placement: 'bottom', modifiers: [ offsetDistanceModifier(8), { name: 'preventOverflow', options: { altAxis: true }, }, ], }); const isSomethingSelected = selectedDraftEmojiIndex !== undefined; useEffect(() => { if (!isSomethingSelected) { return noop; } return handleOutsideClick( target => { if ( target instanceof Element && target.closest('[data-fun-overlay]') != null ) { return true; } deselectDraftEmoji(); return true; }, { containerElements: [popperElement, pickerRef], name: 'CustomizingPreferredReactionsModal.draftEmoji', } ); }, [isSomethingSelected, popperElement, deselectDraftEmoji]); const hasChanged = !isEqual( originalPreferredReactions, draftPreferredReactions ); const canReset = !isSaving && !isEqual( DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES.map(shortName => convertShortName(shortName, emojiSkinToneDefault ?? EmojiSkinTone.None) ), draftPreferredReactions ); const canSave = !isSaving && hasChanged; const footer = ( <> ); return ( { cancelCustomizePreferredReactionsModal(); }} title={i18n('icu:CustomizingPreferredReactions__title')} modalFooter={footer} >
{draftPreferredReactions.map((emoji, index) => { return ( { selectDraftEmojiToBeReplaced(index); }} onDeselect={() => { deselectDraftEmoji(); }} onSelectEmoji={emojiSelection => { const emojiVariant = getEmojiVariantByKey( emojiSelection.variantKey ); replaceSelectedDraftEmoji(emojiVariant.value); }} /> ); })} {hadSaveError ? i18n('icu:CustomizingPreferredReactions__had-save-error') : i18n('icu:CustomizingPreferredReactions__subtitle')}
{!isFunPickerEnabled() && isSomethingSelected && (
{ const emoji = convertShortName( pickedEmoji.shortName, pickedEmoji.skinTone ); replaceSelectedDraftEmoji(emoji); }} recentEmojis={recentEmojis} emojiSkinToneDefault={emojiSkinToneDefault} onEmojiSkinToneDefaultChange={onEmojiSkinToneDefaultChange} onClose={() => { deselectDraftEmoji(); }} wasInvokedFromKeyboard={false} />
)}
); } function CustomizingPreferredReactionsModalItem(props: { emoji: string; isSelected: boolean; onSelect: () => void; onDeselect: () => void; onSelectEmoji: (emojiSelection: FunEmojiSelection) => void; }) { const { onDeselect } = props; const [emojiPickerOpen, setEmojiPickerOpen] = useState(false); const handleEmojiPickerOpenChange = useCallback( (open: boolean) => { setEmojiPickerOpen(open); if (!open) { onDeselect(); } }, [onDeselect] ); const button = ( ); if (isFunPickerEnabled()) { return ( {button} ); } return button; }