re-implement chat in react (#997)
* hidously mangle ChatView to start moving it to React * fix up Realtime/index.js - should be good now? * in theory this should compile * ok the MapChat renders using react... * move Handlers code into react - woot * try reintegrating backbone * fix wrapper styling * chat box opens and closes properly * make the unread count work * organize more sanely * refactor some of the ChatView functions * removed management of chatview from room * css can stop handling logic right about now * makin things work * don't need room here anymore * set raw html in message * make pending work * removeParticipant when mapper left was broken * re-enable scrolling, focus, and blur
This commit is contained in:
parent
68f0e91259
commit
73e8f2d4c8
13 changed files with 474 additions and 475 deletions
|
@ -90,13 +90,16 @@
|
||||||
left: 30px;
|
left: 30px;
|
||||||
top: 72px;
|
top: 72px;
|
||||||
}
|
}
|
||||||
|
#chat-box-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
.chat-box {
|
.chat-box {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
float: right;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: #424242;
|
background: #424242;
|
||||||
box-shadow: -8px 0px 16px 2px rgba(0, 0, 0, 0.23);
|
box-shadow: -8px 0px 16px 2px rgba(0, 0, 0, 0.23);
|
||||||
|
@ -114,7 +117,6 @@
|
||||||
background: url(<%= asset_path 'junto_spinner_dark.gif' %>) no-repeat 2px 8px, url(<%= asset_path 'tray_tab.png' %>) no-repeat !important;
|
background: url(<%= asset_path 'junto_spinner_dark.gif' %>) no-repeat 2px 8px, url(<%= asset_path 'tray_tab.png' %>) no-repeat !important;
|
||||||
}
|
}
|
||||||
.chat-box .chat-button .chat-unread {
|
.chat-box .chat-button .chat-unread {
|
||||||
display: none;
|
|
||||||
background: #DAB539;
|
background: #DAB539;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -3px;
|
top: -3px;
|
||||||
|
@ -176,7 +178,6 @@
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
.chat-box .participants .conversation-live {
|
.chat-box .participants .conversation-live {
|
||||||
display: none;
|
|
||||||
padding: 5px 10px 5px 10px;
|
padding: 5px 10px 5px 10px;
|
||||||
background: #c04f4f;
|
background: #c04f4f;
|
||||||
margin: 5px 10px;
|
margin: 5px 10px;
|
||||||
|
@ -187,15 +188,6 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #EBFF00;
|
color: #EBFF00;
|
||||||
}
|
}
|
||||||
.chat-box .participants .conversation-live .leave {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.chat-box .participants.is-participating .conversation-live .leave {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.chat-box .participants.is-participating .conversation-live .join {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.chat-box .participants .participant {
|
.chat-box .participants .participant {
|
||||||
width: 89%;
|
width: 89%;
|
||||||
padding: 8px 8px 2px 8px;
|
padding: 8px 8px 2px 8px;
|
||||||
|
@ -225,32 +217,18 @@
|
||||||
padding: 2px 8px 0;
|
padding: 2px 8px 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
.chat-box .participants .participant.is-self .chat-participant-invite-call,
|
|
||||||
.chat-box .participants .participant.is-self .chat-participant-invite-join {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
.chat-box .participants.is-live .participant .chat-participant-invite-call {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.chat-box .participants .participant .chat-participant-invite-join {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.chat-box .participants.is-live.is-participating .participant:not(.active) .chat-participant-invite-join {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.chat-box .participants .participant .chat-participant-invite-call,
|
.chat-box .participants .participant .chat-participant-invite-call,
|
||||||
.chat-box .participants .participant .chat-participant-invite-join
|
.chat-box .participants .participant .chat-participant-invite-join
|
||||||
{
|
{
|
||||||
float: right;
|
float: right;
|
||||||
background: #4FC059 url(<%= asset_path 'invitepeer16.png' %>) no-repeat center center;
|
background: #4FC059 url(<%= asset_path 'invitepeer16.png' %>) no-repeat center center;
|
||||||
}
|
}
|
||||||
.chat-box .participants .participant.pending .chat-participant-invite-call,
|
.chat-box .participants .participant .chat-participant-invite-call.pending,
|
||||||
.chat-box .participants .participant.pending .chat-participant-invite-join {
|
.chat-box .participants .participant .chat-participant-invite-join.pending {
|
||||||
background: #dab539 url(<%= asset_path 'ellipsis.gif' %>) no-repeat center center;
|
background: #dab539 url(<%= asset_path 'ellipsis.gif' %>) no-repeat center center;
|
||||||
}
|
}
|
||||||
.chat-box .participants .participant .chat-participant-participating {
|
.chat-box .participants .participant .chat-participant-participating {
|
||||||
float: right;
|
float: right;
|
||||||
display: none;
|
|
||||||
margin-top: 14px;
|
margin-top: 14px;
|
||||||
}
|
}
|
||||||
.chat-box .participants .participant .chat-participant-participating .green-dot {
|
.chat-box .participants .participant .chat-participant-participating .green-dot {
|
||||||
|
@ -259,9 +237,6 @@
|
||||||
height: 12px;
|
height: 12px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
.chat-box .participants .participant.active .chat-participant-participating {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.chat-box .chat-header {
|
.chat-box .chat-header {
|
||||||
width: 276px;
|
width: 276px;
|
||||||
padding: 16px 8px 16px 16px;
|
padding: 16px 8px 16px 16px;
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
|
|
||||||
<body class="<%= authenticated? ? "authenticated" : "unauthenticated" %> controller-<%= controller_name %> action-<%= action_name %>">
|
<body class="<%= authenticated? ? "authenticated" : "unauthenticated" %> controller-<%= controller_name %> action-<%= action_name %>">
|
||||||
|
|
||||||
|
<div id="chat-box-wrapper"></div>
|
||||||
|
|
||||||
<a class='feedback-icon' target='_blank' href='https://hylo.com/c/metamaps'></a>
|
<a class='feedback-icon' target='_blank' href='https://hylo.com/c/metamaps'></a>
|
||||||
|
|
||||||
<%= content_tag :div, class: "main" do %>
|
<%= content_tag :div, class: "main" do %>
|
||||||
|
|
|
@ -8,6 +8,7 @@ import DataModel from '../DataModel'
|
||||||
import JIT from '../JIT'
|
import JIT from '../JIT'
|
||||||
import Util from '../Util'
|
import Util from '../Util'
|
||||||
import Views from '../Views'
|
import Views from '../Views'
|
||||||
|
import { ChatView } from '../Views'
|
||||||
import Visualize from '../Visualize'
|
import Visualize from '../Visualize'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -173,48 +174,37 @@ let Realtime = {
|
||||||
self.room = new Views.Room({
|
self.room = new Views.Room({
|
||||||
webrtc: self.webrtc,
|
webrtc: self.webrtc,
|
||||||
socket: self.socket,
|
socket: self.socket,
|
||||||
username: Active.Mapper ? Active.Mapper.get('name') : '',
|
|
||||||
image: Active.Mapper ? Active.Mapper.get('image') : '',
|
|
||||||
room: 'global',
|
room: 'global',
|
||||||
$video: self.localVideo.$video,
|
$video: self.localVideo.$video,
|
||||||
myVideoView: self.localVideo.view,
|
myVideoView: self.localVideo.view,
|
||||||
config: { DOUBLE_CLICK_TOLERANCE: 200 },
|
config: { DOUBLE_CLICK_TOLERANCE: 200 }
|
||||||
soundUrls: [
|
|
||||||
serverData['sounds/MM_sounds.mp3'],
|
|
||||||
serverData['sounds/MM_sounds.ogg']
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
self.room.videoAdded(self.handleVideoAdded)
|
self.room.videoAdded(self.handleVideoAdded)
|
||||||
|
|
||||||
if (!Active.Map) {
|
|
||||||
self.room.chat.$container.hide()
|
|
||||||
}
|
|
||||||
$('body').prepend(self.room.chat.$container)
|
|
||||||
} // if Active.Mapper
|
} // if Active.Mapper
|
||||||
},
|
},
|
||||||
addJuntoListeners: function() {
|
addJuntoListeners: function() {
|
||||||
var self = Realtime
|
var self = Realtime
|
||||||
|
|
||||||
$(document).on(Views.ChatView.events.openTray, function() {
|
$(document).on(ChatView.events.openTray, function() {
|
||||||
$('.main').addClass('compressed')
|
$('.main').addClass('compressed')
|
||||||
self.chatOpen = true
|
self.chatOpen = true
|
||||||
self.positionPeerIcons()
|
self.positionPeerIcons()
|
||||||
})
|
})
|
||||||
$(document).on(Views.ChatView.events.closeTray, function() {
|
$(document).on(ChatView.events.closeTray, function() {
|
||||||
$('.main').removeClass('compressed')
|
$('.main').removeClass('compressed')
|
||||||
self.chatOpen = false
|
self.chatOpen = false
|
||||||
self.positionPeerIcons()
|
self.positionPeerIcons()
|
||||||
})
|
})
|
||||||
$(document).on(Views.ChatView.events.videosOn, function() {
|
$(document).on(ChatView.events.videosOn, function() {
|
||||||
$('#wrapper').removeClass('hideVideos')
|
$('#wrapper').removeClass('hideVideos')
|
||||||
})
|
})
|
||||||
$(document).on(Views.ChatView.events.videosOff, function() {
|
$(document).on(ChatView.events.videosOff, function() {
|
||||||
$('#wrapper').addClass('hideVideos')
|
$('#wrapper').addClass('hideVideos')
|
||||||
})
|
})
|
||||||
$(document).on(Views.ChatView.events.cursorsOn, function() {
|
$(document).on(ChatView.events.cursorsOn, function() {
|
||||||
$('#wrapper').removeClass('hideCursors')
|
$('#wrapper').removeClass('hideCursors')
|
||||||
})
|
})
|
||||||
$(document).on(Views.ChatView.events.cursorsOff, function() {
|
$(document).on(ChatView.events.cursorsOff, function() {
|
||||||
$('#wrapper').addClass('hideCursors')
|
$('#wrapper').addClass('hideCursors')
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -226,7 +216,7 @@ let Realtime = {
|
||||||
self.setupSocket()
|
self.setupSocket()
|
||||||
self.setupLocalSendables()
|
self.setupLocalSendables()
|
||||||
}
|
}
|
||||||
self.room.addMessages(new DataModel.MessageCollection(DataModel.Messages), true)
|
self.setupChat() // chat can happen on public maps too
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
endActiveMap: function() {
|
endActiveMap: function() {
|
||||||
|
@ -236,16 +226,14 @@ let Realtime = {
|
||||||
if (self.inConversation) self.leaveCall()
|
if (self.inConversation) self.leaveCall()
|
||||||
self.leaveMap()
|
self.leaveMap()
|
||||||
$('.collabCompass').remove()
|
$('.collabCompass').remove()
|
||||||
if (self.room) {
|
if (self.room) self.room.leave()
|
||||||
self.room.leave()
|
ChatView.hide()
|
||||||
self.room.chat.$container.hide()
|
ChatView.close()
|
||||||
self.room.chat.close()
|
ChatView.reset()
|
||||||
}
|
|
||||||
},
|
},
|
||||||
turnOn: function(notify) {
|
turnOn: function(notify) {
|
||||||
var self = Realtime
|
var self = Realtime
|
||||||
$('.collabCompass').show()
|
$('.collabCompass').show()
|
||||||
self.room.chat.$container.show()
|
|
||||||
self.room.room = 'map-' + Active.Map.id
|
self.room.room = 'map-' + Active.Map.id
|
||||||
self.activeMapper = {
|
self.activeMapper = {
|
||||||
id: Active.Mapper.id,
|
id: Active.Mapper.id,
|
||||||
|
@ -258,7 +246,13 @@ let Realtime = {
|
||||||
self.localVideo.view.$container.find('.video-cutoff').css({
|
self.localVideo.view.$container.find('.video-cutoff').css({
|
||||||
border: '4px solid ' + self.activeMapper.color
|
border: '4px solid ' + self.activeMapper.color
|
||||||
})
|
})
|
||||||
self.room.chat.addParticipant(self.activeMapper)
|
},
|
||||||
|
setupChat: function() {
|
||||||
|
const self = Realtime
|
||||||
|
ChatView.setNewMap()
|
||||||
|
ChatView.addParticipant(self.activeMapper)
|
||||||
|
ChatView.addMessages(new DataModel.MessageCollection(DataModel.Messages), true)
|
||||||
|
ChatView.show()
|
||||||
},
|
},
|
||||||
setupSocket: function() {
|
setupSocket: function() {
|
||||||
var self = Realtime
|
var self = Realtime
|
||||||
|
@ -332,7 +326,7 @@ let Realtime = {
|
||||||
var createMessage = function(event, data) {
|
var createMessage = function(event, data) {
|
||||||
self.createMessage(data)
|
self.createMessage(data)
|
||||||
}
|
}
|
||||||
$(document).on(Views.Room.events.newMessage + '.map', createMessage)
|
$(document).on(ChatView.events.newMessage + '.map', createMessage)
|
||||||
},
|
},
|
||||||
countOthersInConversation: function() {
|
countOthersInConversation: function() {
|
||||||
var self = Realtime
|
var self = Realtime
|
||||||
|
@ -403,7 +397,7 @@ let Realtime = {
|
||||||
callEnded: function() {
|
callEnded: function() {
|
||||||
var self = Realtime
|
var self = Realtime
|
||||||
|
|
||||||
self.room.conversationEnding()
|
ChatView.conversationEnded()
|
||||||
self.room.leaveVideoOnly()
|
self.room.leaveVideoOnly()
|
||||||
self.inConversation = false
|
self.inConversation = false
|
||||||
self.localVideo.view.$container.hide().css({
|
self.localVideo.view.$container.hide().css({
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { indexOf } from 'lodash'
|
||||||
import { JUNTO_UPDATED } from './events'
|
import { JUNTO_UPDATED } from './events'
|
||||||
|
|
||||||
import Active from '../Active'
|
import Active from '../Active'
|
||||||
|
import { ChatView } from '../Views'
|
||||||
import DataModel from '../DataModel'
|
import DataModel from '../DataModel'
|
||||||
import GlobalUI from '../GlobalUI'
|
import GlobalUI from '../GlobalUI'
|
||||||
import Control from '../Control'
|
import Control from '../Control'
|
||||||
|
@ -152,7 +153,7 @@ export const topicCreated = self => data => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const messageCreated = 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 => {
|
export const mapUpdated = self => data => {
|
||||||
|
@ -230,10 +231,10 @@ export const lostMapper = self => data => {
|
||||||
// data.userid
|
// data.userid
|
||||||
// data.username
|
// data.username
|
||||||
delete self.mappersOnMap[data.userid]
|
delete self.mappersOnMap[data.userid]
|
||||||
self.room.chat.sound.play('leavemap')
|
ChatView.sound.play('leavemap')
|
||||||
// $('#mapper' + data.userid).remove()
|
// $('#mapper' + data.userid).remove()
|
||||||
$('#compass' + data.userid).remove()
|
$('#compass' + data.userid).remove()
|
||||||
self.room.chat.removeParticipant(data.username)
|
ChatView.removeParticipant(ChatView.participants.findWhere({id: data.userid}))
|
||||||
|
|
||||||
GlobalUI.notifyUser(data.username + ' just left the map')
|
GlobalUI.notifyUser(data.username + ' just left the map')
|
||||||
|
|
||||||
|
@ -262,8 +263,8 @@ export const mapperListUpdated = self => data => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.userid !== Active.Mapper.id) {
|
if (data.userid !== Active.Mapper.id) {
|
||||||
self.room.chat.addParticipant(self.mappersOnMap[data.userid])
|
ChatView.addParticipant(self.mappersOnMap[data.userid])
|
||||||
if (data.userinconversation) self.room.chat.mapperJoinedCall(data.userid)
|
if (data.userinconversation) ChatView.mapperJoinedCall(data.userid)
|
||||||
|
|
||||||
// create a div for the collaborators compass
|
// create a div for the collaborators compass
|
||||||
self.createCompass(data.username, data.userid, data.avatar, self.mappersOnMap[data.userid].color)
|
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
|
// create an item for them in the realtime box
|
||||||
if (data.userid !== Active.Mapper.id) {
|
if (data.userid !== Active.Mapper.id) {
|
||||||
self.room.chat.sound.play('joinmap')
|
ChatView.sound.play('joinmap')
|
||||||
self.room.chat.addParticipant(self.mappersOnMap[data.userid])
|
ChatView.addParticipant(self.mappersOnMap[data.userid])
|
||||||
|
|
||||||
// create a div for the collaborators compass
|
// create a div for the collaborators compass
|
||||||
self.createCompass(data.username, data.userid, data.avatar, self.mappersOnMap[data.userid].color)
|
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
|
// const username = self.mappersOnMap[userid].name
|
||||||
GlobalUI.notifyUser('Conversation starting...')
|
GlobalUI.notifyUser('Conversation starting...')
|
||||||
self.joinCall()
|
self.joinCall()
|
||||||
self.room.chat.invitationAnswered(userid)
|
ChatView.invitationAnswered(userid)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const callDenied = self => userid => {
|
export const callDenied = self => userid => {
|
||||||
var username = self.mappersOnMap[userid].name
|
var username = self.mappersOnMap[userid].name
|
||||||
GlobalUI.notifyUser(username + " didn't accept your invitation")
|
GlobalUI.notifyUser(username + " didn't accept your invitation")
|
||||||
self.room.chat.invitationAnswered(userid)
|
ChatView.invitationAnswered(userid)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const inviteDenied = self => userid => {
|
export const inviteDenied = self => userid => {
|
||||||
var username = self.mappersOnMap[userid].name
|
var username = self.mappersOnMap[userid].name
|
||||||
GlobalUI.notifyUser(username + " didn't accept your invitation")
|
GlobalUI.notifyUser(username + " didn't accept your invitation")
|
||||||
self.room.chat.invitationAnswered(userid)
|
ChatView.invitationAnswered(userid)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const invitedToCall = self => inviter => {
|
export const invitedToCall = self => inviter => {
|
||||||
self.room.chat.sound.stop(self.soundId)
|
ChatView.sound.stop(self.soundId)
|
||||||
self.soundId = self.room.chat.sound.play('sessioninvite')
|
self.soundId = ChatView.sound.play('sessioninvite')
|
||||||
|
|
||||||
var username = self.mappersOnMap[inviter].name
|
var username = self.mappersOnMap[inviter].name
|
||||||
var notifyText = '<img src="' + self['junto_spinner_darkgrey.gif'] + '" style="display: inline-block; margin-top: -12px; margin-bottom: -6px; vertical-align: top;" />'
|
var notifyText = '<img src="' + self['junto_spinner_darkgrey.gif'] + '" style="display: inline-block; margin-top: -12px; margin-bottom: -6px; vertical-align: top;" />'
|
||||||
|
@ -341,8 +342,8 @@ export const invitedToCall = self => inviter => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const invitedToJoin = self => inviter => {
|
export const invitedToJoin = self => inviter => {
|
||||||
self.room.chat.sound.stop(self.soundId)
|
ChatView.sound.stop(self.soundId)
|
||||||
self.soundId = self.room.chat.sound.play('sessioninvite')
|
self.soundId = ChatView.sound.play('sessioninvite')
|
||||||
|
|
||||||
var username = self.mappersOnMap[inviter].name
|
var username = self.mappersOnMap[inviter].name
|
||||||
var notifyText = username + ' is inviting you to the conversation. Join?'
|
var notifyText = username + ' is inviting you to the conversation. Join?'
|
||||||
|
@ -355,16 +356,14 @@ export const invitedToJoin = self => inviter => {
|
||||||
|
|
||||||
export const mapperJoinedCall = self => id => {
|
export const mapperJoinedCall = self => id => {
|
||||||
var mapper = self.mappersOnMap[id]
|
var mapper = self.mappersOnMap[id]
|
||||||
|
|
||||||
if (mapper) {
|
if (mapper) {
|
||||||
if (self.inConversation) {
|
if (self.inConversation) {
|
||||||
var username = mapper.name
|
var username = mapper.name
|
||||||
var notifyText = username + ' joined the call'
|
var notifyText = username + ' joined the call'
|
||||||
GlobalUI.notifyUser(notifyText)
|
GlobalUI.notifyUser(notifyText)
|
||||||
}
|
}
|
||||||
|
|
||||||
mapper.inConversation = true
|
mapper.inConversation = true
|
||||||
self.room.chat.mapperJoinedCall(id)
|
ChatView.mapperJoinedCall(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,7 +376,7 @@ export const mapperLeftCall = self => id => {
|
||||||
GlobalUI.notifyUser(notifyText)
|
GlobalUI.notifyUser(notifyText)
|
||||||
}
|
}
|
||||||
mapper.inConversation = false
|
mapper.inConversation = false
|
||||||
self.room.chat.mapperLeftCall(id)
|
ChatView.mapperLeftCall(id)
|
||||||
if ((self.inConversation && self.countOthersInConversation() === 0) ||
|
if ((self.inConversation && self.countOthersInConversation() === 0) ||
|
||||||
(!self.inConversation && self.countOthersInConversation() === 1)) {
|
(!self.inConversation && self.countOthersInConversation() === 1)) {
|
||||||
self.callEnded()
|
self.callEnded()
|
||||||
|
@ -392,8 +391,7 @@ export const callInProgress = self => () => {
|
||||||
GlobalUI.notifyUser(notifyText, true)
|
GlobalUI.notifyUser(notifyText, true)
|
||||||
$('#toast button.yes').click(e => self.joinCall())
|
$('#toast button.yes').click(e => self.joinCall())
|
||||||
$('#toast button.no').click(e => GlobalUI.clearNotify())
|
$('#toast button.no').click(e => GlobalUI.clearNotify())
|
||||||
|
ChatView.conversationInProgress()
|
||||||
self.room.conversationInProgress()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const callStarted = self => () => {
|
export const callStarted = self => () => {
|
||||||
|
@ -404,7 +402,6 @@ export const callStarted = self => () => {
|
||||||
GlobalUI.notifyUser(notifyText, true)
|
GlobalUI.notifyUser(notifyText, true)
|
||||||
$('#toast button.yes').click(e => self.joinCall())
|
$('#toast button.yes').click(e => self.joinCall())
|
||||||
$('#toast button.no').click(e => GlobalUI.clearNotify())
|
$('#toast button.no').click(e => GlobalUI.clearNotify())
|
||||||
|
ChatView.conversationInProgress()
|
||||||
self.room.conversationInProgress()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* global $ */
|
/* global $ */
|
||||||
|
|
||||||
import Active from '../Active'
|
import Active from '../Active'
|
||||||
|
import { ChatView } from '../Views'
|
||||||
import GlobalUI from '../GlobalUI'
|
import GlobalUI from '../GlobalUI'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -72,6 +73,7 @@ export const joinCall = self => () => {
|
||||||
$('#wrapper').append(self.localVideo.view.$container)
|
$('#wrapper').append(self.localVideo.view.$container)
|
||||||
}
|
}
|
||||||
self.room.join()
|
self.room.join()
|
||||||
|
ChatView.conversationInProgress(true)
|
||||||
})
|
})
|
||||||
self.inConversation = true
|
self.inConversation = true
|
||||||
self.socket.emit(JOIN_CALL, {
|
self.socket.emit(JOIN_CALL, {
|
||||||
|
@ -80,7 +82,7 @@ export const joinCall = self => () => {
|
||||||
})
|
})
|
||||||
self.webrtc.startLocalVideo()
|
self.webrtc.startLocalVideo()
|
||||||
GlobalUI.clearNotify()
|
GlobalUI.clearNotify()
|
||||||
self.room.chat.mapperJoinedCall(Active.Mapper.id)
|
ChatView.mapperJoinedCall(Active.Mapper.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const leaveCall = self => () => {
|
export const leaveCall = self => () => {
|
||||||
|
@ -89,7 +91,8 @@ export const leaveCall = self => () => {
|
||||||
id: Active.Mapper.id
|
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.room.leaveVideoOnly()
|
||||||
self.inConversation = false
|
self.inConversation = false
|
||||||
self.localVideo.view.$container.hide()
|
self.localVideo.view.$container.hide()
|
||||||
|
@ -102,7 +105,7 @@ export const leaveCall = self => () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const acceptCall = self => userid => {
|
export const acceptCall = self => userid => {
|
||||||
self.room.chat.sound.stop(self.soundId)
|
ChatView.sound.stop(self.soundId)
|
||||||
self.socket.emit(ACCEPT_CALL, {
|
self.socket.emit(ACCEPT_CALL, {
|
||||||
mapid: Active.Map.id,
|
mapid: Active.Map.id,
|
||||||
invited: Active.Mapper.id,
|
invited: Active.Mapper.id,
|
||||||
|
@ -114,7 +117,7 @@ export const acceptCall = self => userid => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const denyCall = self => userid => {
|
export const denyCall = self => userid => {
|
||||||
self.room.chat.sound.stop(self.soundId)
|
ChatView.sound.stop(self.soundId)
|
||||||
self.socket.emit(DENY_CALL, {
|
self.socket.emit(DENY_CALL, {
|
||||||
mapid: Active.Map.id,
|
mapid: Active.Map.id,
|
||||||
invited: Active.Mapper.id,
|
invited: Active.Mapper.id,
|
||||||
|
@ -124,7 +127,7 @@ export const denyCall = self => userid => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const denyInvite = self => userid => {
|
export const denyInvite = self => userid => {
|
||||||
self.room.chat.sound.stop(self.soundId)
|
ChatView.sound.stop(self.soundId)
|
||||||
self.socket.emit(DENY_INVITE, {
|
self.socket.emit(DENY_INVITE, {
|
||||||
mapid: Active.Map.id,
|
mapid: Active.Map.id,
|
||||||
invited: Active.Mapper.id,
|
invited: Active.Mapper.id,
|
||||||
|
@ -139,7 +142,7 @@ export const inviteACall = self => userid => {
|
||||||
inviter: Active.Mapper.id,
|
inviter: Active.Mapper.id,
|
||||||
invited: userid
|
invited: userid
|
||||||
})
|
})
|
||||||
self.room.chat.invitationPending(userid)
|
ChatView.invitationPending(userid)
|
||||||
GlobalUI.clearNotify()
|
GlobalUI.clearNotify()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,7 +152,7 @@ export const inviteToJoin = self => userid => {
|
||||||
inviter: Active.Mapper.id,
|
inviter: Active.Mapper.id,
|
||||||
invited: userid
|
invited: userid
|
||||||
})
|
})
|
||||||
self.room.chat.invitationPending(userid)
|
ChatView.invitationPending(userid)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sendCoords = self => coords => {
|
export const sendCoords = self => coords => {
|
||||||
|
|
|
@ -2,128 +2,27 @@
|
||||||
|
|
||||||
import Backbone from 'backbone'
|
import Backbone from 'backbone'
|
||||||
import { Howl } from 'howler'
|
import { Howl } from 'howler'
|
||||||
import Autolinker from 'autolinker'
|
import React from 'react'
|
||||||
import { clone, template as lodashTemplate } from 'lodash'
|
import ReactDOM from 'react-dom'
|
||||||
import outdent from 'outdent'
|
|
||||||
// TODO is this line good or bad
|
// TODO is this line good or bad
|
||||||
// Backbone.$ = window.$
|
// Backbone.$ = window.$
|
||||||
|
|
||||||
const linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false })
|
import Active from '../Active'
|
||||||
|
import DataModel from '../DataModel'
|
||||||
|
import Realtime from '../Realtime'
|
||||||
|
import MapChat from '../../components/MapChat'
|
||||||
|
|
||||||
var Private = {
|
const ChatView = {
|
||||||
messageHTML: outdent`
|
isOpen: false,
|
||||||
<div class='chat-message'>
|
messages: new Backbone.Collection(),
|
||||||
<div class='chat-message-user'><img src='{{ user_image }}' title='{{user_name }}'/></div>
|
conversationLive: false,
|
||||||
<div class='chat-message-text'>{{ message }}</div>
|
isParticipating: false,
|
||||||
<div class='chat-message-time'>{{ timestamp }}</div>
|
mapChat: null,
|
||||||
<div class='clearfloat'></div>
|
domId: 'chat-box-wrapper',
|
||||||
</div>`,
|
init: function(urls) {
|
||||||
participantHTML: outdent`
|
const self = ChatView
|
||||||
<div class='participant participant-{{ id }} {{ selfClass }}'>
|
self.sound = new Howl({
|
||||||
<div class='chat-participant-image'>
|
src: urls,
|
||||||
<img src='{{ image }}' style='border: 2px solid {{ color }};' />
|
|
||||||
</div>
|
|
||||||
<div class='chat-participant-name'>
|
|
||||||
{{ username }} {{ selfName }}
|
|
||||||
</div>
|
|
||||||
<button type='button'
|
|
||||||
class='button chat-participant-invite-call'
|
|
||||||
onclick='Metamaps.Realtime.inviteACall({{ id}});'
|
|
||||||
></button>
|
|
||||||
<button type='button'
|
|
||||||
class='button chat-participant-invite-join'
|
|
||||||
onclick='Metamaps.Realtime.inviteToJoin({{ id}});'
|
|
||||||
></button>
|
|
||||||
<span class='chat-participant-participating'>
|
|
||||||
<div class='green-dot'></div>
|
|
||||||
</span>
|
|
||||||
<div class='clearfloat'></div>
|
|
||||||
</div>`,
|
|
||||||
templates: function() {
|
|
||||||
const templateSettings = {
|
|
||||||
interpolate: /\{\{(.+?)\}\}/g
|
|
||||||
}
|
|
||||||
|
|
||||||
this.messageTemplate = lodashTemplate(Private.messageHTML, templateSettings)
|
|
||||||
|
|
||||||
this.participantTemplate = lodashTemplate(Private.participantHTML, templateSettings)
|
|
||||||
},
|
|
||||||
createElements: function() {
|
|
||||||
this.$unread = $('<div class="chat-unread"></div>')
|
|
||||||
this.$button = $('<div class="chat-button"><div class="tooltips">Chat</div></div>')
|
|
||||||
this.$messageInput = $('<textarea placeholder="Send a message..." class="chat-input"></textarea>')
|
|
||||||
this.$juntoHeader = $('<div class="junto-header">PARTICIPANTS</div>')
|
|
||||||
this.$videoToggle = $('<div class="video-toggle"></div>')
|
|
||||||
this.$cursorToggle = $('<div class="cursor-toggle"></div>')
|
|
||||||
this.$participants = $('<div class="participants"></div>')
|
|
||||||
this.$conversationInProgress = $(outdent`
|
|
||||||
<div class="conversation-live">
|
|
||||||
LIVE
|
|
||||||
<span class="call-action leave" onclick="Metamaps.Realtime.leaveCall();">
|
|
||||||
LEAVE
|
|
||||||
</span>
|
|
||||||
<span class="call-action join" onclick="Metamaps.Realtime.joinCall();">
|
|
||||||
JOIN
|
|
||||||
</span>
|
|
||||||
</div>`)
|
|
||||||
this.$chatHeader = $('<div class="chat-header">CHAT</div>')
|
|
||||||
this.$soundToggle = $('<div class="sound-toggle"></div>')
|
|
||||||
this.$messages = $('<div class="chat-messages"></div>')
|
|
||||||
this.$container = $('<div class="chat-box"></div>')
|
|
||||||
},
|
|
||||||
attachElements: function() {
|
|
||||||
this.$button.append(this.$unread)
|
|
||||||
|
|
||||||
this.$juntoHeader.append(this.$videoToggle)
|
|
||||||
this.$juntoHeader.append(this.$cursorToggle)
|
|
||||||
|
|
||||||
this.$chatHeader.append(this.$soundToggle)
|
|
||||||
|
|
||||||
this.$participants.append(this.$conversationInProgress)
|
|
||||||
|
|
||||||
this.$container.append(this.$juntoHeader)
|
|
||||||
this.$container.append(this.$participants)
|
|
||||||
this.$container.append(this.$chatHeader)
|
|
||||||
this.$container.append(this.$button)
|
|
||||||
this.$container.append(this.$messages)
|
|
||||||
this.$container.append(this.$messageInput)
|
|
||||||
},
|
|
||||||
addEventListeners: function() {
|
|
||||||
var self = this
|
|
||||||
|
|
||||||
this.participants.on('add', function(participant) {
|
|
||||||
Private.addParticipant.call(self, participant)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.participants.on('remove', function(participant) {
|
|
||||||
Private.removeParticipant.call(self, participant)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.$button.on('click', function() {
|
|
||||||
Handlers.buttonClick.call(self)
|
|
||||||
})
|
|
||||||
this.$videoToggle.on('click', function() {
|
|
||||||
Handlers.videoToggleClick.call(self)
|
|
||||||
})
|
|
||||||
this.$cursorToggle.on('click', function() {
|
|
||||||
Handlers.cursorToggleClick.call(self)
|
|
||||||
})
|
|
||||||
this.$soundToggle.on('click', function() {
|
|
||||||
Handlers.soundToggleClick.call(self)
|
|
||||||
})
|
|
||||||
this.$messageInput.on('keyup', function(event) {
|
|
||||||
Handlers.keyUp.call(self, event)
|
|
||||||
})
|
|
||||||
this.$messageInput.on('focus', function() {
|
|
||||||
Handlers.inputFocus.call(self)
|
|
||||||
})
|
|
||||||
this.$messageInput.on('blur', function() {
|
|
||||||
Handlers.inputBlur.call(self)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
initializeSounds: function(soundUrls) {
|
|
||||||
this.sound = new Howl({
|
|
||||||
src: soundUrls,
|
|
||||||
sprite: {
|
sprite: {
|
||||||
joinmap: [0, 561],
|
joinmap: [0, 561],
|
||||||
leavemap: [1000, 592],
|
leavemap: [1000, 592],
|
||||||
|
@ -133,226 +32,172 @@ var Private = {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
incrementUnread: function() {
|
setNewMap: function() {
|
||||||
this.unreadMessages++
|
const self = ChatView
|
||||||
this.$unread.html(this.unreadMessages)
|
self.conversationLive = false
|
||||||
this.$unread.show()
|
self.isParticipating = false
|
||||||
|
self.alertSound = true // whether to play sounds on arrival of new messages or not
|
||||||
|
self.cursorsShowing = true
|
||||||
|
self.videosShowing = true
|
||||||
|
self.participants = new Backbone.Collection()
|
||||||
|
self.render()
|
||||||
},
|
},
|
||||||
addMessage: function(message, isInitial, wasMe) {
|
show: () => {
|
||||||
if (!this.isOpen && !isInitial) Private.incrementUnread.call(this)
|
$('#' + ChatView.domId).show()
|
||||||
|
|
||||||
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)
|
|
||||||
var $html = $(this.messageTemplate(m))
|
|
||||||
this.$messages.append($html)
|
|
||||||
if (!isInitial) this.scrollMessages(200)
|
|
||||||
|
|
||||||
if (!wasMe && !isInitial && this.alertSound) this.sound.play('receivechat')
|
|
||||||
},
|
},
|
||||||
initialMessages: function() {
|
hide: () => {
|
||||||
var messages = this.messages.models
|
$('#' + ChatView.domId).hide()
|
||||||
for (var i = 0; i < messages.length; i++) {
|
|
||||||
Private.addMessage.call(this, messages[i], true)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
handleInputMessage: function() {
|
render: () => {
|
||||||
var message = {
|
if (!Active.Map) return
|
||||||
message: this.$messageInput.val()
|
const self = ChatView
|
||||||
}
|
self.mapChat = ReactDOM.render(React.createElement(MapChat, {
|
||||||
this.$messageInput.val('')
|
conversationLive: self.conversationLive,
|
||||||
$(document).trigger(ChatView.events.message + '-' + this.room, [message])
|
isParticipating: self.isParticipating,
|
||||||
|
onOpen: self.onOpen,
|
||||||
|
onClose: self.onClose,
|
||||||
|
leaveCall: Realtime.leaveCall,
|
||||||
|
joinCall: Realtime.joinCall,
|
||||||
|
inviteACall: Realtime.inviteACall,
|
||||||
|
inviteToJoin: Realtime.inviteToJoin,
|
||||||
|
participants: self.participants.models.map(p => p.attributes),
|
||||||
|
messages: self.messages.models.map(m => m.attributes),
|
||||||
|
videoToggleClick: self.videoToggleClick,
|
||||||
|
cursorToggleClick: self.cursorToggleClick,
|
||||||
|
soundToggleClick: self.soundToggleClick,
|
||||||
|
inputBlur: self.inputBlur,
|
||||||
|
inputFocus: self.inputFocus,
|
||||||
|
handleInputMessage: self.handleInputMessage
|
||||||
|
}), document.getElementById(ChatView.domId))
|
||||||
},
|
},
|
||||||
addParticipant: function(participant) {
|
onOpen: () => {
|
||||||
var p = clone(participant.attributes)
|
$(document).trigger(ChatView.events.openTray)
|
||||||
if (p.self) {
|
|
||||||
p.selfClass = 'is-self'
|
|
||||||
p.selfName = '(me)'
|
|
||||||
} else {
|
|
||||||
p.selfClass = ''
|
|
||||||
p.selfName = ''
|
|
||||||
}
|
|
||||||
var html = this.participantTemplate(p)
|
|
||||||
this.$participants.append(html)
|
|
||||||
},
|
},
|
||||||
removeParticipant: function(participant) {
|
onClose: () => {
|
||||||
this.$container.find('.participant-' + participant.get('id')).remove()
|
$(document).trigger(ChatView.events.closeTray)
|
||||||
}
|
},
|
||||||
}
|
addParticipant: participant => {
|
||||||
|
ChatView.participants.add(participant)
|
||||||
var Handlers = {
|
ChatView.render()
|
||||||
buttonClick: function() {
|
},
|
||||||
if (this.isOpen) this.close()
|
removeParticipant: participant => {
|
||||||
else if (!this.isOpen) this.open()
|
ChatView.participants.remove(participant)
|
||||||
|
ChatView.render()
|
||||||
|
},
|
||||||
|
leaveConversation: () => {
|
||||||
|
ChatView.isParticipating = false
|
||||||
|
ChatView.render()
|
||||||
|
},
|
||||||
|
mapperJoinedCall: id => {
|
||||||
|
const mapper = ChatView.participants.findWhere({id})
|
||||||
|
mapper && mapper.set('isParticipating', true)
|
||||||
|
ChatView.render()
|
||||||
|
},
|
||||||
|
mapperLeftCall: id => {
|
||||||
|
const mapper = ChatView.participants.findWhere({id})
|
||||||
|
mapper && mapper.set('isParticipating', false)
|
||||||
|
ChatView.render()
|
||||||
|
},
|
||||||
|
invitationPending: id => {
|
||||||
|
const mapper = ChatView.participants.findWhere({id})
|
||||||
|
mapper && mapper.set('isPending', true)
|
||||||
|
ChatView.render()
|
||||||
|
},
|
||||||
|
invitationAnswered: id => {
|
||||||
|
const mapper = ChatView.participants.findWhere({id})
|
||||||
|
mapper && mapper.set('isPending', false)
|
||||||
|
ChatView.render()
|
||||||
|
},
|
||||||
|
conversationInProgress: participating => {
|
||||||
|
ChatView.conversationLive = true
|
||||||
|
ChatView.isParticipating = participating
|
||||||
|
ChatView.render()
|
||||||
|
},
|
||||||
|
conversationEnded: () => {
|
||||||
|
ChatView.conversationLive = false
|
||||||
|
ChatView.isParticipating = false
|
||||||
|
ChatView.participants.forEach(p => p.set({isParticipating: false, isPending: false}))
|
||||||
|
ChatView.render()
|
||||||
|
},
|
||||||
|
close: () => {
|
||||||
|
ChatView.mapChat.close()
|
||||||
|
},
|
||||||
|
open: () => {
|
||||||
|
ChatView.mapChat.open()
|
||||||
},
|
},
|
||||||
videoToggleClick: function() {
|
videoToggleClick: function() {
|
||||||
this.$videoToggle.toggleClass('active')
|
ChatView.videosShowing = !ChatView.videosShowing
|
||||||
this.videosShowing = !this.videosShowing
|
$(document).trigger(ChatView.videosShowing ? ChatView.events.videosOn : ChatView.events.videosOff)
|
||||||
$(document).trigger(this.videosShowing ? ChatView.events.videosOn : ChatView.events.videosOff)
|
|
||||||
},
|
},
|
||||||
cursorToggleClick: function() {
|
cursorToggleClick: function() {
|
||||||
this.$cursorToggle.toggleClass('active')
|
ChatView.cursorsShowing = !ChatView.cursorsShowing
|
||||||
this.cursorsShowing = !this.cursorsShowing
|
$(document).trigger(ChatView.cursorsShowing ? ChatView.events.cursorsOn : ChatView.events.cursorsOff)
|
||||||
$(document).trigger(this.cursorsShowing ? ChatView.events.cursorsOn : ChatView.events.cursorsOff)
|
|
||||||
},
|
},
|
||||||
soundToggleClick: function() {
|
soundToggleClick: function() {
|
||||||
this.alertSound = !this.alertSound
|
ChatView.alertSound = !ChatView.alertSound
|
||||||
this.$soundToggle.toggleClass('active')
|
|
||||||
},
|
},
|
||||||
keyUp: function(event) {
|
inputFocus: () => {
|
||||||
switch (event.which) {
|
|
||||||
case 13: // enter
|
|
||||||
Private.handleInputMessage.call(this)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
inputFocus: function() {
|
|
||||||
$(document).trigger(ChatView.events.inputFocus)
|
$(document).trigger(ChatView.events.inputFocus)
|
||||||
},
|
},
|
||||||
inputBlur: function() {
|
inputBlur: () => {
|
||||||
$(document).trigger(ChatView.events.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)
|
||||||
|
self.render()
|
||||||
|
if (!isInitial) self.mapChat.scroll()
|
||||||
|
},
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatView = function(messages, mapper, room, opts = {}) {
|
// ChatView.prototype.scrollMessages = function(duration) {
|
||||||
this.room = room
|
// duration = duration || 0
|
||||||
this.mapper = mapper
|
|
||||||
this.messages = messages // backbone collection
|
|
||||||
|
|
||||||
this.isOpen = false
|
// this.$messages.animate({
|
||||||
this.alertSound = true // whether to play sounds on arrival of new messages or not
|
// scrollTop: this.$messages[0].scrollHeight
|
||||||
this.cursorsShowing = true
|
// }, duration)
|
||||||
this.videosShowing = true
|
// }
|
||||||
this.unreadMessages = 0
|
|
||||||
this.participants = new Backbone.Collection()
|
|
||||||
|
|
||||||
Private.templates.call(this)
|
|
||||||
Private.createElements.call(this)
|
|
||||||
Private.attachElements.call(this)
|
|
||||||
Private.addEventListeners.call(this)
|
|
||||||
Private.initialMessages.call(this)
|
|
||||||
Private.initializeSounds.call(this, opts.soundUrls)
|
|
||||||
this.$container.css({
|
|
||||||
right: '-300px'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatView.prototype.conversationInProgress = function(participating) {
|
|
||||||
this.$conversationInProgress.show()
|
|
||||||
this.$participants.addClass('is-live')
|
|
||||||
if (participating) this.$participants.addClass('is-participating')
|
|
||||||
this.$button.addClass('active')
|
|
||||||
|
|
||||||
// hide invite to call buttons
|
|
||||||
}
|
|
||||||
|
|
||||||
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.leaveConversation = function() {
|
|
||||||
this.$participants.removeClass('is-participating')
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatView.prototype.mapperJoinedCall = function(id) {
|
|
||||||
this.$participants.find('.participant-' + id).addClass('active')
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatView.prototype.mapperLeftCall = function(id) {
|
|
||||||
this.$participants.find('.participant-' + id).removeClass('active')
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatView.prototype.invitationPending = function(id) {
|
|
||||||
this.$participants.find('.participant-' + id).addClass('pending')
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatView.prototype.invitationAnswered = function(id) {
|
|
||||||
this.$participants.find('.participant-' + id).removeClass('pending')
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatView.prototype.addParticipant = function(participant) {
|
|
||||||
this.participants.add(participant)
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatView.prototype.removeParticipant = function(username) {
|
|
||||||
var p = this.participants.find(p => p.get('username') === username)
|
|
||||||
if (p) {
|
|
||||||
this.participants.remove(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatView.prototype.removeParticipants = function() {
|
|
||||||
this.participants.remove(this.participants.models)
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatView.prototype.open = function() {
|
|
||||||
this.$container.css({
|
|
||||||
right: '0'
|
|
||||||
})
|
|
||||||
this.$messageInput.focus()
|
|
||||||
this.isOpen = true
|
|
||||||
this.unreadMessages = 0
|
|
||||||
this.$unread.hide()
|
|
||||||
this.scrollMessages(0)
|
|
||||||
$(document).trigger(ChatView.events.openTray)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
this.$messages.animate({
|
|
||||||
scrollTop: this.$messages[0].scrollHeight
|
|
||||||
}, duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatView.prototype.clearMessages = function() {
|
|
||||||
this.unreadMessages = 0
|
|
||||||
this.$unread.hide()
|
|
||||||
this.$messages.empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatView.prototype.close = function() {
|
|
||||||
this.$container.css({
|
|
||||||
right: '-300px'
|
|
||||||
})
|
|
||||||
this.$messageInput.blur()
|
|
||||||
this.isOpen = false
|
|
||||||
$(document).trigger(ChatView.events.closeTray)
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatView.prototype.remove = function() {
|
|
||||||
this.$button.off()
|
|
||||||
this.$container.remove()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class
|
* @class
|
||||||
* @static
|
* @static
|
||||||
*/
|
*/
|
||||||
ChatView.events = {
|
ChatView.events = {
|
||||||
message: 'ChatView:message',
|
newMessage: 'ChatView:newMessage',
|
||||||
openTray: 'ChatView:openTray',
|
openTray: 'ChatView:openTray',
|
||||||
closeTray: 'ChatView:closeTray',
|
closeTray: 'ChatView:closeTray',
|
||||||
inputFocus: 'ChatView:inputFocus',
|
inputFocus: 'ChatView:inputFocus',
|
||||||
|
|
|
@ -9,8 +9,6 @@ import attachMediaStream from 'attachmediastream'
|
||||||
import Active from '../Active'
|
import Active from '../Active'
|
||||||
import DataModel from '../DataModel'
|
import DataModel from '../DataModel'
|
||||||
import Realtime from '../Realtime'
|
import Realtime from '../Realtime'
|
||||||
|
|
||||||
import ChatView from './ChatView'
|
|
||||||
import VideoView from './VideoView'
|
import VideoView from './VideoView'
|
||||||
|
|
||||||
const Room = function(opts = {}) {
|
const Room = function(opts = {}) {
|
||||||
|
@ -19,38 +17,18 @@ const Room = function(opts = {}) {
|
||||||
this.webrtc = opts.webrtc
|
this.webrtc = opts.webrtc
|
||||||
this.room = opts.room
|
this.room = opts.room
|
||||||
this.config = opts.config
|
this.config = opts.config
|
||||||
this.peopleCount = 0
|
|
||||||
|
|
||||||
this.$myVideo = opts.$video
|
this.$myVideo = opts.$video
|
||||||
this.myVideo = opts.myVideoView
|
this.myVideo = opts.myVideoView
|
||||||
|
|
||||||
this.messages = new Backbone.Collection()
|
|
||||||
this.currentMapper = new Backbone.Model({ name: opts.username, image: opts.image })
|
|
||||||
this.chat = new ChatView(this.messages, this.currentMapper, this.room, {
|
|
||||||
soundUrls: opts.soundUrls
|
|
||||||
})
|
|
||||||
|
|
||||||
this.videos = {}
|
this.videos = {}
|
||||||
|
|
||||||
this.init()
|
this.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
Room.prototype.join = function(cb) {
|
Room.prototype.join = function(cb) {
|
||||||
this.isActiveRoom = true
|
this.isActiveRoom = true
|
||||||
this.webrtc.joinRoom(this.room, cb)
|
this.webrtc.joinRoom(this.room, cb)
|
||||||
this.chat.conversationInProgress(true) // true indicates participation
|
|
||||||
}
|
|
||||||
|
|
||||||
Room.prototype.conversationInProgress = function() {
|
|
||||||
this.chat.conversationInProgress(false) // false indicates not participating
|
|
||||||
}
|
|
||||||
|
|
||||||
Room.prototype.conversationEnding = function() {
|
|
||||||
this.chat.conversationEnded()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Room.prototype.leaveVideoOnly = function() {
|
Room.prototype.leaveVideoOnly = function() {
|
||||||
this.chat.leaveConversation() // the conversation will carry on without you
|
|
||||||
for (var id in this.videos) {
|
for (var id in this.videos) {
|
||||||
this.removeVideo(id)
|
this.removeVideo(id)
|
||||||
}
|
}
|
||||||
|
@ -66,14 +44,6 @@ Room.prototype.leave = function() {
|
||||||
this.isActiveRoom = false
|
this.isActiveRoom = false
|
||||||
this.webrtc.leaveRoom()
|
this.webrtc.leaveRoom()
|
||||||
this.webrtc.stopLocalVideo()
|
this.webrtc.stopLocalVideo()
|
||||||
this.chat.conversationEnded()
|
|
||||||
this.chat.removeParticipants()
|
|
||||||
this.chat.clearMessages()
|
|
||||||
this.messages.reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
Room.prototype.setPeopleCount = function(count) {
|
|
||||||
this.peopleCount = count
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Room.prototype.init = function() {
|
Room.prototype.init = function() {
|
||||||
|
@ -129,11 +99,6 @@ Room.prototype.init = function() {
|
||||||
}
|
}
|
||||||
v.$container.show()
|
v.$container.show()
|
||||||
})
|
})
|
||||||
|
|
||||||
var sendChatMessage = function(event, data) {
|
|
||||||
self.sendChatMessage(data)
|
|
||||||
}
|
|
||||||
$(document).on(ChatView.events.message + '-' + this.room, sendChatMessage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Room.prototype.videoAdded = function(callback) {
|
Room.prototype.videoAdded = function(callback) {
|
||||||
|
@ -158,42 +123,4 @@ Room.prototype.removeVideo = function(peer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Room.prototype.sendChatMessage = function(data) {
|
|
||||||
var self = this
|
|
||||||
// this.roomRef.child('messages').push(data)
|
|
||||||
if (self.chat.alertSound) self.chat.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) {
|
|
||||||
self.chat.addMessage(message, isInitial, wasMe)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class
|
|
||||||
* @static
|
|
||||||
*/
|
|
||||||
Room.events = {
|
|
||||||
newMessage: 'Room:newMessage'
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Room
|
export default Room
|
||||||
|
|
|
@ -7,8 +7,9 @@ import Room from './Room'
|
||||||
import { JUNTO_UPDATED } from '../Realtime/events'
|
import { JUNTO_UPDATED } from '../Realtime/events'
|
||||||
|
|
||||||
const Views = {
|
const Views = {
|
||||||
init: () => {
|
init: (serverData) => {
|
||||||
$(document).on(JUNTO_UPDATED, () => ExploreMaps.render())
|
$(document).on(JUNTO_UPDATED, () => ExploreMaps.render())
|
||||||
|
ChatView.init([serverData['sounds/MM_sounds.mp3'],serverData['sounds/MM_sounds.ogg']])
|
||||||
},
|
},
|
||||||
ExploreMaps,
|
ExploreMaps,
|
||||||
ChatView,
|
ChatView,
|
||||||
|
|
35
frontend/src/components/MapChat/Message.js
Normal file
35
frontend/src/components/MapChat/Message.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
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, created_at } = props
|
||||||
|
const messageHtml = {__html: linker.link(message)}
|
||||||
|
return (
|
||||||
|
<div className="chat-message">
|
||||||
|
<div className="chat-message-user">
|
||||||
|
<img src={user_image} title={user_name} />
|
||||||
|
</div>
|
||||||
|
<div className="chat-message-text" dangerouslySetInnerHTML={messageHtml}></div>
|
||||||
|
<div className="chat-message-time">{formatDate(created_at)}</div>
|
||||||
|
<div className="clearfloat"></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Message
|
45
frontend/src/components/MapChat/Participant.js
Normal file
45
frontend/src/components/MapChat/Participant.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import React, { PropTypes, Component } from 'react'
|
||||||
|
|
||||||
|
class Participant extends Component {
|
||||||
|
render() {
|
||||||
|
const { conversationLive, mapperIsLive, isParticipating, isPending, id, self, image, username, selfName, color } = this.props
|
||||||
|
return (
|
||||||
|
<div className={`participant participant-${id} ${self ? 'is-self' : ''}`}>
|
||||||
|
<div className="chat-participant-image">
|
||||||
|
<img src={image} style={{ border: `2px solid ${color}`}} />
|
||||||
|
</div>
|
||||||
|
<div className="chat-participant-name">
|
||||||
|
{username} {self ? '(me)' : ''}
|
||||||
|
</div>
|
||||||
|
{!self && !conversationLive && <button
|
||||||
|
className={`button chat-participant-invite-call ${isPending ? 'pending' : ''}`}
|
||||||
|
onClick={() => !isPending && this.props.inviteACall(id)} // Realtime.inviteACall(id)
|
||||||
|
/>}
|
||||||
|
{!self && mapperIsLive && !isParticipating && <button
|
||||||
|
className={`button chat-participant-invite-join ${isPending ? 'pending' : ''}`}
|
||||||
|
onClick={() => !isPending && this.props.inviteToJoin(id)} // Realtime.inviteToJoin(id)
|
||||||
|
/>}
|
||||||
|
{isParticipating && <span className="chat-participant-participating">
|
||||||
|
<div className="green-dot"></div>
|
||||||
|
</span>}
|
||||||
|
<div className="clearfloat"></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Participant.propTypes = {
|
||||||
|
conversationLive: PropTypes.bool,
|
||||||
|
mapperIsLive: PropTypes.bool,
|
||||||
|
isParticipating: PropTypes.bool,
|
||||||
|
isPending: PropTypes.bool,
|
||||||
|
color: PropTypes.string, // css color
|
||||||
|
id: PropTypes.number,
|
||||||
|
image: PropTypes.string, // image url
|
||||||
|
self: PropTypes.bool,
|
||||||
|
username: PropTypes.string,
|
||||||
|
inviteACall: PropTypes.func,
|
||||||
|
inviteToJoin: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Participant
|
7
frontend/src/components/MapChat/Unread.js
Normal file
7
frontend/src/components/MapChat/Unread.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const Unread = props => {
|
||||||
|
return props.count ? <div className="chat-unread">{props.count}</div> : null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Unread
|
167
frontend/src/components/MapChat/index.js
Normal file
167
frontend/src/components/MapChat/index.js
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
import React, { PropTypes, Component } from 'react'
|
||||||
|
import Unread from './Unread'
|
||||||
|
import Participant from './Participant'
|
||||||
|
import Message from './Message'
|
||||||
|
|
||||||
|
class MapChat extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
unreadMessages: 0,
|
||||||
|
open: false,
|
||||||
|
messageText: '',
|
||||||
|
alertSound: true, // whether to play sounds on arrival of new messages or not
|
||||||
|
cursorsShowing: true,
|
||||||
|
videosShowing: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reset = () => {
|
||||||
|
this.setState({
|
||||||
|
unreadMessages: 0,
|
||||||
|
open: false,
|
||||||
|
messageText: '',
|
||||||
|
alertSound: true, // whether to play sounds on arrival of new messages or not
|
||||||
|
cursorsShowing: true,
|
||||||
|
videosShowing: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
close = () => {
|
||||||
|
this.setState({open: false})
|
||||||
|
this.props.onClose()
|
||||||
|
this.messageInput.blur()
|
||||||
|
}
|
||||||
|
|
||||||
|
open = () => {
|
||||||
|
this.scroll()
|
||||||
|
this.setState({open: true, unreadMessages: 0})
|
||||||
|
this.props.onOpen()
|
||||||
|
this.messageInput.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
newMessage = () => {
|
||||||
|
if (!this.state.open) this.setState({unreadMessages: this.state.unreadMessages + 1})
|
||||||
|
}
|
||||||
|
|
||||||
|
scroll = () => {
|
||||||
|
this.messagesDiv.scrollTop = this.messagesDiv.scrollHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDrawer = () => {
|
||||||
|
if (this.state.open) this.close()
|
||||||
|
else if (!this.state.open) this.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAlertSound = () => {
|
||||||
|
this.setState({alertSound: !this.state.alertSound})
|
||||||
|
this.props.soundToggleClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleCursorsShowing = () => {
|
||||||
|
this.setState({cursorsShowing: !this.state.cursorsShowing})
|
||||||
|
this.props.cursorToggleClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleVideosShowing = () => {
|
||||||
|
this.setState({videosShowing: !this.state.videosShowing})
|
||||||
|
this.props.videoToggleClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = key => e => {
|
||||||
|
this.setState({
|
||||||
|
[key]: e.target.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTextareaKeyUp = e => {
|
||||||
|
if (e.which === 13) {
|
||||||
|
e.preventDefault()
|
||||||
|
const text = this.state.messageText
|
||||||
|
this.props.handleInputMessage(text)
|
||||||
|
this.setState({ messageText: '' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render = () => {
|
||||||
|
const rightOffset = this.state.open ? '0' : '-300px'
|
||||||
|
const { conversationLive, isParticipating, participants, messages, inviteACall, inviteToJoin } = this.props
|
||||||
|
const { videosShowing, cursorsShowing, alertSound, unreadMessages } = this.state
|
||||||
|
return (
|
||||||
|
<div className="chat-box"
|
||||||
|
style={{ right: rightOffset }}
|
||||||
|
>
|
||||||
|
<div className="junto-header">
|
||||||
|
PARTICIPANTS
|
||||||
|
<div onClick={this.toggleVideosShowing} className={`video-toggle ${videosShowing ? '' : 'active'}`} />
|
||||||
|
<div onClick={this.toggleCursorsShowing} className={`cursor-toggle ${cursorsShowing ? '' : 'active'}`} />
|
||||||
|
</div>
|
||||||
|
<div className="participants">
|
||||||
|
{conversationLive && <div className="conversation-live">
|
||||||
|
LIVE
|
||||||
|
{isParticipating && <span className="call-action leave" onClick={this.props.leaveCall}>
|
||||||
|
LEAVE
|
||||||
|
</span>}
|
||||||
|
{!isParticipating && <span className="call-action join" onClick={this.props.joinCall}>
|
||||||
|
JOIN
|
||||||
|
</span>}
|
||||||
|
</div>}
|
||||||
|
{participants.map(participant => <Participant
|
||||||
|
key={participant.id}
|
||||||
|
{...participant}
|
||||||
|
inviteACall={inviteACall}
|
||||||
|
inviteToJoin={inviteToJoin}
|
||||||
|
conversationLive={conversationLive}
|
||||||
|
mapperIsLive={isParticipating}/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="chat-header">
|
||||||
|
CHAT
|
||||||
|
<div onClick={this.toggleAlertSound} className={`sound-toggle ${alertSound ? '' : 'active'}`}></div>
|
||||||
|
</div>
|
||||||
|
<div className={`chat-button ${conversationLive ? 'active' : ''}`} onClick={this.toggleDrawer}>
|
||||||
|
<div className="tooltips">Chat</div>
|
||||||
|
<Unread count={unreadMessages} />
|
||||||
|
</div>
|
||||||
|
<div className="chat-messages" ref={div => this.messagesDiv = div}>
|
||||||
|
{messages.map(message => <Message key={message.id} {...message} />)}
|
||||||
|
</div>
|
||||||
|
<textarea className="chat-input"
|
||||||
|
ref={textarea => this.messageInput = textarea}
|
||||||
|
placeholder="Send a message..."
|
||||||
|
value={this.state.messageText}
|
||||||
|
onChange={this.handleChange('messageText')}
|
||||||
|
onKeyUp={this.handleTextareaKeyUp}
|
||||||
|
onFocus={this.props.inputFocus}
|
||||||
|
onBlur={this.props.inputBlur}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MapChat.propTypes = {
|
||||||
|
conversationLive: PropTypes.bool,
|
||||||
|
isParticipating: PropTypes.bool,
|
||||||
|
onOpen: PropTypes.func,
|
||||||
|
onClose: PropTypes.func,
|
||||||
|
leaveCall: PropTypes.func,
|
||||||
|
joinCall: PropTypes.func,
|
||||||
|
inviteACall: PropTypes.func,
|
||||||
|
inviteToJoin: PropTypes.func,
|
||||||
|
videoToggleClick: PropTypes.func,
|
||||||
|
cursorToggleClick: PropTypes.func,
|
||||||
|
soundToggleClick: PropTypes.func,
|
||||||
|
participants: PropTypes.arrayOf(PropTypes.shape({
|
||||||
|
color: PropTypes.string, // css color
|
||||||
|
id: PropTypes.number,
|
||||||
|
image: PropTypes.string, // image url
|
||||||
|
self: PropTypes.bool,
|
||||||
|
username: PropTypes.string,
|
||||||
|
isParticipating: PropTypes.bool,
|
||||||
|
isPending: PropTypes.bool
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MapChat
|
|
@ -16,3 +16,4 @@ junto(io, store)
|
||||||
map(io, store)
|
map(io, store)
|
||||||
|
|
||||||
io.listen(parseInt(process.env.NODE_REALTIME_PORT) || 5000)
|
io.listen(parseInt(process.env.NODE_REALTIME_PORT) || 5000)
|
||||||
|
console.log('booting up', process.env.NODE_REALTIME_PORT || 5000)
|
||||||
|
|
Loading…
Reference in a new issue