diff --git a/frontend/src/Metamaps/Backbone/index.js b/frontend/src/Metamaps/Backbone/index.js
index 389d7dcf..a3d5b77e 100644
--- a/frontend/src/Metamaps/Backbone/index.js
+++ b/frontend/src/Metamaps/Backbone/index.js
@@ -63,7 +63,7 @@ _Backbone.Map = Backbone.Model.extend({
this.on('saved', this.savedEvent)
},
savedEvent: function () {
- Realtime.sendMapChange(this)
+ Realtime.updateMap(this)
},
authorizeToEdit: function (mapper) {
if (mapper && (
@@ -370,7 +370,7 @@ _Backbone.init = function () {
return node
},
savedEvent: function () {
- Realtime.sendTopicChange(this)
+ Realtime.updateTopic(this)
},
updateViews: function () {
var onPageWithTopicCard = Active.Map || Active.Topic
@@ -549,7 +549,7 @@ _Backbone.init = function () {
return edge
},
savedEvent: function () {
- Realtime.sendSynapseChange(this)
+ Realtime.updateSynapse(this)
},
updateViews: function () {
this.updateCardView()
diff --git a/frontend/src/Metamaps/Realtime.js b/frontend/src/Metamaps/Realtime.js
deleted file mode 100644
index fe79873c..00000000
--- a/frontend/src/Metamaps/Realtime.js
+++ /dev/null
@@ -1,1139 +0,0 @@
-/* global Metamaps, $, SocketIoConnection */
-
-import _ from 'lodash'
-import SimpleWebRTC from 'simplewebrtc'
-
-import Active from './Active'
-import Control from './Control'
-import GlobalUI from './GlobalUI'
-import JIT from './JIT'
-import Map from './Map'
-import Mapper from './Mapper'
-import Synapse from './Synapse'
-import Topic from './Topic'
-import Util from './Util'
-import Views from './Views'
-import Visualize from './Visualize'
-
-/*
- * Metamaps.Realtime.js
- *
- * Dependencies:
- * - Metamaps.Backbone
- * - Metamaps.Erb
- * - Metamaps.Mappers
- * - Metamaps.Mappings
- * - Metamaps.Messages
- * - Metamaps.Synapses
- * - Metamaps.Topics
- */
-
-const Realtime = {
- videoId: 'video-wrapper',
- socket: null,
- webrtc: null,
- readyToCall: false,
- mappersOnMap: {},
- disconnected: false,
- chatOpen: false,
- soundId: null,
- broadcastingStatus: false,
- inConversation: false,
- localVideo: null,
- init: function () {
- var self = Realtime
-
- self.addJuntoListeners()
-
- self.socket = new SocketIoConnection({ url: Metamaps.Erb['REALTIME_SERVER']})
- self.socket.on('connect', function () {
- console.log('connected')
- if (!self.disconnected) {
- self.startActiveMap()
- self.subscribeToLiveMaps()
- } 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: '',
- detectSpeakingEvents: 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
- },
- subscribeToLiveMaps: function () {
- var self = Metamaps.Realtime
- // Handles livemaps array on the UI
- var liveMaps = []
- self.socket.emit('requestLiveMaps')
- self.socket.on('receiveLiveMaps', function (maps) {
- console.log(maps)
- liveMaps.push(maps)
- })
- self.socket.on('map_went_live', function (map) {
- liveMaps.push(map)
- })
- self.socket.on('map_no_longer_live', function (data) {
- // remove from liveMaps
- })
- },
- 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')
- })
- },
- 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 screenWidth = $(document).width()
- 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'
- })
- }
- })
- },
- startActiveMap: function () {
- var self = Realtime
-
- if (Active.Map && Active.Mapper) {
- if (Active.Map.authorizeToEdit(Active.Mapper)) {
- self.turnOn()
- self.setupSocket()
- } else {
- self.attachMapListener()
- }
- self.room.addMessages(new Metamaps.Backbone.MessageCollection(Metamaps.Messages), true)
- }
- },
- endActiveMap: function () {
- var self = Realtime
-
- $(document).off('.map')
- self.socket.removeAllListeners()
- if (self.inConversation) self.leaveCall()
- self.socket.emit('endMapperNotify')
- $('.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.checkForACallToJoin()
-
- 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)
- },
- checkForACallToJoin: function () {
- var self = Realtime
- self.socket.emit('checkForCall', { room: self.room.room, mapid: Active.Map.id })
- },
- promptToJoin: function () {
- var self = Realtime
-
- var notifyText = "There's a conversation happening, want to join?"
- notifyText += ' '
- notifyText += ' '
- GlobalUI.notifyUser(notifyText, true)
- self.room.conversationInProgress()
- },
- conversationHasBegun: function () {
- var self = Realtime
-
- if (self.inConversation) return
- var notifyText = "There's a conversation starting, want to join?"
- notifyText += ' '
- notifyText += ' '
- GlobalUI.notifyUser(notifyText, true)
- self.room.conversationInProgress()
- },
- countOthersInConversation: function () {
- var self = Realtime
- var count = 0
-
- for (var key in self.mappersOnMap) {
- if (self.mappersOnMap[key].inConversation) count++
- }
- return count
- },
- mapperJoinedCall: function (id) {
- var self = Realtime
- 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)
- }
- },
- mapperLeftCall: function (id) {
- var self = Realtime
- var mapper = self.mappersOnMap[id]
-
- if (mapper) {
- if (self.inConversation) {
- var username = mapper.name
- var notifyText = username + ' left the call'
- GlobalUI.notifyUser(notifyText)
- }
-
- mapper.inConversation = false
- self.room.chat.mapperLeftCall(id)
-
- if ((self.inConversation && self.countOthersInConversation() === 0) ||
- (!self.inConversation && self.countOthersInConversation() === 1)) {
- self.callEnded()
- }
- }
- },
- 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()
- self.webrtc.webrtc.localStreams.forEach(function (stream) {
- stream.getTracks().forEach(function (track) {
- track.stop()
- })
- })
- self.webrtc.webrtc.localStreams = []
- },
- invitedToCall: function (inviter) {
- var self = Realtime
-
- self.room.chat.sound.stop(self.soundId)
- self.soundId = self.room.chat.sound.play('sessioninvite')
-
- var username = self.mappersOnMap[inviter].name
- var notifyText = ''
- notifyText += username + ' is inviting you to a conversation. Join live?'
- notifyText += ' '
- notifyText += ' '
- GlobalUI.notifyUser(notifyText, true)
- },
- invitedToJoin: function (inviter) {
- var self = Realtime
-
- self.room.chat.sound.stop(self.soundId)
- self.soundId = self.room.chat.sound.play('sessioninvite')
-
- var username = self.mappersOnMap[inviter].name
- var notifyText = username + ' is inviting you to the conversation. Join?'
- notifyText += ' '
- notifyText += ' '
- GlobalUI.notifyUser(notifyText, true)
- },
- acceptCall: function (userid) {
- var self = Realtime
- self.room.chat.sound.stop(self.soundId)
- self.socket.emit('callAccepted', {
- mapid: Active.Map.id,
- invited: Active.Mapper.id,
- inviter: userid
- })
- $.post('/maps/' + Active.Map.id + '/events/conversation')
- self.joinCall()
- GlobalUI.clearNotify()
- },
- denyCall: function (userid) {
- var self = Realtime
- self.room.chat.sound.stop(self.soundId)
- self.socket.emit('callDenied', {
- mapid: Active.Map.id,
- invited: Active.Mapper.id,
- inviter: userid
- })
- GlobalUI.clearNotify()
- },
- denyInvite: function (userid) {
- var self = Realtime
- self.room.chat.sound.stop(self.soundId)
- self.socket.emit('inviteDenied', {
- mapid: Active.Map.id,
- invited: Active.Mapper.id,
- inviter: userid
- })
- GlobalUI.clearNotify()
- },
- inviteACall: function (userid) {
- var self = Realtime
- self.socket.emit('inviteACall', {
- mapid: Active.Map.id,
- inviter: Active.Mapper.id,
- invited: userid
- })
- self.room.chat.invitationPending(userid)
- GlobalUI.clearNotify()
- },
- inviteToJoin: function (userid) {
- var self = Realtime
- self.socket.emit('inviteToJoin', {
- mapid: Active.Map.id,
- inviter: Active.Mapper.id,
- invited: userid
- })
- self.room.chat.invitationPending(userid)
- },
- callAccepted: function (userid) {
- var self = Realtime
-
- var username = self.mappersOnMap[userid].name
- GlobalUI.notifyUser('Conversation starting...')
- self.joinCall()
- self.room.chat.invitationAnswered(userid)
- },
- callDenied: function (userid) {
- var self = Realtime
-
- var username = self.mappersOnMap[userid].name
- GlobalUI.notifyUser(username + " didn't accept your invitation")
- self.room.chat.invitationAnswered(userid)
- },
- inviteDenied: function (userid) {
- var self = Realtime
-
- var username = self.mappersOnMap[userid].name
- GlobalUI.notifyUser(username + " didn't accept your invitation")
- self.room.chat.invitationAnswered(userid)
- },
- joinCall: function () {
- var self = Realtime
-
- self.webrtc.off('readyToCall')
- self.webrtc.once('readyToCall', function () {
- self.videoInitialized = true
- self.readyToCall = true
- self.localVideo.view.manuallyPositioned = false
- self.positionVideos()
- self.localVideo.view.$container.show()
- if (self.localVideo) {
- $('#wrapper').append(self.localVideo.view.$container)
- }
- self.room.join()
- })
- self.inConversation = true
- self.socket.emit('mapperJoinedCall', {
- mapid: Active.Map.id,
- id: Active.Mapper.id
- })
- self.webrtc.startLocalVideo()
- GlobalUI.clearNotify()
- self.room.chat.mapperJoinedCall(Active.Mapper.id)
- },
- leaveCall: function () {
- var self = Realtime
-
- self.socket.emit('mapperLeftCall', {
- mapid: Active.Map.id,
- id: Active.Mapper.id
- })
-
- self.room.chat.mapperLeftCall(Active.Mapper.id)
- self.room.leaveVideoOnly()
- self.inConversation = false
- self.localVideo.view.$container.hide()
-
- // if there's only two people in the room, and we're leaving
- // we should shut down the call locally
- if (self.countOthersInConversation() === 1) {
- self.callEnded()
- }
- },
- setupSocket: function () {
- var self = Realtime
- var socket = Realtime.socket
- var myId = Active.Mapper.id
-
- socket.emit('newMapperNotify', {
- userid: myId,
- username: Active.Mapper.get('name'),
- userimage: Active.Mapper.get('image'),
- mapid: Active.Map.id,
- map: Active.Map.attributes
- })
-
- socket.on(myId + '-' + Active.Map.id + '-invitedToCall', self.invitedToCall) // new call
- socket.on(myId + '-' + Active.Map.id + '-invitedToJoin', self.invitedToJoin) // call already in progress
- socket.on(myId + '-' + Active.Map.id + '-callAccepted', self.callAccepted)
- socket.on(myId + '-' + Active.Map.id + '-callDenied', self.callDenied)
- socket.on(myId + '-' + Active.Map.id + '-inviteDenied', self.inviteDenied)
-
- // receive word that there's a conversation in progress
- socket.on('maps-' + Active.Map.id + '-callInProgress', self.promptToJoin)
- socket.on('maps-' + Active.Map.id + '-callStarting', self.conversationHasBegun)
-
- socket.on('maps-' + Active.Map.id + '-mapperJoinedCall', self.mapperJoinedCall)
- socket.on('maps-' + Active.Map.id + '-mapperLeftCall', self.mapperLeftCall)
-
- // if you're the 'new guy' update your list with who's already online
- socket.on(myId + '-' + Active.Map.id + '-UpdateMapperList', self.updateMapperList)
-
- // receive word that there's a new mapper on the map
- socket.on('maps-' + Active.Map.id + '-newmapper', self.newPeerOnMap)
-
- // receive word that a mapper left the map
- socket.on('maps-' + Active.Map.id + '-lostmapper', self.lostPeerOnMap)
-
- //
- socket.on('maps-' + Active.Map.id + '-topicDrag', self.topicDrag)
-
- //
- socket.on('maps-' + Active.Map.id + '-newTopic', self.newTopic)
-
- //
- socket.on('maps-' + Active.Map.id + '-newMessage', self.newMessage)
-
- //
- socket.on('maps-' + Active.Map.id + '-removeTopic', self.removeTopic)
-
- //
- socket.on('maps-' + Active.Map.id + '-newSynapse', self.newSynapse)
-
- //
- socket.on('maps-' + Active.Map.id + '-removeSynapse', self.removeSynapse)
-
- // update mapper compass position
- socket.on('maps-' + Active.Map.id + '-updatePeerCoords', self.updatePeerCoords)
-
- // deletions
- socket.on('deleteTopicFromServer', self.removeTopic)
- socket.on('deleteSynapseFromServer', self.removeSynapse)
-
- socket.on('topicChangeFromServer', self.topicChange)
- socket.on('synapseChangeFromServer', self.synapseChange)
- self.attachMapListener()
-
- // local event listeners that trigger events
- var sendCoords = function (event) {
- var pixels = {
- x: event.pageX,
- y: event.pageY
- }
- var coords = Util.pixelsToCoords(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(pixels)
- self.sendCoords(coords)
- }
- self.positionPeerIcons()
- }
- $(document).on(JIT.events.zoom + '.map', zoom)
-
- $(document).on(JIT.events.pan + '.map', self.positionPeerIcons)
-
- var sendTopicDrag = function (event, positions) {
- self.sendTopicDrag(positions)
- }
- $(document).on(JIT.events.topicDrag + '.map', sendTopicDrag)
-
- var sendNewTopic = function (event, data) {
- self.sendNewTopic(data)
- }
- $(document).on(JIT.events.newTopic + '.map', sendNewTopic)
-
- var sendDeleteTopic = function (event, data) {
- self.sendDeleteTopic(data)
- }
- $(document).on(JIT.events.deleteTopic + '.map', sendDeleteTopic)
-
- var sendRemoveTopic = function (event, data) {
- self.sendRemoveTopic(data)
- }
- $(document).on(JIT.events.removeTopic + '.map', sendRemoveTopic)
-
- var sendNewSynapse = function (event, data) {
- self.sendNewSynapse(data)
- }
- $(document).on(JIT.events.newSynapse + '.map', sendNewSynapse)
-
- var sendDeleteSynapse = function (event, data) {
- self.sendDeleteSynapse(data)
- }
- $(document).on(JIT.events.deleteSynapse + '.map', sendDeleteSynapse)
-
- var sendRemoveSynapse = function (event, data) {
- self.sendRemoveSynapse(data)
- }
- $(document).on(JIT.events.removeSynapse + '.map', sendRemoveSynapse)
-
- var sendNewMessage = function (event, data) {
- self.sendNewMessage(data)
- }
- $(document).on(Views.Room.events.newMessage + '.map', sendNewMessage)
- },
- attachMapListener: function () {
- var self = Realtime
- var socket = Realtime.socket
-
- socket.on('mapChangeFromServer', self.mapChange)
- },
- updateMapperList: function (data) {
- var self = Realtime
- var socket = Realtime.socket
-
- // data.userid
- // data.username
- // data.userimage
-
- self.mappersOnMap[data.userid] = {
- id: data.userid,
- name: data.username,
- username: data.username,
- image: data.userimage,
- color: Util.getPastelColor(),
- inConversation: data.userinconversation,
- coords: {
- x: 0,
- y: 0
- }
- }
-
- if (data.userid !== Active.Mapper.id) {
- self.room.chat.addParticipant(self.mappersOnMap[data.userid])
- if (data.userinconversation) self.room.chat.mapperJoinedCall(data.userid)
-
- // create a div for the collaborators compass
- self.createCompass(data.username, data.userid, data.userimage, self.mappersOnMap[data.userid].color)
- }
- },
- newPeerOnMap: function (data) {
- var self = Realtime
- var socket = Realtime.socket
-
- // data.userid
- // data.username
- // data.userimage
- // data.coords
- var firstOtherPerson = Object.keys(self.mappersOnMap).length === 0
-
- self.mappersOnMap[data.userid] = {
- id: data.userid,
- name: data.username,
- username: data.username,
- image: data.userimage,
- color: Util.getPastelColor(),
- realtime: true,
- coords: {
- x: 0,
- y: 0
- },
- }
-
- // 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])
-
- // create a div for the collaborators compass
- self.createCompass(data.username, data.userid, data.userimage, self.mappersOnMap[data.userid].color)
-
- var notifyMessage = data.username + ' just joined the map'
- if (firstOtherPerson) {
- notifyMessage += ' '
- }
- GlobalUI.notifyUser(notifyMessage)
-
- // send this new mapper back your details, and the awareness that you've loaded the map
- var update = {
- userToNotify: data.userid,
- username: Active.Mapper.get('name'),
- userimage: Active.Mapper.get('image'),
- userid: Active.Mapper.id,
- userinconversation: self.inConversation,
- mapid: Active.Map.id
- }
- socket.emit('updateNewMapperList', update)
- }
- },
- 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 - }) - }, - lostPeerOnMap: function (data) { - var self = Realtime - var socket = Realtime.socket - - // data.userid - // data.username - - delete self.mappersOnMap[data.userid] - self.room.chat.sound.play('leavemap') - // $('#mapper' + data.userid).remove() - $('#compass' + data.userid).remove() - self.room.chat.removeParticipant(data.username) - - GlobalUI.notifyUser(data.username + ' just left the map') - - if ((self.inConversation && self.countOthersInConversation() === 0) || - (!self.inConversation && self.countOthersInConversation() === 1)) { - self.callEnded() - } - }, - updatePeerCoords: function (data) { - var self = Realtime - var socket = Realtime.socket - if (!self.mappersOnMap[data.userid]) return - - self.mappersOnMap[data.userid].coords = {x: data.usercoords.x,y: data.usercoords.y} - self.positionPeerIcon(data.userid) - }, - positionPeerIcons: function () { - var self = Realtime - var socket = Realtime.socket - - for (var key in self.mappersOnMap) { - self.positionPeerIcon(key) - } - }, - positionPeerIcon: function (id) { - var self = Realtime - var socket = Realtime.socket - - var boundary = self.chatOpen ? '#wrapper' : document - var mapper = self.mappersOnMap[id] - var xMax = $(boundary).width() - var yMax = $(boundary).height() - var compassDiameter = 56 - var compassArrowSize = 24 - - var origPixels = Util.coordsToPixels(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 ratio = dy / dx - 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 socket = Realtime.socket - - 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} - }, - sendCoords: function (coords) { - var self = Realtime - var socket = Realtime.socket - - var map = Active.Map - var mapper = Active.Mapper - - if (map.authorizeToEdit(mapper) && socket) { - var update = { - usercoords: coords, - userid: Active.Mapper.id, - mapid: Active.Map.id - } - socket.emit('updateMapperCoords', update) - } - }, - sendTopicDrag: function (positions) { - var self = Realtime - var socket = self.socket - - if (Active.Map) { - positions.mapid = Active.Map.id - socket.emit('topicDrag', positions) - } - }, - topicDrag: function (positions) { - var self = Realtime - var socket = self.socket - - var topic - var node - - if (Active.Map) { - for (var key in positions) { - topic = Metamaps.Topics.get(key) - if (topic) node = topic.get('node') - if (node) node.pos.setc(positions[key].x, positions[key].y) - } // for - Visualize.mGraph.plot() - } - }, - sendTopicChange: function (topic) { - var self = Realtime - var socket = self.socket - - var data = { - topicId: topic.id - } - - socket.emit('topicChangeFromClient', data) - }, - topicChange: function (data) { - var topic = Metamaps.Topics.get(data.topicId) - if (topic) { - var node = topic.get('node') - topic.fetch({ - success: function (model) { - model.set({ node: node }) - model.trigger('changeByOther') - } - }) - } - }, - sendSynapseChange: function (synapse) { - var self = Realtime - var socket = self.socket - - var data = { - synapseId: synapse.id - } - - socket.emit('synapseChangeFromClient', data) - }, - synapseChange: function (data) { - var synapse = Metamaps.Synapses.get(data.synapseId) - if (synapse) { - // edge reset necessary because fetch causes model reset - var edge = synapse.get('edge') - synapse.fetch({ - success: function (model) { - model.set({ edge: edge }) - model.trigger('changeByOther') - } - }) - } - }, - sendMapChange: function (map) { - var self = Realtime - var socket = self.socket - - var data = { - mapId: map.id - } - - socket.emit('mapChangeFromClient', data) - }, - mapChange: function (data) { - var map = Active.Map - var isActiveMap = map && data.mapId === map.id - if (isActiveMap) { - var couldEditBefore = map.authorizeToEdit(Active.Mapper) - var idBefore = map.id - map.fetch({ - success: function (model, response) { - var idNow = model.id - var canEditNow = model.authorizeToEdit(Active.Mapper) - if (idNow !== idBefore) { - Map.leavePrivateMap() // this means the map has been changed to private - } - else if (couldEditBefore && !canEditNow) { - Map.cantEditNow() - } - else if (!couldEditBefore && canEditNow) { - Map.canEditNow() - } else { - model.trigger('changeByOther') - } - } - }) - } - }, - // newMessage - sendNewMessage: function (data) { - var self = Realtime - var socket = self.socket - - var message = data.attributes - message.mapid = Active.Map.id - socket.emit('newMessage', message) - }, - newMessage: function (data) { - var self = Realtime - var socket = self.socket - - self.room.addMessages(new Metamaps.Backbone.MessageCollection(data)) - }, - // newTopic - sendNewTopic: function (data) { - var self = Realtime - var socket = self.socket - - if (Active.Map) { - data.mapperid = Active.Mapper.id - data.mapid = Active.Map.id - socket.emit('newTopic', data) - } - }, - newTopic: function (data) { - var topic, mapping, mapper, cancel - - var self = Realtime - var socket = self.socket - - function waitThenRenderTopic () { - if (topic && mapping && mapper) { - Topic.renderTopic(mapping, topic, false, false) - } - else if (!cancel) { - setTimeout(waitThenRenderTopic, 10) - } - } - - mapper = Metamaps.Mappers.get(data.mapperid) - if (mapper === undefined) { - Mapper.get(data.mapperid, function(m) { - Metamaps.Mappers.add(m) - mapper = m - }) - } - $.ajax({ - url: '/topics/' + data.mappableid + '.json', - success: function (response) { - Metamaps.Topics.add(response) - topic = Metamaps.Topics.get(response.id) - }, - error: function () { - cancel = true - } - }) - $.ajax({ - url: '/mappings/' + data.mappingid + '.json', - success: function (response) { - Metamaps.Mappings.add(response) - mapping = Metamaps.Mappings.get(response.id) - }, - error: function () { - cancel = true - } - }) - - waitThenRenderTopic() - }, - // removeTopic - sendDeleteTopic: function (data) { - var self = Realtime - var socket = self.socket - - if (Active.Map) { - socket.emit('deleteTopicFromClient', data) - } - }, - // removeTopic - sendRemoveTopic: function (data) { - var self = Realtime - var socket = self.socket - - if (Active.Map) { - data.mapid = Active.Map.id - socket.emit('removeTopic', data) - } - }, - removeTopic: function (data) { - var self = Realtime - var socket = self.socket - - var topic = Metamaps.Topics.get(data.mappableid) - if (topic) { - var node = topic.get('node') - var mapping = topic.getMapping() - Control.hideNode(node.id) - Metamaps.Topics.remove(topic) - Metamaps.Mappings.remove(mapping) - } - }, - // newSynapse - sendNewSynapse: function (data) { - var self = Realtime - var socket = self.socket - - if (Active.Map) { - data.mapperid = Active.Mapper.id - data.mapid = Active.Map.id - socket.emit('newSynapse', data) - } - }, - newSynapse: function (data) { - var topic1, topic2, node1, node2, synapse, mapping, cancel, mapper - - var self = Realtime - var socket = self.socket - - function waitThenRenderSynapse () { - if (synapse && mapping && mapper) { - topic1 = synapse.getTopic1() - node1 = topic1.get('node') - topic2 = synapse.getTopic2() - node2 = topic2.get('node') - - Synapse.renderSynapse(mapping, synapse, node1, node2, false) - } - else if (!cancel) { - setTimeout(waitThenRenderSynapse, 10) - } - } - - mapper = Metamaps.Mappers.get(data.mapperid) - if (mapper === undefined) { - Mapper.get(data.mapperid, function(m) { - Metamaps.Mappers.add(m) - mapper = m - }) - } - $.ajax({ - url: '/synapses/' + data.mappableid + '.json', - success: function (response) { - Metamaps.Synapses.add(response) - synapse = Metamaps.Synapses.get(response.id) - }, - error: function () { - cancel = true - } - }) - $.ajax({ - url: '/mappings/' + data.mappingid + '.json', - success: function (response) { - Metamaps.Mappings.add(response) - mapping = Metamaps.Mappings.get(response.id) - }, - error: function () { - cancel = true - } - }) - waitThenRenderSynapse() - }, - // deleteSynapse - sendDeleteSynapse: function (data) { - var self = Realtime - var socket = self.socket - - if (Active.Map) { - data.mapid = Active.Map.id - socket.emit('deleteSynapseFromClient', data) - } - }, - // removeSynapse - sendRemoveSynapse: function (data) { - var self = Realtime - var socket = self.socket - - if (Active.Map) { - data.mapid = Active.Map.id - socket.emit('removeSynapse', data) - } - }, - removeSynapse: function (data) { - var self = Realtime - var socket = self.socket - - var synapse = Metamaps.Synapses.get(data.mappableid) - if (synapse) { - var edge = synapse.get('edge') - var mapping = synapse.getMapping() - if (edge.getData('mappings').length - 1 === 0) { - Control.hideEdge(edge) - } - - var index = _.indexOf(edge.getData('synapses'), synapse) - edge.getData('mappings').splice(index, 1) - edge.getData('synapses').splice(index, 1) - if (edge.getData('displayIndex')) { - delete edge.data.$displayIndex - } - Metamaps.Synapses.remove(synapse) - Metamaps.Mappings.remove(mapping) - } - }, -} - -export default Realtime diff --git a/frontend/src/Metamaps/Realtime/events.js b/frontend/src/Metamaps/Realtime/events.js new file mode 100644 index 00000000..20265154 --- /dev/null +++ b/frontend/src/Metamaps/Realtime/events.js @@ -0,0 +1,54 @@ +/* EVENTS SENDABLE */ +export const REQUEST_LIVE_MAPS = 'REQUEST_LIVE_MAPS' +export const JOIN_MAP = 'JOIN_MAP' +export const LEAVE_MAP = 'LEAVE_MAP' +export const CHECK_FOR_CALL = 'CHECK_FOR_CALL' +export const ACCEPT_CALL = 'ACCEPT_CALL' +export const DENY_CALL = 'DENY_CALL' +export const DENY_INVITE = 'DENY_INVITE' +export const INVITE_TO_JOIN = 'INVITE_TO_JOIN' +export const INVITE_A_CALL = 'INVITE_A_CALL' +export const JOIN_CALL = 'JOIN_CALL' +export const LEAVE_CALL = 'LEAVE_CALL' +export const SEND_MAPPER_INFO = 'SEND_MAPPER_INFO' +export const SEND_COORDS = 'SEND_COORDS' +export const CREATE_MESSAGE = 'CREATE_MESSAGE' +export const DRAG_TOPIC = 'DRAG_TOPIC' +export const CREATE_TOPIC = 'CREATE_TOPIC' +export const UPDATE_TOPIC = 'UPDATE_TOPIC' +export const REMOVE_TOPIC = 'REMOVE_TOPIC' +export const DELETE_TOPIC = 'DELETE_TOPIC' +export const CREATE_SYNAPSE = 'CREATE_SYNAPSE' +export const UPDATE_SYNAPSE = 'UPDATE_SYNAPSE' +export const REMOVE_SYNAPSE = 'REMOVE_SYNAPSE' +export const DELETE_SYNAPSE = 'DELETE_SYNAPSE' +export const UPDATE_MAP = 'UPDATE_MAP' + +/* EVENTS RECEIVABLE */ +export const INVITED_TO_CALL = 'INVITED_TO_CALL' +export const INVITED_TO_JOIN = 'INVITED_TO_JOIN' +export const CALL_ACCEPTED = 'CALL_ACCEPTED' +export const CALL_DENIED = 'CALL_DENIED' +export const INVITE_DENIED = 'INVITE_DENIED' +export const CALL_IN_PROGRESS = 'CALL_IN_PROGRESS' +export const CALL_STARTED = 'CALL_STARTED' +export const MAPPER_JOINED_CALL = 'MAPPER_JOINED_CALL' +export const MAPPER_LEFT_CALL = 'MAPPER_LEFT_CALL' +export const MAPPER_LIST_UPDATED = 'MAPPER_LIST_UPDATED' +export const NEW_MAPPER = 'NEW_MAPPER' +export const LOST_MAPPER = 'LOST_MAPPER' +export const MESSAGE_CREATED = 'MESSAGE_CREATED' +export const TOPIC_DRAGGED = 'TOPIC_DRAGGED' +export const TOPIC_CREATED = 'TOPIC_CREATED' +export const TOPIC_UPDATED = 'TOPIC_UPDATED' +export const TOPIC_REMOVED = 'TOPIC_REMOVED' +export const TOPIC_DELETED = 'TOPIC_DELETED' +export const SYNAPSE_CREATED = 'SYNAPSE_CREATED' +export const SYNAPSE_UPDATED = 'SYNAPSE_UPDATED' +export const SYNAPSE_REMOVED = 'SYNAPSE_REMOVED' +export const SYNAPSE_DELETED = 'SYNAPSE_DELETED' +export const PEER_COORDS_UPDATED = 'PEER_COORDS_UPDATED' +export const MAP_UPDATED = 'MAP_UPDATED' +export const LIVE_MAPS_RECEIVED = 'LIVE_MAPS_RECEIVED' +export const MAP_WENT_LIVE = 'MAP_WENT_LIVE' +export const MAP_CEASED_LIVE = 'MAP_CEASED_LIVE' diff --git a/frontend/src/Metamaps/Realtime/index.js b/frontend/src/Metamaps/Realtime/index.js new file mode 100644 index 00000000..2ef61927 --- /dev/null +++ b/frontend/src/Metamaps/Realtime/index.js @@ -0,0 +1,557 @@ +/* global Metamaps, $, SocketIoConnection */ + +/* + * Metamaps.Realtime.js + * + * Dependencies: + * - Metamaps.Backbone + * - Metamaps.Erb + * - Metamaps.Mappers + * - Metamaps.Mappings + * - Metamaps.Messages + * - Metamaps.Synapses + * - Metamaps.Topics + */ + +import _ from 'lodash' +import SimpleWebRTC from 'simplewebrtc' + +import Active from '../Active' +import GlobalUI from '../GlobalUI' +import JIT from '../JIT' +import Synapse from '../Synapse' +import Topic from '../Topic' +import Util from '../Util' +import Views from '../Views' +import Visualize from '../Visualize' + +import { + INVITED_TO_CALL, + INVITED_TO_JOIN, + CALL_ACCEPTED, + CALL_DENIED, + INVITE_DENIED, + CALL_IN_PROGRESS, + CALL_STARTED, + MAPPER_JOINED_CALL, + MAPPER_LEFT_CALL, + MAPPER_LIST_UPDATED, + 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, + LIVE_MAPS_RECEIVED, + MAP_WENT_LIVE, + MAP_CEASED_LIVE, + MAP_UPDATED +} from './events' + +import { + invitedToCall, + invitedToJoin, + callAccepted, + callDenied, + inviteDenied, + callInProgress, + callStarted, + mapperJoinedCall, + mapperLeftCall, + mapperListUpdated, + peerCoordsUpdated, + newMapper, + lostMapper, + messageCreated, + topicDragged, + topicCreated, + topicUpdated, + topicRemoved, + topicDeleted, + synapseCreated, + synapseUpdated, + synapseRemoved, + synapseDeleted, + mapUpdated, + liveMapsReceived, + mapWentLive, + mapCeasedLive +} from './receivable' + +import { + requestLiveMaps, + joinMap, + leaveMap, + checkForCall, + acceptCall, + denyCall, + denyInvite, + inviteToJoin, + inviteACall, + joinCall, + leaveCall, + sendMapperInfo, + sendCoords, + dragTopic, + createTopic, + updateTopic, + removeTopic, + deleteTopic, + createSynapse, + updateSynapse, + removeSynapse, + deleteSynapse, + updateMap +} from './sendable' + +const Realtime = { + videoId: 'video-wrapper', + socket: null, + webrtc: null, + readyToCall: false, + mappersOnMap: {}, + disconnected: false, + chatOpen: false, + soundId: null, + broadcastingStatus: false, + inConversation: false, + localVideo: null, + init: function () { + var self = Realtime + + self.addJuntoListeners() + + self.socket = new SocketIoConnection({ url: Metamaps.Erb['REALTIME_SERVER']}) + + setupSendables(self) + + 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 Metamaps.Backbone.MessageCollection(Metamaps.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 + // subscribe to rooms on the websocket? + 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(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(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 screenWidth = $(document).width() + 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 boundary = self.chatOpen ? '#wrapper' : document + var mapper = self.mappersOnMap[id] + var xMax = $(boundary).width() + var yMax = $(boundary).height() + var compassDiameter = 56 + var compassArrowSize = 24 + + var origPixels = Util.coordsToPixels(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 ratio = dy / dx + 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 setupSendables = Realtime => { + [requestLiveMaps, + joinMap, + leaveMap, + checkForCall, + acceptCall, + denyCall, + denyInvite, + inviteToJoin, + inviteACall, + joinCall, + leaveCall, + sendMapperInfo, + sendCoords, + dragTopic, + createTopic, + updateTopic, + removeTopic, + deleteTopic, + createSynapse, + updateSynapse, + removeSynapse, + deleteSynapse, + updateMap].forEach(sendable => Realtime[sendable.name] = sendable(Realtime, Realtime.socket)) +} + +const subscribeToEvents = (Realtime, socket) => { + 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_JOINED_CALL, mapperJoinedCall(Realtime)) + socket.on(MAPPER_LEFT_CALL, mapperLeftCall(Realtime)) + socket.on(MAPPER_LIST_UPDATED, mapperListUpdated(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)) + socket.on(LIVE_MAPS_RECEIVED, liveMapsReceived(Realtime)) + socket.on(MAP_WENT_LIVE, mapWentLive(Realtime)) + socket.on(MAP_CEASED_LIVE, mapCeasedLive(Realtime)) +} + +export default Realtime diff --git a/frontend/src/Metamaps/Realtime/receivable.js b/frontend/src/Metamaps/Realtime/receivable.js new file mode 100644 index 00000000..bf974bbe --- /dev/null +++ b/frontend/src/Metamaps/Realtime/receivable.js @@ -0,0 +1,396 @@ +/* +everthing in this file happens as a result of websocket events +*/ + +import Active from '../Active' +import GlobalUI from '../GlobalUI' +import Control from '../Control' +import Map from '../Map' +import Mapper from '../Mapper' +import Topic from '../Topic' +import Synapse from '../Synapse' +import Util from '../Util' +import Visualize from '../Visualize' + +export const synapseRemoved = self => data => { + var synapse = Metamaps.Synapses.get(data.mappableid) + if (synapse) { + var edge = synapse.get('edge') + var mapping = synapse.getMapping() + if (edge.getData('mappings').length - 1 === 0) { + Control.hideEdge(edge) + } + + var index = _.indexOf(edge.getData('synapses'), synapse) + edge.getData('mappings').splice(index, 1) + edge.getData('synapses').splice(index, 1) + if (edge.getData('displayIndex')) { + delete edge.data.$displayIndex + } + Metamaps.Synapses.remove(synapse) + Metamaps.Mappings.remove(mapping) + } +} + +export const synapseDeleted = self => data => { + self.synapseRemoved(data) +} + +export const synapseCreated = self => data => { + var topic1, topic2, node1, node2, synapse, mapping, cancel, mapper + + + function waitThenRenderSynapse () { + if (synapse && mapping && mapper) { + topic1 = synapse.getTopic1() + node1 = topic1.get('node') + topic2 = synapse.getTopic2() + node2 = topic2.get('node') + + Synapse.renderSynapse(mapping, synapse, node1, node2, false) + } + else if (!cancel) { + setTimeout(waitThenRenderSynapse, 10) + } + } + + mapper = Metamaps.Mappers.get(data.mapperid) + if (mapper === undefined) { + Mapper.get(data.mapperid, function(m) { + Metamaps.Mappers.add(m) + mapper = m + }) + } + $.ajax({ + url: '/synapses/' + data.mappableid + '.json', + success: function (response) { + Metamaps.Synapses.add(response) + synapse = Metamaps.Synapses.get(response.id) + }, + error: function () { + cancel = true + } + }) + $.ajax({ + url: '/mappings/' + data.mappingid + '.json', + success: function (response) { + Metamaps.Mappings.add(response) + mapping = Metamaps.Mappings.get(response.id) + }, + error: function () { + cancel = true + } + }) + waitThenRenderSynapse() +} + +export const topicRemoved = self => data => { + var topic = Metamaps.Topics.get(data.mappableid) + if (topic) { + var node = topic.get('node') + var mapping = topic.getMapping() + Control.hideNode(node.id) + Metamaps.Topics.remove(topic) + Metamaps.Mappings.remove(mapping) + } +} + +export const topicDeleted = self => data => { + self.topicRemoved(data) +} + +export const topicCreated = self => data => { + var topic, mapping, mapper, cancel + + function waitThenRenderTopic () { + if (topic && mapping && mapper) { + Topic.renderTopic(mapping, topic, false, false) + } + else if (!cancel) { + setTimeout(waitThenRenderTopic, 10) + } + } + + mapper = Metamaps.Mappers.get(data.mapperid) + if (mapper === undefined) { + Mapper.get(data.mapperid, function(m) { + Metamaps.Mappers.add(m) + mapper = m + }) + } + $.ajax({ + url: '/topics/' + data.mappableid + '.json', + success: function (response) { + Metamaps.Topics.add(response) + topic = Metamaps.Topics.get(response.id) + }, + error: function () { + cancel = true + } + }) + $.ajax({ + url: '/mappings/' + data.mappingid + '.json', + success: function (response) { + Metamaps.Mappings.add(response) + mapping = Metamaps.Mappings.get(response.id) + }, + error: function () { + cancel = true + } + }) + + waitThenRenderTopic() +} + +export const messageCreated = self => data => { + self.room.addMessages(new Metamaps.Backbone.MessageCollection(data)) +} + +export const mapUpdated = self => data => { + var map = Active.Map + var isActiveMap = map && data.mapId === map.id + if (isActiveMap) { + var couldEditBefore = map.authorizeToEdit(Active.Mapper) + var idBefore = map.id + map.fetch({ + success: function (model, response) { + var idNow = model.id + var canEditNow = model.authorizeToEdit(Active.Mapper) + if (idNow !== idBefore) { + Map.leavePrivateMap() // this means the map has been changed to private + } + else if (couldEditBefore && !canEditNow) { + Map.cantEditNow() + } + else if (!couldEditBefore && canEditNow) { + Map.canEditNow() + } else { + model.trigger('changeByOther') + } + } + }) + } +} + +export const topicUpdated = self => data => { + var topic = Metamaps.Topics.get(data.topicId) + if (topic) { + var node = topic.get('node') + topic.fetch({ + success: function (model) { + model.set({ node: node }) + model.trigger('changeByOther') + } + }) + } +} + +export const synapseUpdated = self => data => { + var synapse = Metamaps.Synapses.get(data.synapseId) + if (synapse) { + // edge reset necessary because fetch causes model reset + var edge = synapse.get('edge') + synapse.fetch({ + success: function (model) { + model.set({ edge: edge }) + model.trigger('changeByOther') + } + }) + } +} + +export const topicDragged = self => positions => { + var topic + var node + + if (Active.Map) { + for (var key in positions) { + topic = Metamaps.Topics.get(key) + if (topic) node = topic.get('node') + if (node) node.pos.setc(positions[key].x, positions[key].y) + } // for + Visualize.mGraph.plot() + } +} + +export const peerCoordsUpdated = self => data => { + if (!self.mappersOnMap[data.userid]) return + self.mappersOnMap[data.userid].coords = {x: data.usercoords.x,y: data.usercoords.y} + self.positionPeerIcon(data.userid) +} + +export const lostMapper = self => data => { + // data.userid + // data.username + delete self.mappersOnMap[data.userid] + self.room.chat.sound.play('leavemap') + // $('#mapper' + data.userid).remove() + $('#compass' + data.userid).remove() + self.room.chat.removeParticipant(data.username) + + GlobalUI.notifyUser(data.username + ' just left the map') + + if ((self.inConversation && self.countOthersInConversation() === 0) || + (!self.inConversation && self.countOthersInConversation() === 1)) { + self.callEnded() + } +} + +export const mapperListUpdated = self => data => { + // data.userid + // data.username + // data.userimage + + self.mappersOnMap[data.userid] = { + id: data.userid, + name: data.username, + username: data.username, + image: data.userimage, + color: Util.getPastelColor(), + inConversation: data.userinconversation, + coords: { + x: 0, + y: 0 + } + } + + if (data.userid !== Active.Mapper.id) { + self.room.chat.addParticipant(self.mappersOnMap[data.userid]) + if (data.userinconversation) self.room.chat.mapperJoinedCall(data.userid) + + // create a div for the collaborators compass + self.createCompass(data.username, data.userid, data.userimage, self.mappersOnMap[data.userid].color) + } +} + +export const newMapper = self => data => { + // data.userid + // data.username + // data.userimage + // data.coords + var firstOtherPerson = Object.keys(self.mappersOnMap).length === 0 + + self.mappersOnMap[data.userid] = { + id: data.userid, + name: data.username, + username: data.username, + image: data.userimage, + color: Util.getPastelColor(), + realtime: true, + coords: { + x: 0, + y: 0 + }, + } + + // 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]) + + // create a div for the collaborators compass + self.createCompass(data.username, data.userid, data.userimage, self.mappersOnMap[data.userid].color) + + var notifyMessage = data.username + ' just joined the map' + if (firstOtherPerson) { + notifyMessage += ' ' + } + GlobalUI.notifyUser(notifyMessage) + self.sendMapperInfo(data.userid) + } +} + +export const callAccepted = self => userid => { + var username = self.mappersOnMap[userid].name + GlobalUI.notifyUser('Conversation starting...') + self.joinCall() + self.room.chat.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) +} + +export const inviteDenied = self => userid => { + var username = self.mappersOnMap[userid].name + GlobalUI.notifyUser(username + " didn't accept your invitation") + self.room.chat.invitationAnswered(userid) +} + +export const invitedToCall = self => inviter => { + self.room.chat.sound.stop(self.soundId) + self.soundId = self.room.chat.sound.play('sessioninvite') + + var username = self.mappersOnMap[inviter].name + var notifyText = '