diff --git a/frontend/src/Metamaps/Realtime/index.js b/frontend/src/Metamaps/Realtime/index.js index dd1e4d51..0d1754bf 100644 --- a/frontend/src/Metamaps/Realtime/index.js +++ b/frontend/src/Metamaps/Realtime/index.js @@ -174,8 +174,6 @@ let Realtime = { self.room = new Views.Room({ webrtc: self.webrtc, socket: self.socket, - username: Active.Mapper ? Active.Mapper.get('name') : '', - image: Active.Mapper ? Active.Mapper.get('image') : '', room: 'global', $video: self.localVideo.$video, myVideoView: self.localVideo.view, @@ -187,26 +185,26 @@ let Realtime = { addJuntoListeners: function() { var self = Realtime - $(document).on(Views.ChatView.events.openTray, function() { + $(document).on(ChatView.events.openTray, function() { $('.main').addClass('compressed') self.chatOpen = true self.positionPeerIcons() }) - $(document).on(Views.ChatView.events.closeTray, function() { + $(document).on(ChatView.events.closeTray, function() { $('.main').removeClass('compressed') self.chatOpen = false self.positionPeerIcons() }) - $(document).on(Views.ChatView.events.videosOn, function() { + $(document).on(ChatView.events.videosOn, function() { $('#wrapper').removeClass('hideVideos') }) - $(document).on(Views.ChatView.events.videosOff, function() { + $(document).on(ChatView.events.videosOff, function() { $('#wrapper').addClass('hideVideos') }) - $(document).on(Views.ChatView.events.cursorsOn, function() { + $(document).on(ChatView.events.cursorsOn, function() { $('#wrapper').removeClass('hideCursors') }) - $(document).on(Views.ChatView.events.cursorsOff, function() { + $(document).on(ChatView.events.cursorsOff, function() { $('#wrapper').addClass('hideCursors') }) }, @@ -218,7 +216,7 @@ let Realtime = { self.setupSocket() self.setupLocalSendables() } - self.room.addMessages(new DataModel.MessageCollection(DataModel.Messages), true) + self.setupChat() // chat can happen on public maps too } }, endActiveMap: function() { @@ -228,16 +226,14 @@ let Realtime = { if (self.inConversation) self.leaveCall() self.leaveMap() $('.collabCompass').remove() - if (self.room) { - self.room.leave() - ChatView.hide() - ChatView.close() - } + if (self.room) self.room.leave() + ChatView.hide() + ChatView.close() + ChatView.reset() }, turnOn: function(notify) { var self = Realtime $('.collabCompass').show() - ChatView.show() self.room.room = 'map-' + Active.Map.id self.activeMapper = { id: Active.Mapper.id, @@ -250,7 +246,13 @@ let Realtime = { self.localVideo.view.$container.find('.video-cutoff').css({ border: '4px solid ' + self.activeMapper.color }) + }, + setupChat: function() { + const self = Realtime + ChatView.setNewMap(self.room.room) ChatView.addParticipant(self.activeMapper) + ChatView.addMessages(new DataModel.MessageCollection(DataModel.Messages), true) + ChatView.show() }, setupSocket: function() { var self = Realtime @@ -324,7 +326,7 @@ let Realtime = { var createMessage = function(event, data) { self.createMessage(data) } - $(document).on(Views.Room.events.newMessage + '.map', createMessage) + $(document).on(ChatView.events.newMessage + '.map', createMessage) }, countOthersInConversation: function() { var self = Realtime @@ -395,7 +397,7 @@ let Realtime = { callEnded: function() { var self = Realtime - self.room.conversationEnding() + ChatView.conversationEnded() self.room.leaveVideoOnly() self.inConversation = false self.localVideo.view.$container.hide().css({ diff --git a/frontend/src/Metamaps/Realtime/receivable.js b/frontend/src/Metamaps/Realtime/receivable.js index 32b6bf0c..bc708a16 100644 --- a/frontend/src/Metamaps/Realtime/receivable.js +++ b/frontend/src/Metamaps/Realtime/receivable.js @@ -9,6 +9,7 @@ import { indexOf } from 'lodash' import { JUNTO_UPDATED } from './events' import Active from '../Active' +import { ChatView } from '../Views' import DataModel from '../DataModel' import GlobalUI from '../GlobalUI' import Control from '../Control' @@ -152,7 +153,7 @@ export const topicCreated = self => data => { } export const messageCreated = self => data => { - self.room.addMessages(new DataModel.MessageCollection(data)) + ChatView.addMessages(new DataModel.MessageCollection(data)) } export const mapUpdated = self => data => { @@ -230,10 +231,10 @@ export const lostMapper = self => data => { // data.userid // data.username delete self.mappersOnMap[data.userid] - self.room.chat.sound.play('leavemap') + ChatView.sound.play('leavemap') // $('#mapper' + data.userid).remove() $('#compass' + data.userid).remove() - self.room.chat.removeParticipant(data.username) + ChatView.removeParticipant(data.username) GlobalUI.notifyUser(data.username + ' just left the map') @@ -262,8 +263,8 @@ export const mapperListUpdated = self => data => { } if (data.userid !== Active.Mapper.id) { - self.room.chat.addParticipant(self.mappersOnMap[data.userid]) - if (data.userinconversation) self.room.chat.mapperJoinedCall(data.userid) + ChatView.addParticipant(self.mappersOnMap[data.userid]) + if (data.userinconversation) ChatView.mapperJoinedCall(data.userid) // create a div for the collaborators compass self.createCompass(data.username, data.userid, data.avatar, self.mappersOnMap[data.userid].color) @@ -291,8 +292,8 @@ export const newMapper = self => data => { // create an item for them in the realtime box if (data.userid !== Active.Mapper.id) { - self.room.chat.sound.play('joinmap') - self.room.chat.addParticipant(self.mappersOnMap[data.userid]) + ChatView.sound.play('joinmap') + ChatView.addParticipant(self.mappersOnMap[data.userid]) // create a div for the collaborators compass self.createCompass(data.username, data.userid, data.avatar, self.mappersOnMap[data.userid].color) @@ -311,24 +312,24 @@ export const callAccepted = self => userid => { // const username = self.mappersOnMap[userid].name GlobalUI.notifyUser('Conversation starting...') self.joinCall() - self.room.chat.invitationAnswered(userid) + ChatView.invitationAnswered(userid) } export const callDenied = self => userid => { var username = self.mappersOnMap[userid].name GlobalUI.notifyUser(username + " didn't accept your invitation") - self.room.chat.invitationAnswered(userid) + ChatView.invitationAnswered(userid) } export const inviteDenied = self => userid => { var username = self.mappersOnMap[userid].name GlobalUI.notifyUser(username + " didn't accept your invitation") - self.room.chat.invitationAnswered(userid) + ChatView.invitationAnswered(userid) } export const invitedToCall = self => inviter => { - self.room.chat.sound.stop(self.soundId) - self.soundId = self.room.chat.sound.play('sessioninvite') + ChatView.sound.stop(self.soundId) + self.soundId = ChatView.sound.play('sessioninvite') var username = self.mappersOnMap[inviter].name var notifyText = '' @@ -341,8 +342,8 @@ export const invitedToCall = self => inviter => { } export const invitedToJoin = self => inviter => { - self.room.chat.sound.stop(self.soundId) - self.soundId = self.room.chat.sound.play('sessioninvite') + ChatView.sound.stop(self.soundId) + self.soundId = ChatView.sound.play('sessioninvite') var username = self.mappersOnMap[inviter].name var notifyText = username + ' is inviting you to the conversation. Join?' @@ -355,16 +356,14 @@ export const invitedToJoin = self => inviter => { export const mapperJoinedCall = self => id => { var mapper = self.mappersOnMap[id] - if (mapper) { if (self.inConversation) { var username = mapper.name var notifyText = username + ' joined the call' GlobalUI.notifyUser(notifyText) } - mapper.inConversation = true - self.room.chat.mapperJoinedCall(id) + ChatView.mapperJoinedCall(id) } } @@ -377,7 +376,7 @@ export const mapperLeftCall = self => id => { GlobalUI.notifyUser(notifyText) } mapper.inConversation = false - self.room.chat.mapperLeftCall(id) + ChatView.mapperLeftCall(id) if ((self.inConversation && self.countOthersInConversation() === 0) || (!self.inConversation && self.countOthersInConversation() === 1)) { self.callEnded() @@ -392,8 +391,7 @@ export const callInProgress = self => () => { GlobalUI.notifyUser(notifyText, true) $('#toast button.yes').click(e => self.joinCall()) $('#toast button.no').click(e => GlobalUI.clearNotify()) - - self.room.conversationInProgress() + ChatView.conversationInProgress() } export const callStarted = self => () => { @@ -404,7 +402,6 @@ export const callStarted = self => () => { GlobalUI.notifyUser(notifyText, true) $('#toast button.yes').click(e => self.joinCall()) $('#toast button.no').click(e => GlobalUI.clearNotify()) - - self.room.conversationInProgress() + ChatView.conversationInProgress() } diff --git a/frontend/src/Metamaps/Realtime/sendable.js b/frontend/src/Metamaps/Realtime/sendable.js index ef35cb85..9a45d94b 100644 --- a/frontend/src/Metamaps/Realtime/sendable.js +++ b/frontend/src/Metamaps/Realtime/sendable.js @@ -1,6 +1,7 @@ /* global $ */ import Active from '../Active' +import { ChatView } from '../Views' import GlobalUI from '../GlobalUI' import { @@ -72,6 +73,7 @@ export const joinCall = self => () => { $('#wrapper').append(self.localVideo.view.$container) } self.room.join() + ChatView.conversationInProgress(true) }) self.inConversation = true self.socket.emit(JOIN_CALL, { @@ -80,7 +82,7 @@ export const joinCall = self => () => { }) self.webrtc.startLocalVideo() GlobalUI.clearNotify() - self.room.chat.mapperJoinedCall(Active.Mapper.id) + ChatView.mapperJoinedCall(Active.Mapper.id) } export const leaveCall = self => () => { @@ -89,7 +91,8 @@ export const leaveCall = self => () => { id: Active.Mapper.id }) - self.room.chat.mapperLeftCall(Active.Mapper.id) + ChatView.mapperLeftCall(Active.Mapper.id) + ChatView.leaveConversation() // the conversation will carry on without you self.room.leaveVideoOnly() self.inConversation = false self.localVideo.view.$container.hide() @@ -102,7 +105,7 @@ export const leaveCall = self => () => { } export const acceptCall = self => userid => { - self.room.chat.sound.stop(self.soundId) + ChatView.sound.stop(self.soundId) self.socket.emit(ACCEPT_CALL, { mapid: Active.Map.id, invited: Active.Mapper.id, @@ -114,7 +117,7 @@ export const acceptCall = self => userid => { } export const denyCall = self => userid => { - self.room.chat.sound.stop(self.soundId) + ChatView.sound.stop(self.soundId) self.socket.emit(DENY_CALL, { mapid: Active.Map.id, invited: Active.Mapper.id, @@ -124,7 +127,7 @@ export const denyCall = self => userid => { } export const denyInvite = self => userid => { - self.room.chat.sound.stop(self.soundId) + ChatView.sound.stop(self.soundId) self.socket.emit(DENY_INVITE, { mapid: Active.Map.id, invited: Active.Mapper.id, @@ -139,7 +142,7 @@ export const inviteACall = self => userid => { inviter: Active.Mapper.id, invited: userid }) - self.room.chat.invitationPending(userid) + ChatView.invitationPending(userid) GlobalUI.clearNotify() } @@ -149,7 +152,7 @@ export const inviteToJoin = self => userid => { inviter: Active.Mapper.id, invited: userid }) - self.room.chat.invitationPending(userid) + ChatView.invitationPending(userid) } export const sendCoords = self => coords => { diff --git a/frontend/src/Metamaps/Views/ChatView.js b/frontend/src/Metamaps/Views/ChatView.js index d351aadf..21a083b7 100644 --- a/frontend/src/Metamaps/Views/ChatView.js +++ b/frontend/src/Metamaps/Views/ChatView.js @@ -2,22 +2,19 @@ import Backbone from 'backbone' import { Howl } from 'howler' -import Autolinker from 'autolinker' -import { clone, template as lodashTemplate } from 'lodash' -import outdent from 'outdent' import React from 'react' import ReactDOM from 'react-dom' // TODO is this line good or bad // Backbone.$ = window.$ import Active from '../Active' +import DataModel from '../DataModel' import Realtime from '../Realtime' import MapChat from '../../components/MapChat' -const linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false }) - const ChatView = { isOpen: false, + messages: new Backbone.Collection(), conversationLive: false, isParticipating: false, mapChat: null, @@ -35,11 +32,9 @@ const ChatView = { } }) }, - setNewMap: function(messages, mapper, room) { + setNewMap: function(room) { const self = ChatView self.room = room - self.mapper = mapper - self.messages = messages // is a backbone collection self.conversationLive = false self.isParticipating = false self.alertSound = true // whether to play sounds on arrival of new messages or not @@ -88,67 +83,26 @@ const ChatView = { ChatView.participants.remove(participant) ChatView.render() }, - addMessage: (message, isInitial, wasMe) => { - const self = ChatView - if (!isInitial) self.mapChat.newMessage() - - function addZero(i) { - if (i < 10) { - i = '0' + i - } - return i - } - var m = clone(message.attributes) - - m.timestamp = new Date(m.created_at) - - var date = (m.timestamp.getMonth() + 1) + '/' + m.timestamp.getDate() - date += ' ' + addZero(m.timestamp.getHours()) + ':' + addZero(m.timestamp.getMinutes()) - m.timestamp = date - m.image = m.user_image - m.message = linker.link(m.message) - - self.messages.push(m) - // TODO what is scrollMessages? - // scrollMessages scrolls to the bottom of the div when there's new messages“ - // if (!isInitial) self.scrollMessages(200) - self.render() - - if (!wasMe && !isInitial && self.alertSound) self.sound.play('receivechat') - }, - handleInputMessage: text => { - $(document).trigger(ChatView.events.message + '-' + self.room, [{ message: text }]) - }, leaveConversation: () => { - // TODO refactor - // this.$participants.removeClass('is-participating') ChatView.isParticipating = false ChatView.render() }, mapperJoinedCall: id => { - // TODO refactor - // this.$participants.find('.participant-' + id).addClass('active') const mapper = ChatView.participants.findWhere({id}) mapper && mapper.set('isParticipating', true) ChatView.render() }, mapperLeftCall: () => { - // TODO refactor - // this.$participants.find('.participant-' + id).removeClass('active') const mapper = ChatView.participants.findWhere({id}) mapper && mapper.set('isParticipating', false) ChatView.render() }, invitationPending: id => { - // TODO refactor - // this.$participants.find('.participant-' + id).addClass('pending') const mapper = ChatView.participants.findWhere({id}) mapper && mapper.set('isPending', true) ChatView.render() }, invitationAnswered: id => { - // TODO refactor - // this.$participants.find('.participant-' + id).removeClass('pending') const mapper = ChatView.participants.findWhere({id}) mapper && mapper.set('isPending', false) ChatView.render() @@ -161,11 +115,7 @@ const ChatView = { conversationEnded: () => { ChatView.conversationLive = false ChatView.isParticipating = false - // go through and remove isParticipating from all other participants too - ChatView.render() - }, - removeParticipants: () => { - ChatView.participants.remove(ChatView.participants.models) + ChatView.participants.forEach(p => p.set({isParticipating: false, isPending: false})) ChatView.render() }, close: () => { @@ -196,30 +146,51 @@ const ChatView = { }, inputBlur: () => { $(document).trigger(ChatView.events.inputBlur) + }, + addMessage: (message, isInitial, wasMe) => { + const self = ChatView + if (!isInitial) self.mapChat.newMessage() + if (!wasMe && !isInitial && self.alertSound) self.sound.play('receivechat') + self.messages.add(message) + // TODO what is scrollMessages? + // scrollMessages scrolls to the bottom of the div when there's new messages“ + // if (!isInitial) self.scrollMessages(200) + self.render() + }, + sendChatMessage: message => { + var self = ChatView + if (ChatView.alertSound) ChatView.sound.play('sendchat') + var m = new DataModel.Message({ + message: message.message, + resource_id: Active.Map.id, + resource_type: 'Map' + }) + m.save(null, { + success: function(model, response) { + self.addMessages(new DataModel.MessageCollection(model), false, true) + $(document).trigger(ChatView.events.newMessage, [model]) + }, + error: function(model, response) { + console.log('error!', response) + } + }) + }, + handleInputMessage: text => { + ChatView.sendChatMessage({message: text}) + }, + // they should be instantiated as backbone models before they get + // passed to this function + addMessages: (messages, isInitial, wasMe) => { + messages.models.forEach(m => ChatView.addMessage(m, isInitial, wasMe)) + }, + reset: () => { + ChatView.mapChat.reset() + ChatView.participants.reset() + ChatView.messages.reset() + ChatView.render() } } -// ChatView.prototype.conversationInProgress = function(participating) { -// this.$conversationInProgress.show() -// this.$participants.addClass('is-live') -// if (participating) this.$participants.addClass('is-participating') -// this.$button.addClass('active') -// } - -// ChatView.prototype.conversationEnded = function() { -// this.$conversationInProgress.hide() -// this.$participants.removeClass('is-live') -// this.$participants.removeClass('is-participating') -// this.$button.removeClass('active') -// this.$participants.find('.participant').removeClass('active') -// this.$participants.find('.participant').removeClass('pending') -// } - -// ChatView.prototype.addMessage = function(message, isInitial, wasMe) { -// this.messages.add(message) -// Private.addMessage.call(this, message, isInitial, wasMe) -// } -// // ChatView.prototype.scrollMessages = function(duration) { // duration = duration || 0 @@ -228,18 +199,12 @@ const ChatView = { // }, duration) // } -// ChatView.prototype.clearMessages = function() { -// this.unreadMessages = 0 -// this.$unread.hide() -// this.$messages.empty() -// } - /** * @class * @static */ ChatView.events = { - message: 'ChatView:message', + newMessage: 'ChatView:newMessage', openTray: 'ChatView:openTray', closeTray: 'ChatView:closeTray', inputFocus: 'ChatView:inputFocus', diff --git a/frontend/src/Metamaps/Views/Room.js b/frontend/src/Metamaps/Views/Room.js index 1f49ed84..3dc43708 100644 --- a/frontend/src/Metamaps/Views/Room.js +++ b/frontend/src/Metamaps/Views/Room.js @@ -9,8 +9,6 @@ import attachMediaStream from 'attachmediastream' import Active from '../Active' import DataModel from '../DataModel' import Realtime from '../Realtime' - -import ChatView from './ChatView' import VideoView from './VideoView' const Room = function(opts = {}) { @@ -19,36 +17,18 @@ const Room = function(opts = {}) { this.webrtc = opts.webrtc this.room = opts.room this.config = opts.config - this.peopleCount = 0 - this.$myVideo = opts.$video this.myVideo = opts.myVideoView - - this.messages = new Backbone.Collection() - this.currentMapper = new Backbone.Model({ name: opts.username, image: opts.image }) - ChatView.setNewMap(this.messages, this.currentMapper, this.room) - this.videos = {} - this.init() } Room.prototype.join = function(cb) { this.isActiveRoom = true this.webrtc.joinRoom(this.room, cb) - ChatView.conversationInProgress(true) // true indicates participation -} - -Room.prototype.conversationInProgress = function() { - ChatView.conversationInProgress(false) // false indicates not participating -} - -Room.prototype.conversationEnding = function() { - ChatView.conversationEnded() } Room.prototype.leaveVideoOnly = function() { - ChatView.leaveConversation() // the conversation will carry on without you for (var id in this.videos) { this.removeVideo(id) } @@ -64,14 +44,6 @@ Room.prototype.leave = function() { this.isActiveRoom = false this.webrtc.leaveRoom() this.webrtc.stopLocalVideo() - ChatView.conversationEnded() - ChatView.removeParticipants() - ChatView.clearMessages() - this.messages.reset() -} - -Room.prototype.setPeopleCount = function(count) { - this.peopleCount = count } Room.prototype.init = function() { @@ -127,11 +99,6 @@ Room.prototype.init = function() { } v.$container.show() }) - - var sendChatMessage = function(event, data) { - self.sendChatMessage(data) - } - $(document).on(ChatView.events.message + '-' + this.room, sendChatMessage) } Room.prototype.videoAdded = function(callback) { @@ -156,41 +123,4 @@ Room.prototype.removeVideo = function(peer) { } } -Room.prototype.sendChatMessage = function(data) { - var self = this - if (ChatView.alertSound) ChatView.sound.play('sendchat') - var m = new DataModel.Message({ - message: data.message, - resource_id: Active.Map.id, - resource_type: 'Map' - }) - m.save(null, { - success: function(model, response) { - self.addMessages(new DataModel.MessageCollection(model), false, true) - $(document).trigger(Room.events.newMessage, [model]) - }, - error: function(model, response) { - console.log('error!', response) - } - }) -} - - // they should be instantiated as backbone models before they get - // passed to this function -Room.prototype.addMessages = function(messages, isInitial, wasMe) { - var self = this - - messages.models.forEach(function(message) { - ChatView.addMessage(message, isInitial, wasMe) - }) -} - -/** - * @class - * @static - */ -Room.events = { - newMessage: 'Room:newMessage' -} - export default Room diff --git a/frontend/src/components/MapChat/Message.js b/frontend/src/components/MapChat/Message.js index 21c3098c..d5e631cc 100644 --- a/frontend/src/components/MapChat/Message.js +++ b/frontend/src/components/MapChat/Message.js @@ -1,14 +1,31 @@ import React from 'react' +import Autolinker from 'autolinker' + +const linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false }) + +function addZero(i) { + if (i < 10) { + i = '0' + i + } + return i +} + +function formatDate(created_at) { + let date = new Date(created_at) + let formatted = (date.getMonth() + 1) + '/' + date.getDate() + formatted += ' ' + addZero(date.getHours()) + ':' + addZero(date.getMinutes()) + return formatted +} const Message = props => { - const { user_image, user_name, message, timestamp } = props + const { user_image, user_name, message, created_at } = props return (
-
{message}
-
{timestamp}
+
{linker.link(message)}
+
{formatDate(created_at)}
) diff --git a/frontend/src/components/MapChat/Participant.js b/frontend/src/components/MapChat/Participant.js index 3a716b69..7b86a2c4 100644 --- a/frontend/src/components/MapChat/Participant.js +++ b/frontend/src/components/MapChat/Participant.js @@ -11,11 +11,11 @@ class Participant extends Component {
{username} {self ? '(me)' : ''}
- {!conversationLive &&