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