emoji-mart#Picker TypeScript Examples

The following examples show how to use emoji-mart#Picker. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Chat.tsx    From watchparty with MIT License 6 votes vote down vote up
render() {
    return (
      <div style={{ position: 'absolute', bottom: '60px' }}>
        <Picker
          set="google"
          sheetSize={64}
          theme="dark"
          showPreview={false}
          showSkinTones={false}
          onSelect={this.props.addEmoji}
        />
      </div>
    );
  }
Example #2
Source File: EmojiPicker.tsx    From SocialDev-Firebase with MIT License 6 votes vote down vote up
EmojiPicker: React.FC<Props> = ({ top, right, handleAddEmoji }) => {
  const [pageWidth, setPageWidth] = useState(window.innerWidth);

  const updateDimensions = () => {
    setPageWidth(window.innerWidth);
  };

  useEffect(() => {
    window.addEventListener('resize', updateDimensions);
    return () => {
      window.removeEventListener('resize', updateDimensions);
    };
  }, [pageWidth]);
  return (
    <>
      {pageWidth >= 700 ? (
        <Picker
          set="messenger"
          style={{ position: 'absolute', top: `${top}`, right: `${right}`, zIndex: 10 }}
          darkMode={false}
          onSelect={handleAddEmoji}
          showSkinTones={false}
          showPreview={false}
          color="#1ca0f2"
        />
      ) : null}
    </>
  );
}
Example #3
Source File: SpfxEmojipicker.tsx    From SPFx with Mozilla Public License 2.0 6 votes vote down vote up
public render(): React.ReactElement<ISpfxEmojipickerProps> {
    return (
      <div className={styles.spfxEmojipicker}>
        <Picker onSelect={(emoji) => { console.log(emoji) }} />
        <Emoji emoji={{ id: 'santa', skin: 3 }} size={32} />
        <Emoji emoji=':santa::skin-tone-3:' size={32} />
        <Emoji emoji='santa' set='apple' size={32} />
      </div>
    );
  }
