refactor ChatBox component into a Message components and other sub components

This commit is contained in:
Iuri Matias 2019-02-02 15:58:26 +01:00
parent d50cb5e7c2
commit cea7f32b0a
10 changed files with 200 additions and 139 deletions

2
.gitignore vendored
View File

@ -21,3 +21,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
TODO

View File

@ -1,137 +0,0 @@
// @flow
import React, { Fragment, PureComponent } from 'react';
import ListItem from '@material-ui/core/ListItem';
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import ListItemText from '@material-ui/core/ListItemText';
import Avatar from '@material-ui/core/Avatar';
import YouTube from 'react-youtube';
import Linkify from 'react-linkify';
import SpotifyPlayer from 'react-spotify-player';
import { Emoji } from 'emoji-mart';
import Jazzicon, { jsNumberForAddress } from 'react-jazzicon'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { atomDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import { Matcher } from '@areknawo/rex'
import SyntaxLookup from '../utils/syntaxLookup';
import { getFile } from '../utils/ipfs';
const ipfsMatcher = new Matcher().begin().find('/ipfs/');
// TODO: not exactly bulletproof right now, needs proper regex
function hasYoutubeLink(text) {
return text.indexOf('http://www.youtube.com') >= 0 || text.indexOf('https://www.youtube.com') >= 0;
}
// TODO: not exactly bulletproof right now, needs proper regex
function isSpotifyLink(text) {
return text.indexOf('spotify:') >= 0 ;
}
// https://gist.github.com/takien/4077195#
function getYoutubeId(url) {
let ID = '';
url = url.replace(/(>|<)/gi,'').split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/);
if (url[2] !== undefined) {
ID = url[2].split(/[^0-9a-z_\-]/i);
ID = ID[0];
}
else {
ID = url;
}
return ID;
}
function isImage(text) {
return text.indexOf("http") >= 0 && (text.indexOf('.jpg') || text.indexOf('.gif'));
}
// TODO: this needs to be reviewed. best to return as a css background-image instead
function displayImage(text) {
let reg = new RegExp(/\b(https?:\/\/\S+(?:png|jpe?g|gif)\S*)\b/);
let imageUrl = reg.exec(text);
if (!imageUrl) return (<span></span>);
return (<img src={imageUrl[0]} alt="" style={{maxWidth: '90%'}} />)
}
// TODO use regex for code parsing / detection. Add new line detection for shift+enter
const MessageRender = ({ message }) => {
const emojis = [];
let match;
const regex1 = RegExp(/:[\-a-zA-Z_+0-9]+:/g);
while ((match = regex1.exec(message)) !== null) {
emojis.push(<Emoji emoji={match[0]} size={16} />);
}
const parts = message.split(regex1);
parts.forEach((part, i) => {
parts[i] = <span className="match" key={i}>{part}{emojis[i]}</span>;
});
return (message[2] === "`" && SyntaxLookup[message.slice(0,2)]
? <SyntaxHighlighter language={SyntaxLookup[message.slice(0,2)]} style={atomDark}>{message.slice(3)}</SyntaxHighlighter>
: <Linkify><span style={{ wordWrap: 'break-word', whiteSpace: 'pre-line' }}>{parts}</span></Linkify>)
};
class ChatBox extends PureComponent {
state = {
imgUrl: null
};
componentDidMount() {
const { message } = this.props;
if (ipfsMatcher.test(message)) this.getImageFromIpfs();
}
getImageFromIpfs = async () => {
const { ipfs, message } = this.props;
const files = await getFile(ipfs, message);
const { content } = files[0];
const arrayBufferView = new Uint8Array(content);
const blob = new Blob([ arrayBufferView ], { type: "image/jpeg" });
const imgUrl = URL.createObjectURL(blob);
this.setState({ imgUrl });
};
render() {
const { username, message, pubkey } = this.props;
const { imgUrl } = this.state;
return (
<Fragment>
<ListItem>
<Avatar>
<ListItemAvatar>
<Avatar>
{pubkey && <Jazzicon diameter={40} seed={jsNumberForAddress(pubkey)}/>}
</Avatar>
</ListItemAvatar>
</Avatar>
<ListItemText primary={`${username}`} secondary={<MessageRender message={message}/>}/>
</ListItem>
{hasYoutubeLink(message) &&
<ListItem>
<YouTube
videoId={getYoutubeId(message)}
opts={{ height: '390', width: '640', playerVars: { autoplay: 0 } }}
/>
</ListItem>
}
{isSpotifyLink(message) &&
<ListItem>
<SpotifyPlayer
uri={message}
size={{ 'width': 300, 'height': 300 }}
view='list'
theme='black'
/>
</ListItem>
}
{!!imgUrl && <img src={imgUrl} alt='ipfs' style={{maxWidth: '90%'}} />}
{isImage(message) && displayImage(message)}
</Fragment>
);
};
}
export default ChatBox;

