commit
a18f70fdb4
|
@ -5,6 +5,7 @@
|
|||
<title>Status</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
|
||||
<link rel="stylesheet" href="https://unpkg.com/emoji-mart@2.8.1/css/emoji-mart.css" />
|
||||
<script>
|
||||
(function() {
|
||||
if (!process.env.HOT) {
|
||||
|
|
|
@ -7,6 +7,7 @@ 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/styles/prism';
|
||||
|
@ -40,12 +41,25 @@ function getYoutubeId(url) {
|
|||
return ID;
|
||||
}
|
||||
|
||||
//TODO use regex for code parsing / detection. Add new line detection for shift+enter
|
||||
const MessageRender = ({ message }) => (
|
||||
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' }}>{message}</span></Linkify>
|
||||
);
|
||||
// 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_]+:/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 = {
|
||||
|
@ -64,7 +78,6 @@ class ChatBox extends PureComponent {
|
|||
const arrayBufferView = new Uint8Array(content);
|
||||
const blob = new Blob([ arrayBufferView ], { type: "image/jpeg" });
|
||||
const imgUrl = URL.createObjectURL(blob);
|
||||
const image = `data:image/png;base64,${content.toString('base64')}`;
|
||||
this.setState({ imgUrl });
|
||||
};
|
||||
|
||||
|
@ -77,33 +90,33 @@ class ChatBox extends PureComponent {
|
|||
<Avatar>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
{pubkey && <Jazzicon diameter={40} seed={jsNumberForAddress(pubkey)} />}
|
||||
{pubkey && <Jazzicon diameter={40} seed={jsNumberForAddress(pubkey)}/>}
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
</Avatar>
|
||||
<ListItemText primary={`${username}`} secondary={<MessageRender message={message} />} />
|
||||
<ListItemText primary={`${username}`} secondary={<MessageRender message={message}/>}/>
|
||||
</ListItem>
|
||||
{hasYoutubeLink(message) &&
|
||||
<ListItem>
|
||||
<YouTube
|
||||
videoId={getYoutubeId(message)}
|
||||
opts={{height: '390', width: '640', playerVars: { autoplay: 0 }}}
|
||||
/>
|
||||
</ListItem>
|
||||
<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>
|
||||
<ListItem>
|
||||
<SpotifyPlayer
|
||||
uri={message}
|
||||
size={{ 'width': 300, 'height': 300 }}
|
||||
view='list'
|
||||
theme='black'
|
||||
/>
|
||||
</ListItem>
|
||||
}
|
||||
{!!imgUrl && <img src={imgUrl} alt='ipfs' style={{ width: '100%' }}/>}
|
||||
</Fragment>
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// @flow
|
||||
import React, { Fragment, PureComponent, createRef } from 'react';
|
||||
import React, { Fragment, Component, PureComponent, createRef } from 'react';
|
||||
import { Formik } from 'formik';
|
||||
import autoscroll from 'autoscroll-react';
|
||||
import List from '@material-ui/core/List';
|
||||
|
@ -10,13 +10,19 @@ import ListItemText from '@material-ui/core/ListItemText';
|
|||
import Divider from '@material-ui/core/Divider';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Dropzone from 'react-dropzone';
|
||||
import Jazzicon, { jsNumberForAddress } from 'react-jazzicon';
|
||||
import { Picker } from 'emoji-mart';
|
||||
|
||||
import ChatBox from './ChatBox';
|
||||
import ChatBox, { Emoji } from './ChatBox';
|
||||
import ChatHeader from './ChatHeader';
|
||||
import { uploadFileAndSend } from '../utils/ipfs';
|
||||
|
||||
import 'emoji-mart/css/emoji-mart.css';
|
||||
|
||||
|
||||
|
||||
class WhoIsTyping extends PureComponent {
|
||||
|
||||
whoIsTyping() {
|
||||
|
@ -68,96 +74,138 @@ const AutoScrollList = autoscroll(List);
|
|||
const formStyle = { display: 'flex', justifyContent: 'center', alignItems: 'center', flexBasis: '10%' };
|
||||
const listStyle = { overflowY: 'auto', flexBasis: '76%', position: 'absolute', top: '72px', left: 0, right: 0, bottom: '67px' };
|
||||
const ChatRoomForm = createRef();
|
||||
const ChatRoom = ({ messages, sendMessage, currentChannel, usersTyping, typingEvent, channelUsers, allUsers, ipfs }) => (
|
||||
<Grid container style={{ height: '100vh'}}>
|
||||
<Grid xs={8} item >
|
||||
<Dropzone
|
||||
onDrop={(a, r) => { onDrop(a,r,ipfs,sendMessage) } }
|
||||
disableClick
|
||||
style={{ position: 'relative', height: '100%' }}
|
||||
activeStyle={{ backgroundColor: 'grey', outline: '5px dashed lightgrey', alignSelf: 'center', outlineOffset: '-10px' }}>
|
||||
<Grid
|
||||
container
|
||||
direction="column"
|
||||
justify="flex-start"
|
||||
alignItems="stretch"
|
||||
style={{ height: '100%' }}
|
||||
>
|
||||
<ChatHeader currentChannel={currentChannel}/>
|
||||
<Divider />
|
||||
<AutoScrollList style={listStyle}>
|
||||
{messages[currentChannel] && messages[currentChannel].map((message) => (
|
||||
<Fragment key={message.data.payload}>
|
||||
<ChatBox {...message} ipfs={ipfs} />
|
||||
<li>
|
||||
<Divider />
|
||||
</li>
|
||||
</Fragment>
|
||||
))}
|
||||
</AutoScrollList>
|
||||
<Formik
|
||||
initialValues={{ chatInput: '' }}
|
||||
onSubmit={(values, { setSubmitting, resetForm }) => {
|
||||
const { chatInput } = values;
|
||||
sendMessage(chatInput);
|
||||
resetForm();
|
||||
setSubmitting(false);
|
||||
class ChatRoom extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showEmojis: false
|
||||
};
|
||||
}
|
||||
|
||||
toggleEmojis(e) {
|
||||
e.preventDefault();
|
||||
this.setState(({showEmojis: !this.state.showEmojis}));
|
||||
}
|
||||
|
||||
addEmoji(emoji, chatInput, setValue) {
|
||||
console.log(emoji);
|
||||
setValue('chatInput', `${chatInput}:${emoji.id}:`);
|
||||
this.setState(({showEmojis: false}));
|
||||
// <Emoji emoji=":santa::skin-tone-3:" size={16} />
|
||||
}
|
||||
|
||||
render() {
|
||||
const { messages, sendMessage, currentChannel, usersTyping, typingEvent, channelUsers, allUsers, ipfs } = this.props;
|
||||
const {showEmojis} = this.state;
|
||||
return (
|
||||
<Grid container style={{ height: '100vh' }}>
|
||||
<Grid xs={8} item>
|
||||
<Dropzone
|
||||
onDrop={(a, r) => {
|
||||
onDrop(a, r, ipfs, sendMessage);
|
||||
}}
|
||||
>
|
||||
{({
|
||||
values,
|
||||
errors,
|
||||
touched,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
handleSubmit,
|
||||
setFieldValue
|
||||
}) => (
|
||||
<div className="chat-input" style={{position: 'absolute', bottom: 0, left: 0, right: 0, paddingBottom: 10}}>
|
||||
<form onSubmit={handleSubmit} style={formStyle} ref={ChatRoomForm}>
|
||||
<TextField
|
||||
id="chatInput"
|
||||
multiline
|
||||
style={{ width: 'auto', flexGrow: '0.95', margin: '2px 0 0 0' }}
|
||||
label="Type a message..."
|
||||
type="text"
|
||||
name="chatInput"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onChange={handleChange}
|
||||
onKeyDown={(e) => keyDownHandler(e, typingEvent, setFieldValue, values.chatInput)}
|
||||
onBlur={handleBlur}
|
||||
value={values.chatInput || ''}
|
||||
/>
|
||||
{errors.chatInput && touched.chatInput && errors.chatInput}
|
||||
</form>
|
||||
<WhoIsTyping
|
||||
currentChannel={currentChannel}
|
||||
usersTyping={usersTyping}
|
||||
users={allUsers} />
|
||||
</div>
|
||||
)}
|
||||
</Formik>
|
||||
disableClick
|
||||
style={{ position: 'relative', height: '100%' }}
|
||||
activeStyle={{
|
||||
backgroundColor: 'grey',
|
||||
outline: '5px dashed lightgrey',
|
||||
alignSelf: 'center',
|
||||
outlineOffset: '-10px'
|
||||
}}>
|
||||
<Grid
|
||||
container
|
||||
direction="column"
|
||||
justify="flex-start"
|
||||
alignItems="stretch"
|
||||
style={{ height: '100%' }}
|
||||
>
|
||||
<ChatHeader currentChannel={currentChannel}/>
|
||||
<Divider/>
|
||||
<AutoScrollList style={listStyle}>
|
||||
{messages[currentChannel] && messages[currentChannel].map((message) => (
|
||||
<Fragment key={message.data.payload}>
|
||||
<ChatBox {...message} ipfs={ipfs}/>
|
||||
<li>
|
||||
<Divider/>
|
||||
</li>
|
||||
</Fragment>
|
||||
))}
|
||||
</AutoScrollList>
|
||||
<Formik
|
||||
initialValues={{ chatInput: '' }}
|
||||
onSubmit={(values, { setSubmitting, resetForm }) => {
|
||||
const { chatInput } = values;
|
||||
sendMessage(chatInput);
|
||||
resetForm();
|
||||
setSubmitting(false);
|
||||
}}
|
||||
>
|
||||
{({
|
||||
values,
|
||||
errors,
|
||||
touched,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
handleSubmit,
|
||||
setFieldValue
|
||||
}) => (
|
||||
<div className="chat-input"
|
||||
style={{ position: 'absolute', bottom: 0, left: 0, right: 0, paddingBottom: 10 }}>
|
||||
<form onSubmit={handleSubmit} style={formStyle} ref={ChatRoomForm}>
|
||||
<TextField
|
||||
id="chatInput"
|
||||
multiline
|
||||
style={{ width: 'auto', flexGrow: '0.95', margin: '2px 0 0 0' }}
|
||||
label="Type a message..."
|
||||
type="text"
|
||||
name="chatInput"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onChange={handleChange}
|
||||
onKeyDown={(e) => keyDownHandler(e, typingEvent, setFieldValue, values.chatInput)}
|
||||
onBlur={handleBlur}
|
||||
value={values.chatInput || ''}
|
||||
/>
|
||||
{showEmojis && <Picker onSelect={(emoji) => this.addEmoji(emoji, values.chatInput, setFieldValue)}
|
||||
style={{ position: 'absolute', bottom: '80px', right: '20px' }}/>}
|
||||
<Button onClick={(e) => this.toggleEmojis(e)}>Smile</Button>
|
||||
{errors.chatInput && touched.chatInput && errors.chatInput}
|
||||
</form>
|
||||
<WhoIsTyping
|
||||
currentChannel={currentChannel}
|
||||
usersTyping={usersTyping}
|
||||
users={allUsers}/>
|
||||
</div>
|
||||
)}
|
||||
</Formik>
|
||||
</Grid>
|
||||
</Dropzone>
|
||||
</Grid>
|
||||
</Dropzone>
|
||||
</Grid>
|
||||
<Grid xs={4} item style={{overflow: 'auto'}}>
|
||||
<List>
|
||||
{Object.keys(channelUsers).map(user => (
|
||||
<ListItem button key={user}>
|
||||
<span className="dot" style={{"height": "10px", "width": "11px", "background-color": (allUsers[user].online ? "lightgreen" : "lightgrey"), "border-radius": "50%", "margin-right": "10px"}}/>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<Jazzicon diameter={40} seed={jsNumberForAddress(user)} />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={allUsers[user].username} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
<Grid xs={4} item style={{ overflow: 'auto' }}>
|
||||
<List>
|
||||
{Object.keys(channelUsers).map(user => (
|
||||
<ListItem button key={user}>
|
||||
<span className="dot" style={{
|
||||
'height': '10px',
|
||||
'width': '11px',
|
||||
'background-color': (allUsers[user].online ? 'lightgreen' : 'lightgrey'),
|
||||
'border-radius': '50%',
|
||||
'margin-right': '10px'
|
||||
}}/>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
<Jazzicon diameter={40} seed={jsNumberForAddress(user)}/>
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={allUsers[user].username}/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ChatRoom;
|
||||
|
|
|
@ -7094,6 +7094,11 @@
|
|||
"minimalistic-crypto-utils": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"emoji-mart": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-2.8.1.tgz",
|
||||
"integrity": "sha512-9ScBU9ObG4qVQOMvQWQwUkYVNK/eOu/TRk8o+LABtnL2BE/o9CU4OvtjcoBPNiC/7QtRhW9vOwmvUx/OjT+mwA=="
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz",
|
||||
|
|
|
@ -264,6 +264,7 @@
|
|||
"electron-debug": "^2.0.0",
|
||||
"electron-log": "^2.2.17",
|
||||
"electron-updater": "^3.1.6",
|
||||
"emoji-mart": "^2.8.1",
|
||||
"eth-keyring-controller": "^3.3.1",
|
||||
"formik": "^1.3.1",
|
||||
"history": "^4.7.2",
|
||||
|
|
|
@ -5144,6 +5144,10 @@ elliptic@^6.0.0, elliptic@^6.2.3, elliptic@^6.4.0:
|
|||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-crypto-utils "^1.0.0"
|
||||
|
||||
emoji-mart@^2.8.1:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-2.8.1.tgz#4ffcb807237989a953085c80a6553d21f1effc62"
|
||||
|
||||
emoji-regex@^6.5.1:
|
||||
version "6.5.1"
|
||||
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2"
|
||||
|
|
Loading…
Reference in New Issue