/* global $ */
import SimpleWebRTC from 'simplewebrtc'
import SocketIoConnection from 'simplewebrtc/socketioconnection'
import Active from '../Active'
import DataModel from '../DataModel'
import JIT from '../JIT'
import Util from '../Util'
import Views from '../Views'
import Visualize from '../Visualize'
import {
JUNTO_UPDATED,
INVITED_TO_CALL,
INVITED_TO_JOIN,
CALL_ACCEPTED,
CALL_DENIED,
INVITE_DENIED,
CALL_IN_PROGRESS,
CALL_STARTED,
MAPPER_LIST_UPDATED,
MAPPER_JOINED_CALL,
MAPPER_LEFT_CALL,
NEW_MAPPER,
LOST_MAPPER,
MESSAGE_CREATED,
TOPIC_DRAGGED,
TOPIC_CREATED,
TOPIC_UPDATED,
TOPIC_REMOVED,
TOPIC_DELETED,
SYNAPSE_CREATED,
SYNAPSE_UPDATED,
SYNAPSE_REMOVED,
SYNAPSE_DELETED,
PEER_COORDS_UPDATED,
MAP_UPDATED
} from './events'
import {
juntoUpdated,
invitedToCall,
invitedToJoin,
callAccepted,
callDenied,
inviteDenied,
callInProgress,
callStarted,
mapperListUpdated,
mapperJoinedCall,
mapperLeftCall,
peerCoordsUpdated,
newMapper,
lostMapper,
messageCreated,
topicDragged,
topicCreated,
topicUpdated,
topicRemoved,
topicDeleted,
synapseCreated,
synapseUpdated,
synapseRemoved,
synapseDeleted,
mapUpdated
} from './receivable'
import {
joinMap,
leaveMap,
checkForCall,
acceptCall,
denyCall,
denyInvite,
inviteToJoin,
inviteACall,
joinCall,
leaveCall,
sendCoords,
sendMapperInfo,
createMessage,
dragTopic,
createTopic,
updateTopic,
removeTopic,
deleteTopic,
createSynapse,
updateSynapse,
removeSynapse,
deleteSynapse,
updateMap
} from './sendable'
let Realtime = {
juntoState: { connectedPeople: {}, liveMaps: {} },
videoId: 'video-wrapper',
socket: null,
webrtc: null,
readyToCall: false,
mappersOnMap: {},
disconnected: false,
chatOpen: false,
soundId: null,
broadcastingStatus: false,
inConversation: false,
localVideo: null,
'junto_spinner_darkgrey.gif': '',
init: function(serverData) {
var self = Realtime
self.addJuntoListeners()
self.socket = new SocketIoConnection({ url: serverData['REALTIME_SERVER'] })
self['junto_spinner_darkgrey.gif'] = serverData['junto_spinner_darkgrey.gif']
self.socket.on('connect', function() {
console.log('connected')
subscribeToEvents(self, self.socket)
if (!self.disconnected) {
self.startActiveMap()
} else self.disconnected = false
})
self.socket.on('disconnect', function() {
self.disconnected = true
})
if (Active.Mapper) {
self.webrtc = new SimpleWebRTC({
connection: self.socket,
localVideoEl: self.videoId,
remoteVideosEl: '',
debug: true,
detectSpeakingEvents: false, // true,
autoAdjustMic: false, // true,
autoRequestMedia: false,
localVideo: {
autoplay: true,
mirror: true,
muted: true
},
media: {
video: true,
audio: true
},
nick: Active.Mapper.id
})
self.webrtc.webrtc.on('iceFailed', function(peer) {
console.log('local ice failure', peer)
// local ice failure
})
self.webrtc.webrtc.on('connectivityError', function(peer) {
console.log('remote ice failure', peer)
// remote ice failure
})
var $video = $('').attr('id', self.videoId)
self.localVideo = {
$video: $video,
view: new Views.VideoView($video[0], $('body'), 'me', true, {
DOUBLE_CLICK_TOLERANCE: 200,
avatar: Active.Mapper ? Active.Mapper.get('image') : ''
})
}
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,
config: { DOUBLE_CLICK_TOLERANCE: 200 }
})
self.room.videoAdded(self.handleVideoAdded)
if (!Active.Map) {
self.room.chat.$container.hide()
}
$('body').prepend(self.room.chat.$container)
} // if Active.Mapper
},
addJuntoListeners: function() {
var self = Realtime
$(document).on(Views.ChatView.events.openTray, function() {
$('.main').addClass('compressed')
self.chatOpen = true
self.positionPeerIcons()
})
$(document).on(Views.ChatView.events.closeTray, function() {
$('.main').removeClass('compressed')
self.chatOpen = false
self.positionPeerIcons()
})
$(document).on(Views.ChatView.events.videosOn, function() {
$('#wrapper').removeClass('hideVideos')
})
$(document).on(Views.ChatView.events.videosOff, function() {
$('#wrapper').addClass('hideVideos')
})
$(document).on(Views.ChatView.events.cursorsOn, function() {
$('#wrapper').removeClass('hideCursors')
})
$(document).on(Views.ChatView.events.cursorsOff, function() {
$('#wrapper').addClass('hideCursors')
})
},
startActiveMap: function() {
var self = Realtime
if (Active.Map && Active.Mapper) {
if (Active.Map.authorizeToEdit(Active.Mapper)) {
self.turnOn()
self.setupSocket()
self.setupLocalSendables()
}
self.room.addMessages(new DataModel.MessageCollection(DataModel.Messages), true)
}
},
endActiveMap: function() {
var self = Realtime
$(document).off('.map')
// leave the appropriate rooms to leave
if (self.inConversation) self.leaveCall()
self.leaveMap()
$('.collabCompass').remove()
if (self.room) {
self.room.leave()
self.room.chat.$container.hide()
self.room.chat.close()
}
},
turnOn: function(notify) {
var self = Realtime
$('.collabCompass').show()
self.room.chat.$container.show()
self.room.room = 'map-' + Active.Map.id
self.activeMapper = {
id: Active.Mapper.id,
name: Active.Mapper.get('name'),
username: Active.Mapper.get('name'),
image: Active.Mapper.get('image'),
color: Util.getPastelColor(),
self: true
}
self.localVideo.view.$container.find('.video-cutoff').css({
border: '4px solid ' + self.activeMapper.color
})
self.room.chat.addParticipant(self.activeMapper)
},
setupSocket: function() {
var self = Realtime
self.checkForCall()
self.joinMap()
},
setupLocalSendables: function() {
var self = Realtime
// local event listeners that trigger events
var sendCoords = function(event) {
var pixels = {
x: event.pageX,
y: event.pageY
}
var coords = Util.pixelsToCoords(Visualize.mGraph, pixels)
self.sendCoords(coords)
}
$(document).on('mousemove.map', sendCoords)
var zoom = function(event, e) {
if (e) {
var pixels = {
x: e.pageX,
y: e.pageY
}
var coords = Util.pixelsToCoords(Visualize.mGraph, pixels)
self.sendCoords(coords)
}
self.positionPeerIcons()
}
$(document).on(JIT.events.zoom + '.map', zoom)
$(document).on(JIT.events.pan + '.map', self.positionPeerIcons)
var dragTopic = function(event, positions) {
self.dragTopic(positions)
}
$(document).on(JIT.events.topicDrag + '.map', dragTopic)
var createTopic = function(event, data) {
self.createTopic(data)
}
$(document).on(JIT.events.newTopic + '.map', createTopic)
var deleteTopic = function(event, data) {
self.deleteTopic(data)
}
$(document).on(JIT.events.deleteTopic + '.map', deleteTopic)
var removeTopic = function(event, data) {
self.removeTopic(data)
}
$(document).on(JIT.events.removeTopic + '.map', removeTopic)
var createSynapse = function(event, data) {
self.createSynapse(data)
}
$(document).on(JIT.events.newSynapse + '.map', createSynapse)
var deleteSynapse = function(event, data) {
self.deleteSynapse(data)
}
$(document).on(JIT.events.deleteSynapse + '.map', deleteSynapse)
var removeSynapse = function(event, data) {
self.removeSynapse(data)
}
$(document).on(JIT.events.removeSynapse + '.map', removeSynapse)
var createMessage = function(event, data) {
self.createMessage(data)
}
$(document).on(Views.Room.events.newMessage + '.map', createMessage)
},
countOthersInConversation: function() {
var self = Realtime
var count = 0
for (var key in self.mappersOnMap) {
if (self.mappersOnMap[key].inConversation) count++
}
return count
},
handleVideoAdded: function(v, id) {
var self = Realtime
self.positionVideos()
v.setParent($('#wrapper'))
v.$container.find('.video-cutoff').css({
border: '4px solid ' + self.mappersOnMap[id].color
})
$('#wrapper').append(v.$container)
},
positionVideos: function() {
var self = Realtime
var videoIds = Object.keys(self.room.videos)
// var numOfVideos = videoIds.length
// var numOfVideosToPosition = _.filter(videoIds, function(id) {
// return !self.room.videos[id].manuallyPositioned
// }).length
var screenHeight = $(document).height()
var topExtraPadding = 20
var topPadding = 30
var leftPadding = 30
var videoHeight = 150
var videoWidth = 180
var column = 0
var row = 0
var yFormula = function() {
var y = topExtraPadding + (topPadding + videoHeight) * row + topPadding
if (y + videoHeight > screenHeight) {
row = 0
column += 1
y = yFormula()
}
row++
return y
}
var xFormula = function() {
var x = (leftPadding + videoWidth) * column + leftPadding
return x
}
// do self first
var myVideo = Realtime.localVideo.view
if (!myVideo.manuallyPositioned) {
myVideo.$container.css({
top: yFormula() + 'px',
left: xFormula() + 'px'
})
}
videoIds.forEach(function(id) {
var video = self.room.videos[id]
if (!video.manuallyPositioned) {
video.$container.css({
top: yFormula() + 'px',
left: xFormula() + 'px'
})
}
})
},
callEnded: function() {
var self = Realtime
self.room.conversationEnding()
self.room.leaveVideoOnly()
self.inConversation = false
self.localVideo.view.$container.hide().css({
top: '72px',
left: '30px'
})
self.localVideo.view.audioOn()
self.localVideo.view.videoOn()
},
createCompass: function(name, id, image, color) {
var str = '
' + name + '
' str += '' $('#compass' + id).remove() $('', { id: 'compass' + id, class: 'collabCompass' }).html(str).appendTo('#wrapper') $('#compass' + id + ' img').css({ 'border': '2px solid ' + color }) $('#compass' + id + ' p').css({ 'background-color': color }) }, positionPeerIcons: function() { var self = Realtime for (var key in self.mappersOnMap) { self.positionPeerIcon(key) } }, positionPeerIcon: function(id) { var self = Realtime var mapper = self.mappersOnMap[id] var origPixels = Util.coordsToPixels(Visualize.mGraph, mapper.coords) var pixels = self.limitPixelsToScreen(origPixels) $('#compass' + id).css({ left: pixels.x + 'px', top: pixels.y + 'px' }) /* showing the arrow if the collaborator is off of the viewport screen */ if (origPixels.x !== pixels.x || origPixels.y !== pixels.y) { var dy = origPixels.y - pixels.y // opposite var dx = origPixels.x - pixels.x // adjacent var angle = Math.atan2(dy, dx) $('#compassArrow' + id).show().css({ transform: 'rotate(' + angle + 'rad)', '-webkit-transform': 'rotate(' + angle + 'rad)' }) if (dx > 0) { $('#compass' + id).addClass('labelLeft') } } else { $('#compassArrow' + id).hide() $('#compass' + id).removeClass('labelLeft') } }, limitPixelsToScreen: function(pixels) { var self = Realtime var boundary = self.chatOpen ? '#wrapper' : document var xLimit, yLimit var xMax = $(boundary).width() var yMax = $(boundary).height() var compassDiameter = 56 var compassArrowSize = 24 xLimit = Math.max(0 + compassArrowSize, pixels.x) xLimit = Math.min(xLimit, xMax - compassDiameter) yLimit = Math.max(0 + compassArrowSize, pixels.y) yLimit = Math.min(yLimit, yMax - compassDiameter) return {x: xLimit, y: yLimit} } } const sendables = [ ['joinMap', joinMap], ['leaveMap', leaveMap], ['checkForCall', checkForCall], ['acceptCall', acceptCall], ['denyCall', denyCall], ['denyInvite', denyInvite], ['inviteToJoin', inviteToJoin], ['inviteACall', inviteACall], ['joinCall', joinCall], ['leaveCall', leaveCall], ['sendMapperInfo', sendMapperInfo], ['sendCoords', sendCoords], ['createMessage', createMessage], ['dragTopic', dragTopic], ['createTopic', createTopic], ['updateTopic', updateTopic], ['removeTopic', removeTopic], ['deleteTopic', deleteTopic], ['createSynapse', createSynapse], ['updateSynapse', updateSynapse], ['removeSynapse', removeSynapse], ['deleteSynapse', deleteSynapse], ['updateMap', updateMap] ] sendables.forEach(sendable => { Realtime[sendable[0]] = sendable[1](Realtime) }) const subscribeToEvents = (Realtime, socket) => { socket.on(JUNTO_UPDATED, juntoUpdated(Realtime)) socket.on(INVITED_TO_CALL, invitedToCall(Realtime)) socket.on(INVITED_TO_JOIN, invitedToJoin(Realtime)) socket.on(CALL_ACCEPTED, callAccepted(Realtime)) socket.on(CALL_DENIED, callDenied(Realtime)) socket.on(INVITE_DENIED, inviteDenied(Realtime)) socket.on(CALL_IN_PROGRESS, callInProgress(Realtime)) socket.on(CALL_STARTED, callStarted(Realtime)) socket.on(MAPPER_LIST_UPDATED, mapperListUpdated(Realtime)) socket.on(MAPPER_JOINED_CALL, mapperJoinedCall(Realtime)) socket.on(MAPPER_LEFT_CALL, mapperLeftCall(Realtime)) socket.on(PEER_COORDS_UPDATED, peerCoordsUpdated(Realtime)) socket.on(NEW_MAPPER, newMapper(Realtime)) socket.on(LOST_MAPPER, lostMapper(Realtime)) socket.on(MESSAGE_CREATED, messageCreated(Realtime)) socket.on(TOPIC_DRAGGED, topicDragged(Realtime)) socket.on(TOPIC_CREATED, topicCreated(Realtime)) socket.on(TOPIC_UPDATED, topicUpdated(Realtime)) socket.on(TOPIC_REMOVED, topicRemoved(Realtime)) socket.on(TOPIC_DELETED, topicDeleted(Realtime)) socket.on(SYNAPSE_CREATED, synapseCreated(Realtime)) socket.on(SYNAPSE_UPDATED, synapseUpdated(Realtime)) socket.on(SYNAPSE_REMOVED, synapseRemoved(Realtime)) socket.on(SYNAPSE_DELETED, synapseDeleted(Realtime)) socket.on(MAP_UPDATED, mapUpdated(Realtime)) } export default Realtime