jiti-meet/react/features/chat/components/web/ChatInput.tsx

264 lines
7.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { Component, RefObject } from 'react';
import { WithTranslation } from 'react-i18next';
import { IReduxState, IStore } from '../../../app/types';
import { isMobileBrowser } from '../../../base/environment/utils';
import { translate } from '../../../base/i18n/functions';
import { IconPlane, IconSmile } from '../../../base/icons/svg';
import { connect } from '../../../base/redux/functions';
import Button from '../../../base/ui/components/web/Button';
import Input from '../../../base/ui/components/web/Input';
import { areSmileysDisabled } from '../../functions';
// @ts-ignore
import SmileysPanel from './SmileysPanel';
/**
* The type of the React {@code Component} props of {@link ChatInput}.
*/
interface IProps extends WithTranslation {
/**
* Whether chat emoticons are disabled.
*/
_areSmileysDisabled: boolean;
/**
* Invoked to send chat messages.
*/
dispatch: IStore['dispatch'];
/**
* Callback to invoke on message send.
*/
onSend: Function;
}
/**
* The type of the React {@code Component} state of {@link ChatInput}.
*/
interface IState {
/**
* User provided nickname when the input text is provided in the view.
*/
message: string;
/**
* Whether or not the smiley selector is visible.
*/
showSmileysPanel: boolean;
}
/**
* Implements a React Component for drafting and submitting a chat message.
*
* @augments Component
*/
class ChatInput extends Component<IProps, IState> {
_textArea?: RefObject<HTMLTextAreaElement>;
state = {
message: '',
showSmileysPanel: false
};
/**
* Initializes a new {@code ChatInput} instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props: IProps) {
super(props);
this._textArea = React.createRef<HTMLTextAreaElement>();
// Bind event handlers so they are only bound once for every instance.
this._onDetectSubmit = this._onDetectSubmit.bind(this);
this._onMessageChange = this._onMessageChange.bind(this);
this._onSmileySelect = this._onSmileySelect.bind(this);
this._onSubmitMessage = this._onSubmitMessage.bind(this);
this._toggleSmileysPanel = this._toggleSmileysPanel.bind(this);
}
/**
* Implements React's {@link Component#componentDidMount()}.
*
* @inheritdoc
*/
componentDidMount() {
if (isMobileBrowser()) {
// Ensure textarea is not focused when opening chat on mobile browser.
this._textArea?.current && this._textArea.current.blur();
}
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
return (
<div className = { `chat-input-container${this.state.message.trim().length ? ' populated' : ''}` }>
<div id = 'chat-input' >
{!this.props._areSmileysDisabled && this.state.showSmileysPanel && (
<div
className = 'smiley-input'>
<div
className = 'smileys-panel' >
<SmileysPanel
onSmileySelect = { this._onSmileySelect } />
</div>
</div>
)}
<Input
autoFocus = { true }
className = 'chat-input'
icon = { this.props._areSmileysDisabled ? undefined : IconSmile }
iconClick = { this._toggleSmileysPanel }
maxRows = { 5 }
onChange = { this._onMessageChange }
onKeyPress = { this._onDetectSubmit }
placeholder = { this.props.t('chat.messagebox') }
ref = { this._textArea }
textarea = { true }
value = { this.state.message } />
<Button
accessibilityLabel = { this.props.t('chat.sendButton') }
disabled = { !this.state.message.trim() }
icon = { IconPlane }
onClick = { this._onSubmitMessage }
size = { isMobileBrowser() ? 'large' : 'medium' } />
</div>
</div>
);
}
/**
* Place cursor focus on this component's text area.
*
* @private
* @returns {void}
*/
_focus() {
this._textArea?.current && this._textArea.current.focus();
}
/**
* Submits the message to the chat window.
*
* @returns {void}
*/
_onSubmitMessage() {
const trimmed = this.state.message.trim();
if (trimmed) {
this.props.onSend(trimmed);
this.setState({ message: '' });
// Keep the textarea in focus when sending messages via submit button.
this._focus();
}
}
/**
* Detects if enter has been pressed. If so, submit the message in the chat
* window.
*
* @param {string} event - Keyboard event.
* @private
* @returns {void}
*/
_onDetectSubmit(event: any) {
// Composition events used to add accents to characters
// despite their absence from standard US keyboards,
// to build up logograms of many Asian languages
// from their base components or categories and so on.
if (event.isComposing || event.keyCode === 229) {
// keyCode 229 means that user pressed some button,
// but input method is still processing that.
// This is a standard behavior for some input methods
// like entering japanese or сhinese hieroglyphs.
return;
}
if (event.key === 'Enter'
&& event.shiftKey === false
&& event.ctrlKey === false) {
event.preventDefault();
event.stopPropagation();
this._onSubmitMessage();
}
}
/**
* Updates the known message the user is drafting.
*
* @param {string} value - Keyboard event.
* @private
* @returns {void}
*/
_onMessageChange(value: string) {
this.setState({ message: value });
}
/**
* Appends a selected smileys to the chat message draft.
*
* @param {string} smileyText - The value of the smiley to append to the
* chat message.
* @private
* @returns {void}
*/
_onSmileySelect(smileyText: string) {
if (smileyText) {
this.setState({
message: `${this.state.message} ${smileyText}`,
showSmileysPanel: false
});
} else {
this.setState({
showSmileysPanel: false
});
}
this._focus();
}
/**
* Callback invoked to hide or show the smileys selector.
*
* @private
* @returns {void}
*/
_toggleSmileysPanel() {
if (this.state.showSmileysPanel) {
this._focus();
}
this.setState({ showSmileysPanel: !this.state.showSmileysPanel });
}
}
/**
* Function that maps parts of Redux state tree into component props.
*
* @param {Object} state - Redux state.
* @private
* @returns {{
* _areSmileysDisabled: boolean
* }}
*/
const mapStateToProps = (state: IReduxState) => {
return {
_areSmileysDisabled: areSmileysDisabled(state)
};
};
export default translate(connect(mapStateToProps)(ChatInput));