View File

@ -13,7 +13,7 @@ import AddCircle from '@material-ui/icons/AddCircle';
import 'emoji-mart/css/emoji-mart.css';
import ChatBox from './ChatBox';
import Message from './Message';
import ChatHeader from './ChatHeader';
import Userlist from './Userlist';
import { uploadFileAndSend } from '../utils/ipfs';
@ -144,7 +144,7 @@ class ChatRoom extends Component {
<AutoScrollList style={{ height: messagesHeight, overflow: 'scroll' }}>
{messages[currentChannel] && messages[currentChannel].map((message) => (
<Fragment key={message.data.payload}>
<ChatBox {...message} ipfs={ipfs}/>
<Message {...message} ipfs={ipfs}/>
<li>
<Divider/>
</li>

25
src/components/Message.js Normal file
View File

@ -0,0 +1,25 @@
import React, { Fragment } from 'react';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import UserAvatar from './UserAvatar';
import MessageRenderer from './messages/MessageRenderer';
import YoutubeMessage from './messages/youtube';
import SpotifyMessage from './messages/spotify';
import ImageMessage from './messages/image';
import IpfsImageMessage from './messages/ipfsImage';
const Message = ({message, pubkey, username, ipfs}) => (
<Fragment>
<ListItem>
<UserAvatar pubkey={pubkey} />
<ListItemText primary={`${username}`} secondary={<MessageRenderer message={message}/>}/>
</ListItem>
<YoutubeMessage message={message} />
<SpotifyMessage message={message} />
<ImageMessage message={message} />
<IpfsImageMessage message={message} ipfs={ipfs} />
</Fragment>
);
export default Message;

View File

@ -0,0 +1,14 @@
import React from 'react';
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import Avatar from '@material-ui/core/Avatar';
import Jazzicon, { jsNumberForAddress } from 'react-jazzicon'
const UserAvatar = ({pubkey}) => (
<Avatar>
<ListItemAvatar>
{pubkey && <Jazzicon diameter={40} seed={jsNumberForAddress(pubkey)}/>}
</ListItemAvatar>
</Avatar>
);
export default UserAvatar;

View File

@ -0,0 +1,34 @@
import React from 'react';
import { Emoji } from 'emoji-mart';
import SyntaxLookup from '../../utils/syntaxLookup';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { atomDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import Linkify from 'react-linkify';
const isCode = (message) => {
return message[2] === "`" && SyntaxLookup[message.slice(0,2)]
}
// TODO use regex for code parsing / detection. Add new line detection for shift+enter
const MessageRenderer = ({ message }) => {
if (isCode(message)) {
return (<SyntaxHighlighter language={SyntaxLookup[message.slice(0,2)]} style={atomDark}>{message.slice(3)}</SyntaxHighlighter>)
}
const emojis = [];
let match;
const regex1 = RegExp(/:[\-a-zA-Z_+0-9]+:/g);
while ((match = regex1.exec(message)) !== null) {
emojis.push(<Emoji emoji={match[0]} size={16} />);
}
const parts = message.split(regex1);
parts.forEach((part, i) => {
parts[i] = <span className="match" key={i}>{part}{emojis[i]}</span>;
});
return (<Linkify><span style={{ wordWrap: 'break-word', whiteSpace: 'pre-line' }}>{parts}</span></Linkify>)
};
export default MessageRenderer;

View File

@ -0,0 +1,22 @@
import React, { Fragment } from 'react';
import ListItem from '@material-ui/core/ListItem';
function isImage(text) {
return text.indexOf("http") >= 0 && (text.indexOf('.jpg') || text.indexOf('.gif'));
}
// TODO: this needs to be reviewed. best to return as a css background-image instead
function displayImage(text) {
let reg = new RegExp(/\b(https?:\/\/\S+(?:png|jpe?g|gif)\S*)\b/);
let imageUrl = reg.exec(text);
if (!imageUrl) return (<span></span>);
return (<img src={imageUrl[0]} alt="" style={{maxWidth: '90%'}} />)
}
const ImageMessage = ({message}) => (
<Fragment>
{isImage(message) && displayImage(message)}
</Fragment>
)
export default ImageMessage;

View File

@ -0,0 +1,39 @@
import React, { Fragment, PureComponent } from 'react';
import { Matcher } from '@areknawo/rex'
import { getFile } from '../../utils/ipfs';
const ipfsMatcher = new Matcher().begin().find('/ipfs/');
class IpfsImageMessage extends PureComponent {
state = {
imgUrl: null
};
componentDidMount() {
const { message } = this.props;
if (ipfsMatcher.test(message)) this.getImageFromIpfs();
}
getImageFromIpfs = async () => {
const { ipfs, message } = this.props;
const files = await getFile(ipfs, message);
const { content } = files[0];
const arrayBufferView = new Uint8Array(content);
const blob = new Blob([ arrayBufferView ], { type: "image/jpeg" });
const imgUrl = URL.createObjectURL(blob);
this.setState({ imgUrl });
};
render() {
const { imgUrl } = this.state;
return (
<Fragment>
{!!imgUrl && <img src={imgUrl} alt='ipfs' style={{maxWidth: '90%'}} />}
</Fragment>
);
};
}
export default IpfsImageMessage;

View File

@ -0,0 +1,25 @@
import React, { Fragment } from 'react';
import ListItem from '@material-ui/core/ListItem';
import SpotifyPlayer from 'react-spotify-player';
// TODO: not exactly bulletproof right now, needs proper regex
function isSpotifyLink(text) {
return text.indexOf('spotify:') >= 0 ;
}
const SpotifyMessage = ({message}) => (
<Fragment>
{isSpotifyLink(message) &&
<ListItem>
<SpotifyPlayer
uri={message}
size={{ 'width': 300, 'height': 300 }}
view='list'
theme='black'
/>
</ListItem>
}
</Fragment>
)
export default SpotifyMessage;

View File

@ -0,0 +1,37 @@
import React, { Fragment } from 'react';
import YouTube from 'react-youtube';
import ListItem from '@material-ui/core/ListItem';
// TODO: not exactly bulletproof right now, needs proper regex
function hasYoutubeLink(text) {
return text.indexOf('http://www.youtube.com') >= 0 || text.indexOf('https://www.youtube.com') >= 0;
}
// https://gist.github.com/takien/4077195#
function getYoutubeId(url) {
let ID = '';
url = url.replace(/(>|<)/gi,'').split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/);
if (url[2] !== undefined) {
ID = url[2].split(/[^0-9a-z_\-]/i);
ID = ID[0];
}
else {
ID = url;
}
return ID;
}
const YoutubeMessage = ({message}) => (
<Fragment>
{hasYoutubeLink(message) &&
<ListItem>
<YouTube
videoId={getYoutubeId(message)}
opts={{ height: '390', width: '640', playerVars: { autoplay: 0 } }}
/>
</ListItem>
}
</Fragment>
)
export default YoutubeMessage;