Refactor Chat

This commit is contained in:
Jeremie Pardou-Piquemal 2022-12-15 23:15:55 +01:00
parent 7eb31f13e3
commit 31db176328

View File

@ -1,22 +1,22 @@
import React, { Component } from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import sanitizeHtml from 'sanitize-html'; import sanitizeHtml from 'sanitize-html';
import { CornerDownRight } from 'react-feather'; import { CornerDownRight } from 'react-feather';
import { getSelectedText, hasTouchSupport } from '@/utils/dom'; import { hasTouchSupport } from '@/utils/dom';
import FileTransfer from '@/components/FileTransfer'; import FileTransfer from '@/components/FileTransfer';
export class Chat extends Component { export const Chat = ({ sendEncryptedMessage, showNotice, userId, username, clearActivities, translations }) => {
constructor(props) { const [message, setMessage] = React.useState('');
super(props); const [shiftKeyDown, setShiftKeyDown] = React.useState(false);
this.state = { const textInputRef = React.useRef();
message: '',
touchSupport: hasTouchSupport,
shiftKeyDown: false,
};
this.commands = [ const touchSupport = hasTouchSupport;
const canSend = message.trim().length;
const commands = [
{ {
command: 'nick', command: 'nick',
description: 'Changes nickname.', description: 'Changes nickname.',
@ -45,18 +45,18 @@ export class Chat extends Component {
} }
if (errors.length) { if (errors.length) {
return this.props.showNotice({ return showNotice({
message: `${errors.join(', ')}`, message: `${errors.join(', ')}`,
level: 'error', level: 'error',
}); });
} }
this.props.sendEncryptedMessage({ sendEncryptedMessage({
type: 'CHANGE_USERNAME', type: 'CHANGE_USERNAME',
payload: { payload: {
id: this.props.userId, id: userId,
newUsername, newUsername,
currentUsername: this.props.username, currentUsername: username,
}, },
}); });
}, },
@ -64,13 +64,12 @@ export class Chat extends Component {
{ {
command: 'help', command: 'help',
description: 'Shows a list of commands.', description: 'Shows a list of commands.',
paramaters: [], parameters: [],
usage: '/help', usage: '/help',
scope: 'local', scope: 'local',
action: params => { action: () => {
// eslint-disable-line const validCommands = commands.map(command => `/${command.command}`);
const validCommands = this.commands.map(command => `/${command.command}`); showNotice({
this.props.showNotice({
message: `Valid commands: ${validCommands.sort().join(', ')}`, message: `Valid commands: ${validCommands.sort().join(', ')}`,
level: 'info', level: 'info',
}); });
@ -79,17 +78,16 @@ export class Chat extends Component {
{ {
command: 'me', command: 'me',
description: 'Invoke virtual action', description: 'Invoke virtual action',
paramaters: ['{action}'], parameters: ['{action}'],
usage: '/me {action}', usage: '/me {action}',
scope: 'global', scope: 'global',
action: params => { action: params => {
// eslint-disable-line
const actionMessage = params.join(' '); const actionMessage = params.join(' ');
if (!actionMessage.trim().length) { if (!actionMessage.trim().length) {
return false; return false;
} }
this.props.sendEncryptedMessage({ sendEncryptedMessage({
type: 'USER_ACTION', type: 'USER_ACTION',
payload: { payload: {
action: actionMessage, action: actionMessage,
@ -100,72 +98,37 @@ export class Chat extends Component {
{ {
command: 'clear', command: 'clear',
description: 'Clears the chat screen', description: 'Clears the chat screen',
paramaters: [], parameters: [],
usage: '/clear', usage: '/clear',
scope: 'local', scope: 'local',
action: (params = null) => { action: () => {
// eslint-disable-line clearActivities();
this.props.clearActivities();
}, },
}, },
]; ];
}
componentDidMount() { const handleKeyUp = e => {
if (!hasTouchSupport) {
// Disable for now due to vary issues:
// Paste not working, shift+enter line breaks
// autosize(this.textInput);
this.textInput.addEventListener('autosize:resized', () => {
this.props.scrollToBottom();
});
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.focusChat) {
if (!getSelectedText()) {
// Don't focus for now, evaluate UX benfits
// this.textInput.focus()
}
}
}
componentDidUpdate(nextProps, nextState) {
if (!nextState.message.trim().length) {
// autosize.update(this.textInput)
}
}
handleKeyUp(e) {
if (e.key === 'Shift') { if (e.key === 'Shift') {
this.setState({ setShiftKeyDown(false);
shiftKeyDown: false,
});
}
} }
};
handleKeyPress(e) { const handleKeyPress = e => {
if (e.key === 'Shift') { if (e.key === 'Shift') {
this.setState({ setShiftKeyDown(true);
shiftKeyDown: true,
});
} }
// Fix when autosize is enabled - line breaks require shift+enter twice if (e.key === 'Enter' && !hasTouchSupport && !shiftKeyDown) {
if (e.key === 'Enter' && !hasTouchSupport && !this.state.shiftKeyDown) {
e.preventDefault(); e.preventDefault();
if (this.canSend()) { if (canSend) {
this.sendMessage(); sendMessage();
} else { } else {
this.setState({ setMessage('');
message: '',
});
}
} }
} }
};
executeCommand(command) { const executeCommand = command => {
const commandToExecute = this.commands.find(cmnd => cmnd.command === command.command); const commandToExecute = commands.find(cmnd => cmnd.command === command.command);
if (commandToExecute) { if (commandToExecute) {
const { params } = command; const { params } = command;
@ -175,19 +138,19 @@ export class Chat extends Component {
} }
return null; return null;
} };
handleSendClick() { const handleSendClick = () => {
this.sendMessage.bind(this); sendMessage();
this.textInput.focus(); textInputRef.current.focus();
} };
handleFormSubmit(evt) { const handleFormSubmit = evt => {
evt.preventDefault(); evt.preventDefault();
this.sendMessage(); sendMessage();
} };
parseCommand(message) { const parseCommand = message => {
const commandTrigger = { const commandTrigger = {
command: null, command: null,
params: [], params: [],
@ -207,23 +170,22 @@ export class Chat extends Component {
} }
return false; return false;
} };
sendMessage() { const sendMessage = () => {
if (!this.canSend()) { if (!canSend) {
return; return;
} }
const { message } = this.state; const isCommand = parseCommand(message);
const isCommand = this.parseCommand(message);
if (isCommand) { if (isCommand) {
const res = this.executeCommand(isCommand); const res = executeCommand(isCommand);
if (res === false) { if (res === false) {
return; return;
} }
} else { } else {
this.props.sendEncryptedMessage({ sendEncryptedMessage({
type: 'TEXT_MESSAGE', type: 'TEXT_MESSAGE',
payload: { payload: {
text: message, text: message,
@ -232,55 +194,41 @@ export class Chat extends Component {
}); });
} }
this.setState({ setMessage('');
message: '', };
});
}
handleInputChange(evt) { const handleInputChange = evt => {
this.setState({ setMessage(evt.target.value);
message: evt.target.value, };
});
}
canSend() {
return this.state.message.trim().length;
}
render() {
const touchSupport = this.state.touchSupport;
return ( return (
<form onSubmit={this.handleFormSubmit.bind(this)} className="chat-preflight-container"> <form onSubmit={handleFormSubmit} className="chat-preflight-container">
<textarea <textarea
rows="1" rows="1"
onKeyUp={this.handleKeyUp.bind(this)} onKeyUp={handleKeyUp}
onKeyDown={this.handleKeyPress.bind(this)} onKeyDown={handleKeyPress}
ref={input => { ref={textInputRef}
this.textInput = input;
}}
autoFocus autoFocus
className="chat" className="chat"
value={this.state.message} value={message}
placeholder={this.props.translations.typePlaceholder} placeholder={translations.typePlaceholder}
onChange={this.handleInputChange.bind(this)} onChange={handleInputChange}
/> />
<div className="input-controls"> <div className="input-controls">
<FileTransfer sendEncryptedMessage={this.props.sendEncryptedMessage} /> <FileTransfer sendEncryptedMessage={sendEncryptedMessage} />
{touchSupport && ( {touchSupport && (
<button <button
onClick={this.handleSendClick.bind(this)} onClick={handleSendClick}
className={`icon is-right send btn btn-link ${this.canSend() ? 'active' : ''}`} className={`icon is-right send btn btn-link ${canSend ? 'active' : ''}`}
title="Send" title="Send"
> >
<CornerDownRight className={this.canSend() ? '' : 'disabled'} /> <CornerDownRight className={canSend ? '' : 'disabled'} />
</button> </button>
)} )}
</div> </div>
</form> </form>
); );
} };
}
Chat.propTypes = { Chat.propTypes = {
sendEncryptedMessage: PropTypes.func.isRequired, sendEncryptedMessage: PropTypes.func.isRequired,