Example #4
Source File: TextArea.tsx    From core with GNU Affero General Public License v3.0 5 votes vote down vote up
TextArea: React.FC<TextAreaProps> = ({ name, placeholder, theme='auto', max, setValue, value }) => {
	const ref = useRef()
	const [ emojiPickerHidden, setEmojiPickerHidden ] = useState(true)
	useOutsideClick(ref, () => {
		setEmojiPickerHidden(true)
	})
	
	return <div className='border border-grey-light dark:border-transparent h-96 text-black dark:bg-very-black dark:text-white rounded px-4 py-3 inline-block relative w-full'>
		<Field as='textarea' name={name} className='dark:border-transparent text-black dark:bg-very-black dark:text-white w-full relative h-full resize-none outline-none' placeholder={placeholder} />
		<div ref={ref}>
			<div className='absolute bottom-12 left-10 z-30'>
				{
					!emojiPickerHidden && <Picker title='선택해주세요' emoji='sunglasses' set='twitter' enableFrequentEmojiSort	theme={theme} showSkinTones={false} onSelect={(e) => {
						setEmojiPickerHidden(true)
						setValue(value + ' ' + ((e as { native: string }).native || e.colons))
					}} i18n={{
						search: '검색',
						notfound: '검색 결과가 없습니다.',
						categories: {
							search: '검색 결과',
							recent: '최근 사용',
							people: '사람',
							nature: '자연',
							foods: '음식',
							activity: '활동',
							places: '장소',
							objects: '사물',
							symbols: '기호',
							flags: '국기',
							custom: '커스텀'
						}
					}} custom={KoreanbotsEmoji}/>
				}
			</div>
			<div className='absolute bottom-2 left-4 hidden sm:block'>
				<div className='emoji-selector-button outline-none' onClick={() => setEmojiPickerHidden(false)} onKeyPress={() => setEmojiPickerHidden(false)} role='button' tabIndex={0} />
			</div>
			{
				max && <span className={`absolute bottom-2 right-4 ${max < value.length ? ' text-red-400' : ''}`}>
					{max-value.length}
				</span>
			}
		</div>
	</div>
}
Example #5
Source File: CommentCreator.tsx    From taskcafe with MIT License 4 votes vote down vote up
CommentCreator: React.FC<CommentCreatorProps> = ({
  me,
  disabled = false,
  message,
  onMemberProfile,
  onCreateComment,
  onCancelEdit,
  autoFocus = false,
}) => {
  const $commentWrapper = useRef<HTMLDivElement>(null);
  const $comment = useRef<HTMLTextAreaElement>(null);
  const $emoji = useRef<HTMLDivElement>(null);
  const $emojiCart = useRef<HTMLDivElement>(null);
  const [comment, setComment] = useState(message ?? '');
  const [showCommentActions, setShowCommentActions] = useState(autoFocus);
  const { showPopup, hidePopup } = usePopup();
  useEffect(() => {
    if (autoFocus && $comment && $comment.current) {
      $comment.current.select();
    }
  }, []);
  useOnOutsideClick(
    [$commentWrapper, $emojiCart],
    showCommentActions,
    () => {
      if (onCancelEdit) {
        onCancelEdit();
      }
      setShowCommentActions(false);
    },
    null,
  );
  return (
    <CommentInnerWrapper ref={$commentWrapper}>
      {me && onMemberProfile && (
        <CommentProfile
          member={me}
          size={32}
          onMemberProfile={$target => {
            onMemberProfile($target, me.id);
          }}
        />
      )}
      <CommentEditorContainer>
        <CommentTextArea
          $showCommentActions={showCommentActions}
          placeholder="Write a comment..."
          ref={$comment}
          disabled={disabled}
          value={comment}
          onChange={e => setComment(e.currentTarget.value)}
          onFocus={() => {
            setShowCommentActions(true);
          }}
        />
        <CommentEditorActions visible={showCommentActions}>
          <CommentEditorActionIcon>
            <Paperclip width={12} height={12} />
          </CommentEditorActionIcon>
          <CommentEditorActionIcon>
            <At width={12} height={12} />
          </CommentEditorActionIcon>
          <CommentEditorActionIcon
            ref={$emoji}
            onClick={() => {
              showPopup(
                $emoji,
                <div ref={$emojiCart}>
                  <Picker
                    onClick={emoji => {
                      if ($comment && $comment.current) {
                        const textToInsert = `${emoji.colons} `;
                        const cursorPosition = $comment.current.selectionStart;
                        const textBeforeCursorPosition = $comment.current.value.substring(0, cursorPosition);
                        const textAfterCursorPosition = $comment.current.value.substring(
                          cursorPosition,
                          $comment.current.value.length,
                        );
                        setComment(textBeforeCursorPosition + textToInsert + textAfterCursorPosition);
                      }
                      hidePopup();
                    }}
                    set="google"
                  />
                </div>,
              );
            }}
          >
            <Smile width={12} height={12} />
          </CommentEditorActionIcon>
          <CommentEditorActionIcon>
            <Task width={12} height={12} />
          </CommentEditorActionIcon>
          <CommentEditorSaveButton
            onClick={() => {
              setShowCommentActions(false);
              onCreateComment(comment);
              setComment('');
            }}
          >
            Save
          </CommentEditorSaveButton>
        </CommentEditorActions>
      </CommentEditorContainer>
    </CommentInnerWrapper>
  );
}
Example #6
Source File: MessageInput.tsx    From convoychat with GNU General Public License v3.0 4 votes vote down vote up
MessageInput: MessageInputType = ({
  value,
  innerRef,
  onCancel,
  handleSubmit,
  handleChange,
  onEmojiClick,
  mentionSuggestions,
  setValue,
  ...props
}) => {
  const isMobile = !mql.matches;
  const formRef = useRef<HTMLFormElement>();
  const textareaRef = useRef<HTMLTextAreaElement>();
  const suggestionsData = useRef<ISuggestionsData>();

  const {
    open,
    getRootProps,
    isDragActive,
    getInputProps,
    uploadImageInProgress,
  } = useImageUpload({
    value,
    setValue,
  });

  const imparativeSubmit = (event: any) => {
    event.preventDefault();
    formRef?.current.dispatchEvent(new Event("submit", { cancelable: true }));
  };

  const handleKeydown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
    if (event.key === "Escape") {
      onCancel && onCancel();
    }
    if (isMobile) return;
    if (event.key === "Enter" && !event.shiftKey) {
      imparativeSubmit(event);
    }
  };

  const getRef: any = (e: any) => {
    textareaRef.current = e;
    if (innerRef) {
      innerRef.current = e;
    }
  };

  useEffect(() => {
    textareaAutoResize(textareaRef?.current);
  }, [value]);

  useEffect(() => {
    suggestionsData.current = mentionSuggestions?.map(curr => {
      return {
        display: curr.username,
        id: curr.id,
      };
    });
  }, [mentionSuggestions]);

  return (
    <MessageInputWrapper className="message__input">
      <Flex gap="large" align="center" justify="space-between" nowrap>
        <IconButton
          onClick={open}
          icon={<FaImage />}
          data-testid="upload-button"
          isLoading={uploadImageInProgress}
        />

        <form
          ref={formRef}
          onSubmit={handleSubmit}
          className={isDragActive ? "active-animation" : ""}
        >
          <div {...getRootProps({ className: "dropzone" })}>
            <MentionsInput
              data-testid="messageInput"
              name={"message"}
              inputRef={getRef}
              autoComplete={"off"}
              placeholder="Write something"
              value={value}
              onChange={handleChange}
              onKeyDown={handleKeydown}
              style={defaultMentionStyles}
              allowSuggestionsAboveCursor={true}
              {...props}
            >
              <Mention
                trigger="@"
                data={suggestionsData?.current || []}
                displayTransform={id =>
                  `@${suggestionsData.current.find(i => i.id === id).display} `
                }
              />
            </MentionsInput>
            <input {...getInputProps()} data-testid="dropzone" />
          </div>
        </form>
        {isMobile && (
          <SendButton
            icon={FaPaperPlane}
            onClick={imparativeSubmit}
            className="input__send-button"
          />
        )}
        {!isMobile && (
          <Dropdown>
            <Dropdown.Toggle>
              <IconButton icon={<FaSmile />} />
            </Dropdown.Toggle>
            <Dropdown.Content style={{ position: "absolute", bottom: 0 }}>
              <Picker
                set="apple"
                theme="dark"
                onSelect={(emoji: any) => onEmojiClick && onEmojiClick(emoji)}
              />
            </Dropdown.Content>
          </Dropdown>
        )}
      </Flex>
    </MessageInputWrapper>
  );
}
Example #7
Source File: EmojiInput.tsx    From glific-frontend with GNU Affero General Public License v3.0 4 votes vote down vote up
EmojiInput: React.FC<EmojiInputProps> = ({
  field: { value, name, onBlur },
  handleChange,
  getEditorValue,
  handleBlur,
  ...props
}: EmojiInputProps) => {
  const [showEmojiPicker, setShowEmojiPicker] = useState(false);
  const { t } = useTranslation();

  const updateValue = (input: any, isEmoji = false) => {
    const editorContentState = value.getCurrentContent();
    const editorSelectionState: any = value.getSelection();
    const ModifiedContent = Modifier.replaceText(
      editorContentState,
      editorSelectionState,
      isEmoji ? input.native : input
    );
    let updatedEditorState = EditorState.push(value, ModifiedContent, 'insert-characters');
    if (!isEmoji) {
      const editorSelectionStateMod = updatedEditorState.getSelection();
      const updatedSelection = editorSelectionStateMod.merge({
        anchorOffset: editorSelectionStateMod.getAnchorOffset() - 1,
        focusOffset: editorSelectionStateMod.getFocusOffset() - 1,
      });
      updatedEditorState = EditorState.forceSelection(updatedEditorState, updatedSelection);
    }
    props.form.setFieldValue(name, updatedEditorState);
  };

  const handleKeyCommand = (command: any, editorState: any) => {
    if (command === 'underline') {
      return 'handled';
    }
    if (command === 'bold') {
      updateValue('**');
    } else if (command === 'italic') {
      updateValue('__');
    } else {
      const newState = RichUtils.handleKeyCommand(editorState, command);
      if (newState) {
        props.form.setFieldValue(name, newState);
        return 'handled';
      }
    }
    return 'not-handled';
  };

  const draftJsChange = (editorState: any) => {
    if (handleChange) {
      handleChange(getPlainTextFromEditor(props.form.values.example));
    }
    if (getEditorValue) {
      getEditorValue(editorState);
    } else {
      props.form.setFieldValue(name, editorState);
    }
  };

  const mentions = props.inputProp?.suggestions || [];

  const [open, setOpen] = useState(false);
  const [suggestions, setSuggestions] = useState(mentions);

  const onOpenChange = (_open: boolean) => {
    setOpen(_open);
  };

  const getSuggestions = useCallback(customSuggestionsFilter, []);

  const onSearchChange = ({ value: searchValue }: { value: string }) => {
    setSuggestions(getSuggestions(searchValue, mentions));
  };

  const inputProps = {
    component: Editor,
    editorState: value,
    open,
    readOnly: props.disabled,
    suggestions,
    onOpenChange,
    onSearchChange,
    handlePastedText: (text: string, html: string, editorState: EditorState) => {
      const pastedBlocks = ContentState.createFromText(text).getBlockMap();
      const newState = Modifier.replaceWithFragment(
        editorState.getCurrentContent(),
        editorState.getSelection(),
        pastedBlocks
      );
      const newEditorState = EditorState.push(editorState, newState, 'insert-fragment');
      draftJsChange(newEditorState);
      return 'handled';
    },
    handleKeyCommand,
    onBlur: handleBlur,
    onChange: draftJsChange,
  };

  const editor = { inputComponent: DraftField, inputProps };

  const emojiPicker = showEmojiPicker ? (
    <Picker
      data-testid="emoji-container"
      title={t('Pick your emoji…')}
      emoji="point_up"
      style={{ position: 'absolute', top: '10px', right: '0px', zIndex: 2 }}
      onSelect={(emojiValue) => updateValue(emojiValue, true)}
    />
  ) : (
    ''
  );

  const handleClickAway = () => {
    setShowEmojiPicker(false);
  };

  const picker = (
    <ClickAwayListener onClickAway={handleClickAway}>
      <InputAdornment position="end" className={Styles.EmojiPosition}>
        <IconButton
          color="primary"
          data-testid="emoji-picker"
          aria-label="pick emoji"
          component="span"
          className={Styles.Emoji}
          onClick={() => setShowEmojiPicker(!showEmojiPicker)}
        >
          <span role="img" aria-label="pick emoji">
            ?
          </span>
        </IconButton>

        {emojiPicker}
      </InputAdornment>
    </ClickAwayListener>
  );

  const input = (
    <Input field={{ name, value, onBlur }} {...props} editor={editor} emojiPicker={picker} />
  );

  return input;
}
Example #8
Source File: WhatsAppEditor.tsx    From glific-frontend with GNU Affero General Public License v3.0 4 votes vote down vote up
WhatsAppEditor: React.SFC<WhatsAppEditorProps> = (props) => {
  const { setEditorState, sendMessage, editorState, handleHeightChange, readOnly = false } = props;
  const [showEmojiPicker, setShowEmojiPicker] = useState(false);
  const { t } = useTranslation();

  const handleChange = (editorStateChange: any) => {
    setEditorState(editorStateChange);
  };

  const updateValue = (input: any, isEmoji: boolean = false) => {
    const editorContentState = editorState.getCurrentContent();
    const editorSelectionState: any = editorState.getSelection();
    const ModifiedContent = Modifier.replaceText(
      editorContentState,
      editorSelectionState,
      isEmoji ? input.native : input
    );
    let updatedEditorState = EditorState.push(editorState, ModifiedContent, 'insert-characters');
    if (!isEmoji) {
      const editorSelectionStateMod = updatedEditorState.getSelection();
      const updatedSelection = editorSelectionStateMod.merge({
        anchorOffset: editorSelectionStateMod.getAnchorOffset() - 1,
        focusOffset: editorSelectionStateMod.getFocusOffset() - 1,
      });
      updatedEditorState = EditorState.forceSelection(updatedEditorState, updatedSelection);
    }
    setEditorState(updatedEditorState);
  };

  const handleKeyCommand = (command: string, editorStateChange: any) => {
    // On enter, submit. Otherwise, deal with commands like normal.
    if (command === 'enter') {
      // Convert Draft.js to WhatsApp
      sendMessage(getPlainTextFromEditor(editorStateChange));
      return 'handled';
    }

    if (command === 'underline') {
      return 'handled';
    }

    if (command === 'bold') {
      updateValue('**');
    } else if (command === 'italic') {
      updateValue('__');
    } else {
      const newState = RichUtils.handleKeyCommand(editorStateChange, command);
      if (newState) {
        setEditorState(newState);
        return 'handled';
      }
    }
    return 'not-handled';
  };

  const keyBindingFn = (e: any) => {
    // Shift-enter is by default supported. Only 'enter' needs to be changed.
    if (e.keyCode === 13 && !e.nativeEvent.shiftKey) {
      return 'enter';
    }
    return getDefaultKeyBinding(e);
  };

  const handleClickAway = () => {
    setShowEmojiPicker(false);
  };

  const emojiStyles: any = {
    position: 'absolute',
    bottom: '60px',
    right: '-150px',
    zIndex: 100,
  };

  if (window.innerWidth <= 768) {
    emojiStyles.right = '5%';
  }

  return (
    <>
      <ReactResizeDetector
        data-testid="resizer"
        handleHeight
        onResize={(width: any, height: any) => handleHeightChange(height - 40)} // 40 is the initial height
      >
        <div className={styles.Editor}>
          <Editor
            data-testid="editor"
            editorState={editorState}
            onChange={handleChange}
            handleKeyCommand={handleKeyCommand}
            keyBindingFn={keyBindingFn}
            placeholder={t('Type a message...')}
            readOnly={readOnly}
          />
        </div>
      </ReactResizeDetector>
      <ClickAwayListener onClickAway={handleClickAway}>
        <div>
          <div className={styles.EmojiButton}>
            <IconButton
              data-testid="emoji-picker"
              color="primary"
              aria-label="pick emoji"
              component="span"
              onClick={() => setShowEmojiPicker(!showEmojiPicker)}
            >
              <span role="img" aria-label="pick emoji">
                ?
              </span>
            </IconButton>
          </div>
          {showEmojiPicker ? (
            <Picker
              data-testid="emoji-popup"
              title={t('Pick your emoji…')}
              emoji="point_up"
              style={emojiStyles}
              onSelect={(emoji) => updateValue(emoji, true)}
            />
          ) : null}
        </div>
      </ClickAwayListener>
    </>
  );
}
Example #9
Source File: Chat.tsx    From SkyOffice with MIT License 4 votes vote down vote up
export default function Chat() {
  const [inputValue, setInputValue] = useState('')
  const [showEmojiPicker, setShowEmojiPicker] = useState(false)
  const messagesEndRef = useRef<HTMLDivElement>(null)
  const inputRef = useRef<HTMLInputElement>(null)
  const chatMessages = useAppSelector((state) => state.chat.chatMessages)
  const focused = useAppSelector((state) => state.chat.focused)
  const showChat = useAppSelector((state) => state.chat.showChat)
  const dispatch = useAppDispatch()
  const game = phaserGame.scene.keys.game as Game

  const handleChange = (event: React.FormEvent<HTMLInputElement>) => {
    setInputValue(event.currentTarget.value)
  }

  const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
    if (event.key === 'Escape') {
      // move focus back to the game
      inputRef.current?.blur()
      dispatch(setShowChat(false))
    }
  }

  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault()
    // move focus back to the game
    inputRef.current?.blur()

    const val = inputValue.trim()
    setInputValue('')
    if (val) {
      game.network.addChatMessage(val)
      game.myPlayer.updateDialogBubble(val)
    }
  }

  const scrollToBottom = () => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
  }

  useEffect(() => {
    if (focused) {
      inputRef.current?.focus()
    }
  }, [focused])

  useEffect(() => {
    scrollToBottom()
  }, [chatMessages, showChat])

  return (
    <Backdrop>
      <Wrapper>
        {showChat ? (
          <>
            <ChatHeader>
              <h3>Chat</h3>
              <IconButton
                aria-label="close dialog"
                className="close"
                onClick={() => dispatch(setShowChat(false))}
                size="small"
              >
                <CloseIcon />
              </IconButton>
            </ChatHeader>
            <ChatBox>
              {chatMessages.map(({ messageType, chatMessage }, index) => (
                <Message chatMessage={chatMessage} messageType={messageType} key={index} />
              ))}
              <div ref={messagesEndRef} />
              {showEmojiPicker && (
                <EmojiPickerWrapper>
                  <Picker
                    theme="dark"
                    showSkinTones={false}
                    showPreview={false}
                    onSelect={(emoji) => {
                      setInputValue(inputValue + emoji.native)
                      setShowEmojiPicker(!showEmojiPicker)
                      dispatch(setFocused(true))
                    }}
                    exclude={['recent', 'flags']}
                  />
                </EmojiPickerWrapper>
              )}
            </ChatBox>
            <InputWrapper onSubmit={handleSubmit}>
              <InputTextField
                inputRef={inputRef}
                autoFocus={focused}
                fullWidth
                placeholder="Press Enter to chat"
                value={inputValue}
                onKeyDown={handleKeyDown}
                onChange={handleChange}
                onFocus={() => {
                  if (!focused) dispatch(setFocused(true))
                }}
                onBlur={() => dispatch(setFocused(false))}
              />
              <IconButton aria-label="emoji" onClick={() => setShowEmojiPicker(!showEmojiPicker)}>
                <InsertEmoticonIcon />
              </IconButton>
            </InputWrapper>
          </>
        ) : (
          <FabWrapper>
            <Fab
              color="secondary"
              aria-label="showChat"
              onClick={() => {
                dispatch(setShowChat(true))
                dispatch(setFocused(true))
              }}
            >
              <ChatBubbleOutlineIcon />
            </Fab>
          </FabWrapper>
        )}
      </Wrapper>
    </Backdrop>
  )
}
Example #10
Source File: foot.tsx    From sphinx-win-linux-desktop with MIT License 4 votes vote down vote up
export default function Foot({
  height,
  messagePrice,
  tribeBots,
  msgPrice,
  setMsgPrice,
}) {
  const { ui, msg, meme, details, user } = useStores();
  const [recording, setRecording] = useState(false);
  const [record, setRecord] = useState(false);
  const [uploading, setUploading] = useState(false);

  return useObserver(() => {
    const myid = user.myid;
    const chat = ui.selectedChat;
    let text = (chat ? ui.tribeText[chat.id] : "") || "";

    useEffect(() => {
      if (recording) {
        setRecord(true);
      }
    }, [recording]);

    const msgInputRef = useRef();
    // if height change, maybe clicked reply
    const oldHeight = useRef(height);
    useEffect(() => {
      if (oldHeight.current < height) {
        if (msgInputRef && msgInputRef.current) {
          (msgInputRef.current as any).focus();
        }
      }
    }, [height]);

    async function sendGif(amount: number) {
      const params = ui.imgViewerParams;
      const gifJSON = JSON.stringify({
        id: params.id,
        url: params.data,
        aspect_ratio: params.aspect_ratio,
        text: text,
      });
      const b64 = btoa(gifJSON);
      let contact_id = chat.contact_ids.find((cid) => cid !== myid);
      await msg.sendMessage({
        contact_id,
        chat_id: chat.id,
        text: "giphy::" + b64,
        reply_uuid: "",
        amount: amount || 0,
      });
      ui.setImgViewerParams(null);
      ui.setTribeText(chat.id, "");
    }

    async function sendPaidMsg() {
      setUploading(true);
      const server = meme.getDefaultServer();
      const file = new File([text], "message.txt", {
        type: "text/plain;charset=utf-8",
      });
      const r = await uploadFile(
        file,
        "sphinx/text",
        server.host,
        server.token,
        "message.txt"
      );
      await msg.sendAttachment({
        contact_id: null,
        chat_id: chat.id,
        muid: r.muid,
        media_key: r.media_key,
        media_type: "sphinx/text",
        text: "",
        price: parseInt(msgPrice) || 0,
        amount: messagePrice || 0,
      });
      ui.setTribeText(chat.id, "");
      setMsgPrice("");
      setUploading(false);
    }

    function sendMessage() {
      if (!text) return;
      if (msgPrice) {
        return sendPaidMsg();
      }
      let contact_id = chat.contact_ids.find((cid) => cid !== myid);
      let { price, failureMessage } = calcBotPrice(tribeBots, text);
      if (failureMessage) {
        return alert(failureMessage);
      }
      if (price > details.balance) {
        return alert("Not enough balance");
      }

      if (ui.imgViewerParams && ui.imgViewerParams.type === "image/gif") {
        return sendGif(messagePrice + price);
      }

      let txt = text;
      if (ui.extraTextContent) {
        const { type, ...rest } = ui.extraTextContent;
        txt = type + "::" + JSON.stringify({ ...rest, text });
      }
      msg.sendMessage({
        contact_id,
        text: txt,
        chat_id: chat.id || null,
        amount: messagePrice + price || 0, // 5, // CHANGE THIS
        reply_uuid: ui.replyUUID || "",
      });
      ui.setTribeText(chat.id, "");
      if (ui.replyUUID) ui.setReplyUUID("");
      if (ui.extraTextContent) ui.setExtraTextContent(null);
    }

    let [count, setCount] = useState(0);

    useInterval(
      () => {
        // Your custom logic here
        setCount(count + 1);
      },
      recording ? 1000 : null
    );

    function duration(seconds) {
      var start = moment(0);
      var end = moment(seconds * 1000);
      let diff = end.diff(start);
      return moment.utc(diff).format("m:ss");
    }

    async function onStop(res) {
      const blob = res.blob;
      const file = new File([blob], "Audio.wav", { type: blob.type });
      const server = meme.getDefaultServer();
      setUploading(true);
      const r = await uploadFile(
        file,
        blob.type,
        server.host,
        server.token,
        "Audio.wav"
      );
      await msg.sendAttachment({
        contact_id: null,
        chat_id: chat.id,
        muid: r.muid,
        media_key: r.media_key,
        media_type: blob.type,
        text: "",
        price: 0,
        amount: 0,
      });
      setUploading(false);
      setRecording(false);
      setCount(0);
    }

    const [anchorEl, setAnchorEl] = React.useState(null);

    const handleClick = (event) => {
      setAnchorEl(event.currentTarget);
    };

    const handleClose = () => {
      setAnchorEl(null);
    };
    const open = Boolean(anchorEl);
    const id = open ? "simple-popover" : undefined;
    const msgs = chat && msg.messages[chat.id];

    const { replyMessageSenderAlias, replyMessageContent, replyColor } =
      useReplyContent(msgs, ui.replyUUID, ui.extraTextContent);

    const [showGiphy, setShowGiphy] = useState(false);
    function handleGiphy() {
      setShowGiphy(true);
    }

    function handlePriceChange(e) {
      let numRegex = /^\d+$/;
      if (numRegex.test(e.target.value) || e.target.value === "") {
        setMsgPrice(e.target.value);
      }
    }

    if (ui.showBots) {
      return <></>;
    }
    if (recording) {
      return (
        <MicWrap style={{ background: theme.bg, height }}>
          <Blinker>
            <BlinkingButton
              style={{
                height: 10,
                padding: 7,
                backgroundColor: "#ea7574",
                marginTop: -1,
              }}
            />
          </Blinker>
          <WaveWrap>
            <ReactMic
              className="sound-wave"
              record={record}
              backgroundColor={theme.bg}
              onStop={onStop}
              // onStart={onStart}
              strokeColor="#ffffff"
            />
          </WaveWrap>
          <div
            style={{
              color: "white",
              height: 25,
              marginTop: 8,
              marginRight: 10,
            }}
          >
            {duration(count)}
          </div>
          <IconButton
            style={{
              width: 39,
              height: 39,
              marginRight: 17,
              backgroundColor: "#ea7574",
              opacity: uploading ? 0.8 : 1,
            }}
            onClick={() => {
              setRecord(false), setRecording(false), setCount(0);
            }}
            disabled={uploading}
          >
            <Close
              style={{ color: "white", fontSize: 30, borderRadius: "50%" }}
            />
          </IconButton>
          <IconButton
            style={{
              width: 39,
              height: 39,
              marginRight: 17,
              backgroundColor: "#47ca97",
              opacity: uploading ? 0.8 : 1,
            }}
            onClick={() => setRecord(false)}
            disabled={uploading}
          >
            <Check
              style={{ color: "white", fontSize: 30, borderRadius: "50%" }}
            />
          </IconButton>
        </MicWrap>
      );
    }

    return (
      <Wrap style={{ background: theme.bg, height }}>
        {replyMessageContent && replyMessageSenderAlias && (
          <ReplyMsg color={replyColor || "grey"}>
            <ReplyMsgText>
              <span style={{ color: "white" }}>{replyMessageSenderAlias}</span>
              <span style={{ color: "#809ab7", marginTop: 5 }}>
                {replyMessageContent}
              </span>
            </ReplyMsgText>
            <CloseButton
              style={{ cursor: "pointer" }}
              onClick={() => {
                ui.setReplyUUID(null);
                ui.setExtraTextContent(null);
              }}
            />
          </ReplyMsg>
        )}
        {showGiphy && (
          <GiphyWrap>
            <ReactGiphySearchbox
              style={{ position: "absolute" }}
              apiKey="cnc84wQZqQn2vsWeg4sYK3RQJSrYPAl7"
              onSelect={(item) => {
                const data = item.images.original.url;
                const height = parseInt(item.images.original.height) || 200;
                const width = parseInt(item.images.original.width) || 200;
                ui.setImgViewerParams({
                  data,
                  aspect_ratio: width / height,
                  id: item.id,
                  type: "image/gif",
                });
                setShowGiphy(false);
              }}
            />
            <CloseWrap onClick={() => setShowGiphy(false)}>
              CLOSE <CloseButton />
            </CloseWrap>
          </GiphyWrap>
        )}
        <InnerWrap>
          <IconButton
            style={{
              pointerEvents:
                chat && chat.type === constants.chat_types.conversation
                  ? "auto"
                  : "none",
              cursor: "pointer",
              height: 30,
              width: 30,
              marginLeft: 10,
              backgroundColor: "#618af8",
            }}
            onClick={() => ui.setSendRequestModal(chat)}
          >
            <AddIcon
              style={{ color: chat ? "#ffffff" : "#b0c4ff", fontSize: 22 }}
            />
          </IconButton>
          <img
            src={giphyIcon}
            onClick={chat && handleGiphy}
            style={{
              cursor: chat ? "pointer" : "auto",
              marginLeft: "15px",
              filter: chat ? "grayscale(0%)" : "grayscale(75%)",
            }}
          />
          <InsertEmoticonButton
            style={{
              pointerEvents: chat ? "auto" : "none",
              cursor: "pointer",
              marginLeft: 10,
              color: chat ? "#8f9ca9" : "#2a3540",
              fontSize: 30,
            }}
            aria-describedby={id}
            onClick={handleClick}
          />
          <Popover
            id={id}
            open={open}
            anchorEl={anchorEl}
            onClose={handleClose}
            anchorOrigin={{
              vertical: "top",
              horizontal: "right",
            }}
            transformOrigin={{
              vertical: "bottom",
              horizontal: "left",
            }}
          >
            <Picker
              showPreview={false}
              showSkinTones={false}
              onSelect={(emoji) =>
                ui.setTribeText(chat.id, text + emoji.native)
              }
            />
          </Popover>
          <Input
            value={text}
            onChange={(e) => ui.setTribeText(chat.id, e.target.value)}
            placeholder="Message"
            ref={msgInputRef}
            style={{
              background: theme.extraDeep,
              fontSize: 18,
              textAlign: "left",
            }}
            disabled={!chat || chat.status === constants.chat_statuses.pending}
            onKeyPress={(e) => {
              if (e.key === "Enter") {
                e.preventDefault(), sendMessage();
              }
            }}
          ></Input>
          <PriceInput theme={theme}>
            <div>Price:</div>
            <PriceAmount
              onChange={handlePriceChange}
              value={msgPrice}
              theme={theme}
              disabled={!chat}
            />
          </PriceInput>
          <IconButton
            style={{
              width: 39,
              height: 39,
              marginRight: 10,
              marginLeft: 10,
              backgroundColor: "#618af8",
            }}
            disabled={!chat || !text || uploading}
            onClick={sendMessage}
          >
            <SendIcon
              style={{ color: chat ? "#ffffff" : "#b0c4ff", fontSize: 22 }}
            />
          </IconButton>
          <IconButton
            style={{
              width: 39,
              height: 39,
              marginRight: 10,
              backgroundColor: "transparent",
            }}
            disabled={!chat}
            onClick={() => setRecording(true)}
          >
            <MicIcon
              style={{ color: chat ? "#8f9ca9" : "#2a3540", fontSize: 30 }}
            />
          </IconButton>
        </InnerWrap>
      </Wrap>
    );
  });
}
Example #11
Source File: Chat.tsx    From sync-party with GNU General Public License v3.0 4 votes vote down vote up
export default function Chat({
    isActive,
    socket,
    setPlayerFocused,
    freezeUiVisible
}: Props): ReactElement {
    const party = useSelector((state: RootAppState) => state.globalState.party);
    const user = useSelector((state: RootAppState) => state.globalState.user);
    const chat = useSelector((state: RootAppState) => state.globalState.chat);
    const uiVisible = useSelector(
        (state: RootAppState) => state.globalState.uiVisible
    );
    const uiFocused = useSelector(
        (state: RootAppState) => state.globalState.uiFocused
    );

    const { t } = useTranslation();
    const dispatch = useDispatch();

    const [textInput, setTextInput] = useState('');
    const [showEmojiPicker, setShowEmojiPicker] = useState(false);
    const [historyStaysVisible, setHistoryStaysVisible] = useState(false);

    const chatHistoryRef = useRef<HTMLDivElement | null>(null);
    const textInputRef = useRef<HTMLTextAreaElement | null>(null);

    const scrollHistoryToBottom = (): void => {
        if (chatHistoryRef && chatHistoryRef.current) {
            chatHistoryRef.current.scrollTop =
                chatHistoryRef.current.scrollHeight;
        }
    };

    const sendMessage = (message: string): void => {
        if (socket && user && party && message !== '') {
            const chatMessage = {
                userId: user.id,
                partyId: party.id,
                userName: user.username,
                message: message
            };
            socket.emit('chatMessage', chatMessage);
            setTextInput('');
        }
    };

    const focusTextInput = useCallback((): void => {
        if (textInputRef.current) {
            textInputRef.current.focus();
            freezeUiVisible(true);
        }
    }, [freezeUiVisible]);

    const blurTextInput = (): void => {
        if (textInputRef.current) {
            textInputRef.current.blur();
        }
    };

    const handleInputFieldKeyDown = (event: React.KeyboardEvent): void => {
        if (event.key === 'Enter') {
            event.preventDefault();
            sendMessage(textInput);
            freezeUiVisible(false);
            setShowEmojiPicker(false);
        } else if (event.key === 'Escape') {
            if (showEmojiPicker) {
                setShowEmojiPicker(false);
                focusTextInput();
            } else {
                setPlayerFocused(true);
                freezeUiVisible(false);
                blurTextInput();
            }
        }
    };

    const handleEmojiPickerKeydown = (event: React.KeyboardEvent): void => {
        if (event.key === 'Escape') {
            setShowEmojiPicker(false);
            focusTextInput();
        }
    };

    const handleEmojiPickerIconClick = (): void => {
        if (!showEmojiPicker) {
            freezeUiVisible(true);
        }
        setShowEmojiPicker(!showEmojiPicker);
        setPlayerFocused(!showEmojiPicker);
        focusTextInput();
    };

    const addEmoji = (emoji: BaseEmoji): void => {
        if (textInputRef.current) {
            textInputRef.current.focus();

            setTimeout(() => {
                if (textInputRef.current) {
                    const newCursorPosition =
                        textInputRef.current.selectionStart +
                        emoji.native.length;

                    const textBeforeCursorPosition =
                        textInputRef.current.value.substring(
                            0,
                            textInputRef.current.selectionStart
                        );
                    const textAfterCursorPosition =
                        textInputRef.current.value.substring(
                            textInputRef.current.selectionEnd,
                            textInputRef.current.value.length
                        );

                    setTextInput(
                        textBeforeCursorPosition +
                            emoji.native +
                            textAfterCursorPosition
                    );

                    textInputRef.current.selectionStart =
                        textInputRef.current.selectionEnd = newCursorPosition;
                }
            }, 10);
        }
    };

    // At mounting
    useEffect(() => {
        scrollHistoryToBottom();
    }, [isActive]);

    // If isActive changes
    useEffect(() => {
        if (isActive !== uiFocused.chat) {
            if (isActive) {
                setTimeout(() => {
                    scrollHistoryToBottom();
                    focusTextInput();
                }, 50);
            } else {
                setHistoryStaysVisible(false);
            }

            dispatch(
                setGlobalState({
                    uiFocused: {
                        ...uiFocused,
                        chat: isActive
                    }
                })
            );
        }
    }, [isActive, dispatch, focusTextInput, uiFocused]);

    // If ui visibility or history timeout changes
    useEffect(() => {
        scrollHistoryToBottom();
    }, [uiVisible, historyStaysVisible]);

    // If there is a new message
    useEffect(() => {
        scrollHistoryToBottom();
        setHistoryStaysVisible(true);
        const historyTimeoutId = setTimeout(() => {
            setHistoryStaysVisible(false);
        }, 12000);

        return (): void => {
            clearTimeout(historyTimeoutId);
        };
    }, [chat, freezeUiVisible]);

    return (
        <div
            className={
                'absolute bottom-0 left-0 ml-3' +
                (uiVisible ? ' mb-24' : ' mb-10')
            }
        >
            <div className="flex flex-row">
                <div className="flex flex-col mt-auto z-50">
                    {!(uiVisible && !isActive && !historyStaysVisible) &&
                        (uiVisible || historyStaysVisible) &&
                        party &&
                        user &&
                        chat[party.id] && (
                            <ChatHistory
                                chatHistoryRef={chatHistoryRef}
                                chat={chat}
                                party={party}
                                userId={user.id}
                                isActive={isActive}
                                uiVisible={uiVisible}
                                t={t}
                            ></ChatHistory>
                        )}
                    {isActive && uiVisible && (
                        <div className="mt-auto">
                            <ChatInput
                                textInputRef={textInputRef}
                                textInput={textInput}
                                setPlayerFocused={setPlayerFocused}
                                freezeUiVisible={freezeUiVisible}
                                handleInputFieldKeyDown={
                                    handleInputFieldKeyDown
                                }
                                setTextInput={setTextInput}
                                t={t}
                            ></ChatInput>
                        </div>
                    )}
                </div>
                {isActive && (
                    <div className="mt-auto">
                        {showEmojiPicker && uiVisible && (
                            <div
                                className="ml-2 mb-1"
                                onKeyDown={handleEmojiPickerKeydown}
                            >
                                <Picker
                                    native={true}
                                    sheetSize={16}
                                    showPreview={false}
                                    useButton={false}
                                    onSelect={(emoji: BaseEmoji): void => {
                                        addEmoji(emoji);
                                    }}
                                ></Picker>
                            </div>
                        )}
                        {!showEmojiPicker && uiVisible && (
                            <FontAwesomeIcon
                                icon={faSmile}
                                className="ml-2 cursor-pointer text-2xl mb-1"
                                onClick={handleEmojiPickerIconClick}
                            ></FontAwesomeIcon>
                        )}
                    </div>
                )}
            </div>
        </div>
    );
}