diff --git a/client/src/components/Home/Activity.js b/client/src/components/Home/Activity.js
deleted file mode 100644
index a531c51..0000000
--- a/client/src/components/Home/Activity.js
+++ /dev/null
@@ -1,156 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import Message from 'components/Message'
-import Username from 'components/Username'
-import Notice from 'components/Notice'
-import Zoom from 'utils/ImageZoom'
-import { getObjectUrl } from 'utils/file'
-
-import T from 'components/T'
-
-class Activity extends Component {
- constructor(props) {
- super(props)
-
- this.state = {
- zoomableImages: [],
- }
- }
-
- getFileDisplay(activity) {
- const type = activity.fileType
- if (type.match('image.*')) {
- return (
-
this._zoomableImage = c}
- className="image-transfer zoomable"
- src={`data:${activity.fileType};base64,${activity.encodedFile}`}
- alt={`${activity.fileName} from ${activity.username}`}
- onLoad={this.handleImageDisplay.bind(this)}
- />
- )
- }
- return null
- }
-
- handleImageDisplay() {
- Zoom(this._zoomableImage)
- this.props.scrollToBottom()
- }
-
- getActivityComponent(activity) {
- switch (activity.type) {
- case 'TEXT_MESSAGE':
- return (
-
- )
- case 'USER_ENTER':
- return (
-
-
-
- }} path='userJoined'/>
-
-
- )
- case 'USER_EXIT':
- return (
-
-
-
- }} path='userLeft'/>
-
-
- )
- case 'TOGGLE_LOCK_ROOM':
- if (activity.locked) {
- return (
-
-
- }} path='lockedRoom'/>
-
- )
- } else {
- return (
-
-
- }} path='unlockedRoom'/>
-
- )
- }
- case 'NOTICE':
- return (
-
- {activity.message}
-
- )
- case 'CHANGE_USERNAME':
- return (
-
- ,
- newUsername:
- }} path='nameChange'/>
-
-
- )
- case 'USER_ACTION':
- return (
-
- * {activity.action}
-
- )
- case 'RECEIVE_FILE':
- const downloadUrl = getObjectUrl(activity.encodedFile, activity.fileType)
- return (
-
-
,
- }} path='userSentFile'/>
-
-
-
-
- {this.getFileDisplay(activity)}
-
- )
- case 'SEND_FILE':
- const url = getObjectUrl(activity.encodedFile, activity.fileType)
- return (
-
-
- {activity.fileName},
- }} path='sentFile'/>
-
- {this.getFileDisplay(activity)}
-
- )
- default:
- return false
- }
- }
-
- render() {
- return (
- this.getActivityComponent(this.props.activity)
- )
- }
-}
-
-Activity.propTypes = {
- activity: PropTypes.object.isRequired,
- scrollToBottom: PropTypes.func.isRequired,
-}
-
-export default Activity;
diff --git a/client/src/components/Home/ActivityList.js b/client/src/components/Home/ActivityList.js
deleted file mode 100644
index 3c05a71..0000000
--- a/client/src/components/Home/ActivityList.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import ChatInput from 'components/Chat'
-import { defer } from 'lodash'
-import Activity from './Activity'
-
-import T from 'components/T'
-
-import styles from './styles.module.scss'
-
-class ActivityList extends Component {
- constructor(props) {
- super(props)
-
- this.state = {
- zoomableImages: [],
- focusChat: false,
- }
- }
-
- componentDidMount() {
- this.bindEvents()
- }
-
- componentDidUpdate(prevProps) {
- if (prevProps.activities.length < this.props.activities.length) {
- this.scrollToBottomIfShould()
- }
- }
-
- onScroll() {
- const messageStreamHeight = this.messageStream.clientHeight
- const activitiesListHeight = this.activitiesList.clientHeight
-
- const bodyRect = document.body.getBoundingClientRect()
- const elemRect = this.activitiesList.getBoundingClientRect()
- const offset = elemRect.top - bodyRect.top
- const activitiesListYPos = offset
-
- const scrolledToBottom = (activitiesListHeight + (activitiesListYPos - 60)) <= messageStreamHeight
- if (scrolledToBottom) {
- if (!this.props.scrolledToBottom) {
- this.props.setScrolledToBottom(true)
- }
- } else if (this.props.scrolledToBottom) {
- this.props.setScrolledToBottom(false)
- }
- }
-
- scrollToBottomIfShould() {
- if (this.props.scrolledToBottom) {
- setTimeout(() => {
- this.messageStream.scrollTop = this.messageStream.scrollHeight
- }, 0)
- }
- }
-
- scrollToBottom() {
- this.messageStream.scrollTop = this.messageStream.scrollHeight
- this.props.setScrolledToBottom(true)
- }
-
- bindEvents() {
- this.messageStream.addEventListener('scroll', this.onScroll.bind(this))
- }
-
- handleChatClick() {
- this.setState({ focusChat: true })
- defer(() => this.setState({ focusChat: false }))
- }
-
- render() {
- return (
-
-
this.messageStream = el}>
-
this.activitiesList = el}>
-
- {this.props.activities.map((activity, index) => (
- -
-
-
- ))}
-
-
-
-
-
-
- )
- }
-}
-
-ActivityList.propTypes = {
- activities: PropTypes.array.isRequired,
- openModal: PropTypes.func.isRequired,
- setScrolledToBottom: PropTypes.func.isRequired,
- scrolledToBottom: PropTypes.bool.isRequired,
-}
-
-export default ActivityList;
diff --git a/client/src/components/Home/Home.js b/client/src/components/Home/Home.js
deleted file mode 100644
index 32e717c..0000000
--- a/client/src/components/Home/Home.js
+++ /dev/null
@@ -1,277 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import Crypto from 'utils/crypto'
-import { connect as connectSocket } from 'utils/socket'
-import Nav from 'components/Nav'
-import shortId from 'shortid'
-import Connecting from 'components/Connecting'
-import Modal from 'react-modal'
-import About from 'components/About'
-import Settings from 'components/Settings'
-import Welcome from 'components/Welcome'
-import RoomLocked from 'components/RoomLocked'
-import { X, AlertCircle } from 'react-feather'
-import { defer } from 'lodash'
-import Tinycon from 'tinycon'
-import beepFile from 'audio/beep.mp3'
-import classNames from 'classnames'
-import ActivityList from './ActivityList'
-
-import styles from './styles.module.scss'
-
-const crypto = new Crypto()
-
-Modal.setAppElement('#root');
-
-class Home extends Component {
- constructor(props) {
- super(props)
-
- this.state = {
- zoomableImages: []
- }
- }
-
- async componentWillMount() {
- const roomId = encodeURI(this.props.match.params.roomId)
-
- const user = await this.createUser()
-
- const socket = connectSocket(roomId)
-
- this.socket = socket;
-
- socket.on('disconnect', () => {
- this.props.toggleSocketConnected(false)
- })
-
- socket.on('connect', () => {
- this.initApp(user)
- this.props.toggleSocketConnected(true)
- })
-
- socket.on('USER_ENTER', (payload) => {
- this.props.receiveUnencryptedMessage('USER_ENTER', payload)
- this.props.sendEncryptedMessage({
- type: 'ADD_USER',
- payload: {
- username: this.props.username,
- publicKey: this.props.publicKey,
- isOwner: this.props.iAmOwner,
- id: this.props.userId,
- },
- })
- if (payload.users.length === 1) {
- this.props.openModal('Welcome');
- }
- })
-
- socket.on('USER_EXIT', (payload) => {
- this.props.receiveUnencryptedMessage('USER_EXIT', payload)
- })
-
- socket.on('ENCRYPTED_MESSAGE', (payload) => {
- this.props.receiveEncryptedMessage(payload)
- })
-
- socket.on('TOGGLE_LOCK_ROOM', (payload) => {
- this.props.receiveUnencryptedMessage('TOGGLE_LOCK_ROOM', payload)
- })
-
- socket.on('ROOM_LOCKED', (payload) => {
- this.props.openModal('Room Locked')
- });
-
- window.addEventListener('beforeunload', (evt) => {
- socket.emit('USER_DISCONNECT')
- });
- }
-
- componentDidMount() {
- this.bindEvents()
-
- this.beep = window.Audio && new window.Audio(beepFile)
- }
-
- componentWillReceiveProps(nextProps) {
- Tinycon.setBubble(nextProps.faviconCount)
-
- if (nextProps.faviconCount !== 0 && nextProps.faviconCount !== this.props.faviconCount && this.props.soundIsEnabled) {
- this.beep.play()
- }
- }
-
- getModal() {
- switch (this.props.modalComponent) {
- case 'Connecting':
- return {
- component: ,
- title: 'Connecting...',
- preventClose: true,
- }
- case 'About':
- return {
- component: ,
- title: this.props.translations.aboutHeader,
- }
- case 'Settings':
- return {
- component: ,
- title: this.props.translations.settingsHeader,
- }
- case 'Welcome':
- return {
- component: ,
- title: this.props.translations.welcomeHeader,
- }
- case 'Room Locked':
- return {
- component: ,
- title: this.props.translations.lockedRoomHeader,
- preventClose: true,
- }
- default:
- return {
- component: null,
- title: null,
- }
- }
- }
-
- initApp(user) {
- this.socket.emit('USER_ENTER', {
- publicKey: user.publicKey,
- })
- }
-
- bindEvents() {
-
- window.onfocus = () => {
- this.props.toggleWindowFocus(true)
- }
-
- window.onblur = () => {
- this.props.toggleWindowFocus(false)
- }
- }
-
- createUser() {
- return new Promise(async (resolve) => {
- const username = shortId.generate()
-
- const encryptDecryptKeys = await crypto.createEncryptDecryptKeys()
- const exportedEncryptDecryptPrivateKey = await crypto.exportKey(encryptDecryptKeys.privateKey)
- const exportedEncryptDecryptPublicKey = await crypto.exportKey(encryptDecryptKeys.publicKey)
-
- this.props.createUser({
- username,
- publicKey: exportedEncryptDecryptPublicKey,
- privateKey: exportedEncryptDecryptPrivateKey,
- })
-
- resolve({
- publicKey: exportedEncryptDecryptPublicKey,
- })
- })
- }
-
- handleChatClick() {
- this.setState({ focusChat: true })
- defer(() => this.setState({ focusChat: false }))
- }
-
- render() {
- const modalOpts = this.getModal()
- return (
-
-
- {!this.props.socketConnected &&
-
- }
-
-
-
-
- {!modalOpts.preventClose &&
-
- }
-
- {modalOpts.title}
-
-
-
- {modalOpts.component}
-
-
-
- )
- }
-}
-
-Home.defaultProps = {
- modalComponent: null,
-}
-
-Home.propTypes = {
- receiveEncryptedMessage: PropTypes.func.isRequired,
- createUser: PropTypes.func.isRequired,
- activities: PropTypes.array.isRequired,
- username: PropTypes.string.isRequired,
- publicKey: PropTypes.object.isRequired,
- members: PropTypes.array.isRequired,
- match: PropTypes.object.isRequired,
- roomId: PropTypes.string.isRequired,
- roomLocked: PropTypes.bool.isRequired,
- modalComponent: PropTypes.string,
- openModal: PropTypes.func.isRequired,
- closeModal: PropTypes.func.isRequired,
- setScrolledToBottom: PropTypes.func.isRequired,
- scrolledToBottom: PropTypes.bool.isRequired,
- iAmOwner: PropTypes.bool.isRequired,
- userId: PropTypes.string.isRequired,
- toggleWindowFocus: PropTypes.func.isRequired,
- faviconCount: PropTypes.number.isRequired,
- soundIsEnabled: PropTypes.bool.isRequired,
- toggleSoundEnabled: PropTypes.func.isRequired,
- toggleSocketConnected: PropTypes.func.isRequired,
- socketConnected: PropTypes.bool.isRequired,
- sendUnencryptedMessage: PropTypes.func.isRequired,
- sendEncryptedMessage: PropTypes.func.isRequired
-}
-
-export default Home;
diff --git a/client/src/components/Home/index.js b/client/src/components/Home/index.js
index b90450c..b427a14 100644
--- a/client/src/components/Home/index.js
+++ b/client/src/components/Home/index.js
@@ -1,4 +1,26 @@
-import Home from './Home'
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import Crypto from 'utils/crypto'
+import { connect as connectSocket } from 'utils/socket'
+import Nav from 'components/Nav'
+import shortId from 'shortid'
+import ChatInput from 'components/Chat'
+import Connecting from 'components/Connecting'
+import Message from 'components/Message'
+import Username from 'components/Username'
+import Notice from 'components/Notice'
+import Modal from 'react-modal'
+import About from 'components/About'
+import Settings from 'components/Settings'
+import Welcome from 'components/Welcome'
+import RoomLocked from 'components/RoomLocked'
+import { X, AlertCircle } from 'react-feather'
+import { defer } from 'lodash'
+import Tinycon from 'tinycon'
+import beepFile from 'audio/beep.mp3'
+import Zoom from 'utils/ImageZoom'
+import classNames from 'classnames'
+import { getObjectUrl } from 'utils/file'
import { connect } from 'react-redux'
import {
receiveEncryptedMessage,
@@ -14,6 +36,439 @@ import {
sendEncryptedMessage,
setLanguage
} from 'actions'
+import T from 'components/T'
+
+import styles from './styles.module.scss'
+
+const crypto = new Crypto()
+
+Modal.setAppElement('#root');
+
+class Home extends Component {
+ constructor(props) {
+ super(props)
+
+ this.state = {
+ zoomableImages: [],
+ focusChat: false,
+ }
+
+ this.hasConnected = false
+ }
+
+ async componentWillMount() {
+ const roomId = encodeURI(this.props.match.params.roomId)
+
+ const user = await this.createUser()
+
+ const socket = connectSocket(roomId)
+
+ this.socket = socket;
+
+ socket.on('disconnect', () => {
+ this.props.toggleSocketConnected(false)
+ })
+
+ socket.on('connect', () => {
+ this.initApp(user)
+ this.props.toggleSocketConnected(true)
+ })
+
+ socket.on('USER_ENTER', (payload) => {
+ this.props.receiveUnencryptedMessage('USER_ENTER', payload)
+ this.props.sendEncryptedMessage({
+ type: 'ADD_USER',
+ payload: {
+ username: this.props.username,
+ publicKey: this.props.publicKey,
+ isOwner: this.props.iAmOwner,
+ id: this.props.userId,
+ },
+ })
+ if (payload.users.length === 1) {
+ this.props.openModal('Welcome');
+ }
+ })
+
+ socket.on('USER_EXIT', (payload) => {
+ this.props.receiveUnencryptedMessage('USER_EXIT', payload)
+ })
+
+ socket.on('ENCRYPTED_MESSAGE', (payload) => {
+ this.props.receiveEncryptedMessage(payload)
+ })
+
+ socket.on('TOGGLE_LOCK_ROOM', (payload) => {
+ this.props.receiveUnencryptedMessage('TOGGLE_LOCK_ROOM', payload)
+ })
+
+ socket.on('ROOM_LOCKED', (payload) => {
+ this.props.openModal('Room Locked')
+ });
+
+ window.addEventListener('beforeunload', (evt) => {
+ socket.emit('USER_DISCONNECT')
+ });
+ }
+
+ componentDidMount() {
+ this.bindEvents()
+
+ this.beep = window.Audio && new window.Audio(beepFile)
+ }
+
+ componentWillReceiveProps(nextProps) {
+ Tinycon.setBubble(nextProps.faviconCount)
+
+ if (nextProps.faviconCount !== 0 && nextProps.faviconCount !== this.props.faviconCount && this.props.soundIsEnabled) {
+ this.beep.play()
+ }
+ }
+
+ componentDidUpdate(prevProps) {
+ if (prevProps.activities.length < this.props.activities.length) {
+ this.scrollToBottomIfShould()
+ }
+ }
+
+ onScroll() {
+ const messageStreamHeight = this.messageStream.clientHeight
+ const activitiesListHeight = this.activitiesList.clientHeight
+
+ const bodyRect = document.body.getBoundingClientRect()
+ const elemRect = this.activitiesList.getBoundingClientRect()
+ const offset = elemRect.top - bodyRect.top
+ const activitiesListYPos = offset
+
+ const scrolledToBottom = (activitiesListHeight + (activitiesListYPos - 60)) <= messageStreamHeight
+ if (scrolledToBottom) {
+ if (!this.props.scrolledToBottom) {
+ this.props.setScrolledToBottom(true)
+ }
+ } else if (this.props.scrolledToBottom) {
+ this.props.setScrolledToBottom(false)
+ }
+ }
+
+ getFileDisplay(activity) {
+ const type = activity.fileType
+ if (type.match('image.*')) {
+ return (
+
this._zoomableImage = c}
+ className="image-transfer zoomable"
+ src={`data:${activity.fileType};base64,${activity.encodedFile}`}
+ alt={`${activity.fileName} from ${activity.username}`}
+ onLoad={this.handleImageDisplay.bind(this)}
+ />
+ )
+ }
+ return null
+ }
+
+ getActivityComponent(activity) {
+ switch (activity.type) {
+ case 'TEXT_MESSAGE':
+ return (
+
+ )
+ case 'USER_ENTER':
+ return (
+
+
+
+ }} path='userJoined'/>
+
+
+ )
+ case 'USER_EXIT':
+ return (
+
+
+
+ }} path='userLeft'/>
+
+
+ )
+ case 'TOGGLE_LOCK_ROOM':
+ if (activity.locked) {
+ return (
+
+
+ }} path='lockedRoom'/>
+
+ )
+ } else {
+ return (
+
+
+ }} path='unlockedRoom'/>
+
+ )
+ }
+ case 'NOTICE':
+ return (
+
+ {activity.message}
+
+ )
+ case 'CHANGE_USERNAME':
+ return (
+
+ ,
+ newUsername:
+ }} path='nameChange'/>
+
+
+ )
+ case 'USER_ACTION':
+ return (
+
+ * {activity.action}
+
+ )
+ case 'RECEIVE_FILE':
+ const downloadUrl = getObjectUrl(activity.encodedFile, activity.fileType)
+ return (
+
+
,
+ }} path='userSentFile'/>
+
+
+
+
+ {this.getFileDisplay(activity)}
+
+ )
+ case 'SEND_FILE':
+ const url = getObjectUrl(activity.encodedFile, activity.fileType)
+ return (
+
+
+ {activity.fileName},
+ }} path='sentFile'/>
+
+ {this.getFileDisplay(activity)}
+
+ )
+ default:
+ return false
+ }
+ }
+
+ getModal() {
+ switch (this.props.modalComponent) {
+ case 'Connecting':
+ return {
+ component: ,
+ title: 'Connecting...',
+ preventClose: true,
+ }
+ case 'About':
+ return {
+ component: ,
+ title: this.props.translations.aboutHeader,
+ }
+ case 'Settings':
+ return {
+ component: ,
+ title: this.props.translations.settingsHeader,
+ }
+ case 'Welcome':
+ return {
+ component: ,
+ title: this.props.translations.welcomeHeader,
+ }
+ case 'Room Locked':
+ return {
+ component: ,
+ title: this.props.translations.lockedRoomHeader,
+ preventClose: true,
+ }
+ default:
+ return {
+ component: null,
+ title: null,
+ }
+ }
+ }
+
+ initApp(user) {
+ this.socket.emit('USER_ENTER', {
+ publicKey: user.publicKey,
+ })
+ }
+
+ handleImageDisplay() {
+ Zoom(this._zoomableImage)
+ this.scrollToBottomIfShould()
+ }
+
+ scrollToBottomIfShould() {
+ if (this.props.scrolledToBottom) {
+ setTimeout(() => {
+ this.messageStream.scrollTop = this.messageStream.scrollHeight
+ }, 0)
+ }
+ }
+
+ scrollToBottom() {
+ this.messageStream.scrollTop = this.messageStream.scrollHeight
+ this.props.setScrolledToBottom(true)
+ }
+
+ bindEvents() {
+ this.messageStream.addEventListener('scroll', this.onScroll.bind(this))
+
+ window.onfocus = () => {
+ this.props.toggleWindowFocus(true)
+ }
+
+ window.onblur = () => {
+ this.props.toggleWindowFocus(false)
+ }
+ }
+
+ createUser() {
+ return new Promise(async (resolve) => {
+ const username = shortId.generate()
+
+ const encryptDecryptKeys = await crypto.createEncryptDecryptKeys()
+ const exportedEncryptDecryptPrivateKey = await crypto.exportKey(encryptDecryptKeys.privateKey)
+ const exportedEncryptDecryptPublicKey = await crypto.exportKey(encryptDecryptKeys.publicKey)
+
+ this.props.createUser({
+ username,
+ publicKey: exportedEncryptDecryptPublicKey,
+ privateKey: exportedEncryptDecryptPrivateKey,
+ })
+
+ resolve({
+ publicKey: exportedEncryptDecryptPublicKey,
+ })
+ })
+ }
+
+ handleChatClick() {
+ this.setState({ focusChat: true })
+ defer(() => this.setState({ focusChat: false }))
+ }
+
+ render() {
+ const modalOpts = this.getModal()
+ return (
+
+
+ {!this.props.socketConnected &&
+
+ }
+
+
+
this.messageStream = el}>
+
this.activitiesList = el}>
+
+ {this.props.activities.map((activity, index) => (
+ -
+ {this.getActivityComponent(activity)}
+
+ ))}
+
+
+
+
+
+
+
+
+ {!modalOpts.preventClose &&
+
+ }
+
+ {modalOpts.title}
+
+
+
+ {modalOpts.component}
+
+
+
+ )
+ }
+}
+
+Home.defaultProps = {
+ modalComponent: null,
+}
+
+Home.propTypes = {
+ receiveEncryptedMessage: PropTypes.func.isRequired,
+ createUser: PropTypes.func.isRequired,
+ activities: PropTypes.array.isRequired,
+ username: PropTypes.string.isRequired,
+ publicKey: PropTypes.object.isRequired,
+ members: PropTypes.array.isRequired,
+ match: PropTypes.object.isRequired,
+ roomId: PropTypes.string.isRequired,
+ roomLocked: PropTypes.bool.isRequired,
+ modalComponent: PropTypes.string,
+ openModal: PropTypes.func.isRequired,
+ closeModal: PropTypes.func.isRequired,
+ setScrolledToBottom: PropTypes.func.isRequired,
+ scrolledToBottom: PropTypes.bool.isRequired,
+ iAmOwner: PropTypes.bool.isRequired,
+ userId: PropTypes.string.isRequired,
+ toggleWindowFocus: PropTypes.func.isRequired,
+ faviconCount: PropTypes.number.isRequired,
+ soundIsEnabled: PropTypes.bool.isRequired,
+ toggleSoundEnabled: PropTypes.func.isRequired,
+ toggleSocketConnected: PropTypes.func.isRequired,
+ socketConnected: PropTypes.bool.isRequired,
+ sendUnencryptedMessage: PropTypes.func.isRequired,
+ sendEncryptedMessage: PropTypes.func.isRequired
+}
const mapStateToProps = (state) => {
const me = state.room.members.find(m => m.id === state.user.id)