From d004e98adabb960d544da6c8678afeb81f987c7a Mon Sep 17 00:00:00 2001 From: Connor Turland Date: Tue, 18 Oct 2016 12:34:19 -0400 Subject: [PATCH] holy insanity realtime refactor (#779) * all the refactoring * make it all work --- frontend/src/Metamaps/Backbone/index.js | 6 +- frontend/src/Metamaps/Realtime.js | 1139 ------------------ frontend/src/Metamaps/Realtime/events.js | 54 + frontend/src/Metamaps/Realtime/index.js | 557 +++++++++ frontend/src/Metamaps/Realtime/receivable.js | 396 ++++++ frontend/src/Metamaps/Realtime/sendable.js | 246 ++++ frontend/src/Metamaps/Views/Room.js | 2 + realtime/realtime-server.js | 391 +++--- 8 files changed, 1492 insertions(+), 1299 deletions(-) delete mode 100644 frontend/src/Metamaps/Realtime.js create mode 100644 frontend/src/Metamaps/Realtime/events.js create mode 100644 frontend/src/Metamaps/Realtime/index.js create mode 100644 frontend/src/Metamaps/Realtime/receivable.js create mode 100644 frontend/src/Metamaps/Realtime/sendable.js 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 = '' + notifyText += username + ' is inviting you to a conversation. Join live?' + notifyText += ' ' + notifyText += ' ' + GlobalUI.notifyUser(notifyText, true) +} + +export const invitedToJoin = 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 = username + ' is inviting you to the conversation. Join?' + notifyText += ' ' + notifyText += ' ' + GlobalUI.notifyUser(notifyText, true) +} + +export const mapperJoinedCall = self => id => { + var mapper = self.mappersOnMap[id] + + if (mapper) { + if (self.inConversation) { + var username = mapper.name + var notifyText = username + ' joined the call' + GlobalUI.notifyUser(notifyText) + } + + mapper.inConversation = true + self.room.chat.mapperJoinedCall(id) + } +} + +export const mapperLeftCall = self => id => { + 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() + } + } +} + +export const callInProgress = self => () => { + var notifyText = "There's a conversation happening, want to join?" + notifyText += ' ' + notifyText += ' ' + GlobalUI.notifyUser(notifyText, true) + self.room.conversationInProgress() +} + +export const callStarted = self => () => { + if (self.inConversation) return + var notifyText = "There's a conversation starting, want to join?" + notifyText += ' ' + notifyText += ' ' + GlobalUI.notifyUser(notifyText, true) + self.room.conversationInProgress() +} + +export const liveMapsReceived = self => () => {} +export const mapWentLive = self => () => {} +export const mapCeasedLive = self => () => {} diff --git a/frontend/src/Metamaps/Realtime/sendable.js b/frontend/src/Metamaps/Realtime/sendable.js new file mode 100644 index 00000000..a3321b03 --- /dev/null +++ b/frontend/src/Metamaps/Realtime/sendable.js @@ -0,0 +1,246 @@ +import Active from '../Active' +import GlobalUI from '../GlobalUI' + +import { + REQUEST_LIVE_MAPS, + JOIN_MAP, + LEAVE_MAP, + CHECK_FOR_CALL, + ACCEPT_CALL, + DENY_CALL, + DENY_INVITE, + INVITE_TO_JOIN, + INVITE_A_CALL, + JOIN_CALL, + LEAVE_CALL, + SEND_MAPPER_INFO, + SEND_COORDS, + CREATE_MESSAGE, + DRAG_TOPIC, + CREATE_TOPIC, + UPDATE_TOPIC, + REMOVE_TOPIC, + DELETE_TOPIC, + CREATE_SYNAPSE, + UPDATE_SYNAPSE, + REMOVE_SYNAPSE, + DELETE_SYNAPSE, + UPDATE_MAP +} from './events' + +export const requestLiveMaps = (self, socket) => () => { + socket.emit(REQUEST_LIVE_MAPS) +} + +export const joinMap = (self, socket) => () => { + socket.emit(JOIN_MAP, { + userid: Active.Mapper.id, + username: Active.Mapper.get('name'), + userimage: Active.Mapper.get('image'), + mapid: Active.Map.id, + map: Active.Map.attributes + }) +} + +export const leaveMap = (self, socket) => () => { + socket.emit(LEAVE_MAP) +} + +export const checkForCall = (self, socket) => () => { + socket.emit(CHECK_FOR_CALL, { room: self.room.room, mapid: Active.Map.id }) +} + +export const sendMapperInfo = (self, socket) => userid => { + // send this new mapper back your details, and the awareness that you've loaded the map + var update = { + userToNotify: userid, + username: Active.Mapper.get('name'), + userimage: Active.Mapper.get('image'), + userid: Active.Mapper.id, + userinconversation: self.inConversation, + mapid: Active.Map.id + } + socket.emit(SEND_MAPPER_INFO, update) +} + +export const joinCall = (self, socket) => () => { + 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 + socket.emit(JOIN_CALL, { + mapid: Active.Map.id, + id: Active.Mapper.id + }) + self.webrtc.startLocalVideo() + GlobalUI.clearNotify() + self.room.chat.mapperJoinedCall(Active.Mapper.id) +} + +export const leaveCall = (self, socket) => () => { + socket.emit(LEAVE_CALL, { + 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() + } +} + +export const acceptCall = (self, socket) => userid => { + self.room.chat.sound.stop(self.soundId) + socket.emit(ACCEPT_CALL, { + mapid: Active.Map.id, + invited: Active.Mapper.id, + inviter: userid + }) + $.post('/maps/' + Active.Map.id + '/events/conversation') + self.joinCall() + GlobalUI.clearNotify() +} + +export const denyCall = (self, socket) => userid => { + self.room.chat.sound.stop(self.soundId) + socket.emit(DENY_CALL, { + mapid: Active.Map.id, + invited: Active.Mapper.id, + inviter: userid + }) + GlobalUI.clearNotify() +} + +export const denyInvite = (self, socket) => userid => { + self.room.chat.sound.stop(self.soundId) + socket.emit(DENY_INVITE, { + mapid: Active.Map.id, + invited: Active.Mapper.id, + inviter: userid + }) + GlobalUI.clearNotify() +} + +export const inviteACall = (self, socket) => userid => { + socket.emit(INVITE_A_CALL, { + mapid: Active.Map.id, + inviter: Active.Mapper.id, + invited: userid + }) + self.room.chat.invitationPending(userid) + GlobalUI.clearNotify() +} + +export const inviteToJoin = (self, socket) => userid => { + socket.emit(INVITE_TO_JOIN, { + mapid: Active.Map.id, + inviter: Active.Mapper.id, + invited: userid + }) + self.room.chat.invitationPending(userid) +} + +export const sendCoords = (self, socket) => coords => { + var map = Active.Map + var mapper = Active.Mapper + if (map.authorizeToEdit(mapper)) { + var update = { + usercoords: coords, + userid: Active.Mapper.id, + mapid: Active.Map.id + } + socket.emit(SEND_COORDS, update) + } +} + +export const dragTopic = (self, socket) => positions => { + if (Active.Map) { + positions.mapid = Active.Map.id + socket.emit(DRAG_TOPIC, positions) + } +} + +export const updateTopic = (self, socket) => topic => { + var data = { + topicId: topic.id + } + socket.emit(UPDATE_TOPIC, data) +} + +export const updateSynapse = (self, socket) => synapse => { + var data = { + synapseId: synapse.id + } + socket.emit(UPDATE_SYNAPSE, data) +} + +export const updateMap = (self, socket) => map => { + var data = { + mapId: map.id + } + socket.emit(UPDATE_MAP, data) +} + +export const createMessage = (self, socket) => data => { + var message = data.attributes + message.mapid = Active.Map.id + socket.emit(CREATE_MESSAGE, message) +} + +export const createTopic = (self, socket) => data => { + if (Active.Map) { + data.mapperid = Active.Mapper.id + data.mapid = Active.Map.id + socket.emit(CREATE_TOPIC, data) + } +} + +export const deleteTopic = (self, socket) => data => { + if (Active.Map) { + socket.emit(DELETE_TOPIC, data) + } +} + +export const removeTopic = (self, socket) => data => { + if (Active.Map) { + data.mapid = Active.Map.id + socket.emit(REMOVE_TOPIC, data) + } +} + +export const createSynapse = (self, socket) => data => { + if (Active.Map) { + data.mapperid = Active.Mapper.id + data.mapid = Active.Map.id + socket.emit(CREATE_SYNAPSE, data) + } +} + +export const deleteSynapse = (self, socket) => data => { + if (Active.Map) { + data.mapid = Active.Map.id + socket.emit(DELETE_SYNAPSE, data) + } +} + +export const removeSynapse = (self, socket) => data => { + if (Active.Map) { + data.mapid = Active.Map.id + socket.emit(REMOVE_SYNAPSE, data) + } +} diff --git a/frontend/src/Metamaps/Views/Room.js b/frontend/src/Metamaps/Views/Room.js index b1434d42..7075e017 100644 --- a/frontend/src/Metamaps/Views/Room.js +++ b/frontend/src/Metamaps/Views/Room.js @@ -60,6 +60,7 @@ Room.prototype.leaveVideoOnly = function() { } this.isActiveRoom = false this.webrtc.leaveRoom() + this.webrtc.stopLocalVideo() } Room.prototype.leave = function() { @@ -68,6 +69,7 @@ Room.prototype.leave = function() { } this.isActiveRoom = false this.webrtc.leaveRoom() + this.webrtc.stopLocalVideo() this.chat.conversationEnded() this.chat.removeParticipants() this.chat.clearMessages() diff --git a/realtime/realtime-server.js b/realtime/realtime-server.js index ba5eccf4..89b38c31 100644 --- a/realtime/realtime-server.js +++ b/realtime/realtime-server.js @@ -1,190 +1,267 @@ var - io = require('socket.io').listen(5001), - signalServer = require('./signal'), - stunservers = [{"url": "stun:stun.l.google.com:19302"}]; +io = require('socket.io').listen(5001), +signalServer = require('./signal'), +stunservers = [{"url": "stun:stun.l.google.com:19302"}] -io.set('log', false); +import { + // server sendable, client receivable + 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, + + // server receivable, client sendable + REQUEST_LIVE_MAPS, + JOIN_MAP, + LEAVE_MAP, + CHECK_FOR_CALL, + ACCEPT_CALL, + DENY_CALL, + DENY_INVITE, + INVITE_TO_JOIN, + INVITE_A_CALL, + JOIN_CALL, + LEAVE_CALL, + REQUEST_MAPPER_INFO, + SEND_MAPPER_INFO, + SEND_COORDS, + CREATE_MESSAGE, + DRAG_TOPIC, + CREATE_TOPIC, + UPDATE_TOPIC, + REMOVE_TOPIC, + DELETE_TOPIC, + CREATE_SYNAPSE, + UPDATE_SYNAPSE, + REMOVE_SYNAPSE, + DELETE_SYNAPSE, + UPDATE_MAP +} from '../frontend/src/Metamaps/Realtime/events' + +io.set('log', false) function start() { - var livemaps = {} + var livemaps = {} - signalServer(io, stunservers); + signalServer(io, stunservers) - io.on('connection', function (socket) { - - socket.on('requestLiveMaps', function (activeUser) { - //constrain response to maps visible to user - var maps = Object.keys(livemaps).map(function(key) { return livemaps[key] }) - socket.emit('receiveLiveMaps', maps) - }) - // this will ping a new person with awareness of who's already on the map - socket.on('updateNewMapperList', function (data) { - var existingUser = { - userid: data.userid, - username: data.username, - userrealtime: data.userrealtime, - userinconversation: data.userinconversation, - userimage: data.userimage - }; - socket.broadcast.emit(data.userToNotify + '-' + data.mapid + '-UpdateMapperList', existingUser); - }); + io.on('connection', function (socket) { - // as a new mapper check whether there's a call in progress to join - socket.on('checkForCall', function (data) { - var socketsInRoom = io.sockets.clients(data.room); - if (socketsInRoom.length) socket.emit('maps-' + data.mapid + '-callInProgress'); - }); - // send the invitation to start a call - socket.on('inviteACall', function (data) { - socket.broadcast.emit(data.invited + '-' + data.mapid + '-invitedToCall', data.inviter); - }); - // send an invitation to join a call in progress - socket.on('inviteToJoin', function (data) { - socket.broadcast.emit(data.invited + '-' + data.mapid + '-invitedToJoin', data.inviter); - }); - // send response back to the inviter - socket.on('callAccepted', function (data) { - socket.broadcast.emit(data.inviter + '-' + data.mapid + '-callAccepted', data.invited); - socket.broadcast.emit('maps-' + data.mapid + '-callStarting'); - }); - socket.on('callDenied', function (data) { - socket.broadcast.emit(data.inviter + '-' + data.mapid + '-callDenied', data.invited); - }); - socket.on('inviteDenied', function (data) { - socket.broadcast.emit(data.inviter + '-' + data.mapid + '-inviteDenied', data.invited); - }); - socket.on('mapperJoinedCall', function (data) { - socket.broadcast.emit('maps-' + data.mapid + '-mapperJoinedCall', data.id); - }); - socket.on('mapperLeftCall', function (data) { - socket.broadcast.emit('maps-' + data.mapid + '-mapperLeftCall', data.id); - }); + socket.on(REQUEST_LIVE_MAPS, function (activeUser) { + //constrain response to maps visible to user + var maps = Object.keys(livemaps).map(function(key) { return livemaps[key] }) + socket.emit(LIVE_MAPS_RECEIVED, maps) + }) - // this will ping everyone on a map that there's a person just joined the map - socket.on('newMapperNotify', function (data) { - - if (!livemaps[data.mapid]) { - livemaps[data.mapid] = data.map // { name: '', desc: '', numTopics: '' } - livemaps[data.mapid].mapper_count = 1 - io.sockets.emit('map_went_live', livemaps[data.mapid]) - } - else { - livemaps[data.mapid].mapper_count++ - } - - socket.set('mapid', data.mapid); - socket.set('userid', data.userid); - socket.set('username', data.username); + // this will ping a new person with awareness of who's already on the map + socket.on(SEND_MAPPER_INFO, function (data) { + var existingUser = { + userid: data.userid, + username: data.username, + userrealtime: data.userrealtime, + userinconversation: data.userinconversation, + userimage: data.userimage + } + //socket.broadcast.emit(data.userToNotify + '-' + data.mapid + '-UpdateMapperList', existingUser) + socket.broadcast.emit(MAPPER_LIST_UPDATED, existingUser) + }) - var newUser = { - userid: data.userid, - username: data.username, - userimage: data.userimage - }; + // as a new mapper check whether there's a call in progress to join + socket.on(CHECK_FOR_CALL, function (data) { + var socketsInRoom = io.sockets.clients(data.room) + //if (socketsInRoom.length) socket.emit('maps-' + data.mapid + '-callInProgress') + if (socketsInRoom.length) socket.emit(CALL_IN_PROGRESS) + }) - socket.broadcast.emit('maps-' + data.mapid + '-newmapper', newUser); - }); + // send the invitation to start a call + socket.on(INVITE_A_CALL, function (data) { + //socket.broadcast.emit(data.invited + '-' + data.mapid + '-invitedToCall', data.inviter) + socket.broadcast.emit(INVITED_TO_CALL, data.inviter) + }) - var end = function () { - - var socketUserName, socketUserID; - socket.get('userid', function (err, id) { - socketUserID = id; - }); - socket.get('username', function (err, name) { - socketUserName = name; - }); - var data = { - username: socketUserName, - userid: socketUserID - }; - socket.get('mapid', function (err, mapid) { - if (livemaps[mapid] && livemaps[mapid].mapper_count == 1) { - delete livemaps[mapid] - io.sockets.emit('map_no_longer_live', { id: mapid }) - } - else if (livemaps[mapid]) { - livemaps[mapid].mapper_count-- - } - socket.broadcast.emit('maps-' + mapid + '-lostmapper', data); - }); - }; - // this will ping everyone on a map that there's a person just left the map - socket.on('disconnect', end); - socket.on('endMapperNotify', end); + // send an invitation to join a call in progress + socket.on(INVITE_TO_JOIN, function (data) { + //socket.broadcast.emit(data.invited + '-' + data.mapid + '-invitedToJoin', data.inviter) + socket.broadcast.emit(INVITED_TO_JOIN, data.inviter) + }) - socket.on('updateMapperCoords', function (data) { - var peer = { - userid: data.userid, - usercoords: data.usercoords - }; + // send response back to the inviter + socket.on(ACCEPT_CALL, function (data) { + //socket.broadcast.emit(data.inviter + '-' + data.mapid + '-callAccepted', data.invited) + //socket.broadcast.emit('maps-' + data.mapid + '-callStarting') + socket.broadcast.emit(CALL_ACCEPTED, data.invited) + socket.broadcast.emit(CALL_STARTED) + }) - socket.broadcast.emit('maps-' + data.mapid + '-updatePeerCoords', peer); - }); + socket.on(DENY_CALL, function (data) { + //socket.broadcast.emit(data.inviter + '-' + data.mapid + '-callDenied', data.invited) + socket.broadcast.emit(CALL_DENIED, data.invited) + }) + socket.on(DENY_INVITE, function (data) { + //socket.broadcast.emit(data.inviter + '-' + data.mapid + '-inviteDenied', data.invited) + socket.broadcast.emit(INVITE_DENIED, data.invited) + }) + socket.on(JOIN_CALL, function (data) { + //socket.broadcast.emit('maps-' + data.mapid + '-mapperJoinedCall', data.id) + socket.broadcast.emit(MAPPER_JOINED_CALL, data.id) + }) + socket.on(LEAVE_CALL, function (data) { + //socket.broadcast.emit('maps-' + data.mapid + '-mapperLeftCall', data.id) + socket.broadcast.emit(MAPPER_LEFT_CALL, data.id) + }) - socket.on('topicDrag', function (data) { - var mapId = data.mapid; - delete data.mapid; + // this will ping everyone on a map that there's a person just joined the map + socket.on(JOIN_MAP, function (data) { - socket.broadcast.emit('maps-' + mapId + '-topicDrag', data); - }); + if (!livemaps[data.mapid]) { + livemaps[data.mapid] = data.map // { name: '', desc: '', numTopics: '' } + livemaps[data.mapid].mapper_count = 1 + io.sockets.emit(MAP_WENT_LIVE, livemaps[data.mapid]) + } + else { + livemaps[data.mapid].mapper_count++ + } - socket.on('newMessage', function (data) { - var mapId = data.mapid; - delete data.mapid; + socket.set('mapid', data.mapid) + socket.set('userid', data.userid) + socket.set('username', data.username) - socket.broadcast.emit('maps-' + mapId + '-newMessage', data); - }); + var newUser = { + userid: data.userid, + username: data.username, + userimage: data.userimage + } - socket.on('newTopic', function (data) { - var mapId = data.mapid; - delete data.mapid; + //socket.broadcast.emit('maps-' + data.mapid + '-newmapper', newUser) + socket.broadcast.emit(NEW_MAPPER, newUser) + }) - socket.broadcast.emit('maps-' + mapId + '-newTopic', data); - }); + var end = function () { - socket.on('topicChangeFromClient', function (data) { - socket.broadcast.emit('topicChangeFromServer', data); - }); + var socketUserName, socketUserID + socket.get('userid', function (err, id) { + socketUserID = id + }) + socket.get('username', function (err, name) { + socketUserName = name + }) + var data = { + username: socketUserName, + userid: socketUserID + } + socket.get('mapid', function (err, mapid) { + if (livemaps[mapid] && livemaps[mapid].mapper_count == 1) { + delete livemaps[mapid] + io.sockets.emit(MAP_CEASED_LIVE, { id: mapid }) + } + else if (livemaps[mapid]) { + livemaps[mapid].mapper_count-- + } + // scope by map + socket.broadcast.emit(LOST_MAPPER, data) + }) + } + // this will ping everyone on a map that there's a person just left the map + socket.on('disconnect', end) + socket.on(LEAVE_MAP, end) - socket.on('synapseChangeFromClient', function (data) { - socket.broadcast.emit('synapseChangeFromServer', data); - }); + socket.on(SEND_COORDS, function (data) { + var peer = { + userid: data.userid, + usercoords: data.usercoords + } - socket.on('mapChangeFromClient', function (data) { - socket.broadcast.emit('mapChangeFromServer', data); - }); + //socket.broadcast.emit('maps-' + data.mapid + '-updatePeerCoords', peer) + socket.broadcast.emit(PEER_COORDS_UPDATED, peer) + }) - socket.on('deleteTopicFromClient', function (data) { - socket.broadcast.emit('deleteTopicFromServer', data); - }); + socket.on(DRAG_TOPIC, function (data) { + var mapId = data.mapid + delete data.mapid - socket.on('removeTopic', function (data) { - var mapId = data.mapid; - delete data.mapid; + //socket.broadcast.emit('maps-' + mapId + '-topicDrag', data) + socket.broadcast.emit(TOPIC_DRAGGED, data) + }) - socket.broadcast.emit('maps-' + mapId + '-removeTopic', data); - }); + socket.on(CREATE_MESSAGE, function (data) { + var mapId = data.mapid + delete data.mapid + //socket.broadcast.emit('maps-' + mapId + '-newMessage', data) + socket.broadcast.emit(MESSAGE_CREATED, data) + }) - socket.on('newSynapse', function (data) { - var mapId = data.mapid; - delete data.mapid; + socket.on(CREATE_TOPIC, function (data) { + var mapId = data.mapid + delete data.mapid + //socket.broadcast.emit('maps-' + mapId + '-newTopic', data) + socket.broadcast.emit(TOPIC_CREATED, data) + }) - socket.broadcast.emit('maps-' + mapId + '-newSynapse', data); - }); + socket.on(UPDATE_TOPIC, function (data) { + socket.broadcast.emit(TOPIC_UPDATED, data) + }) - socket.on('deleteSynapseFromClient', function (data) { - socket.broadcast.emit('deleteSynapseFromServer', data); - }); + socket.on(REMOVE_TOPIC, function (data) { + var mapId = data.mapid + delete data.mapid + //socket.broadcast.emit('maps-' + mapId + '-removeTopic', data) + socket.broadcast.emit(TOPIC_REMOVED, data) + }) - socket.on('removeSynapse', function (data) { - var mapId = data.mapid; - delete data.mapid; + socket.on(DELETE_TOPIC, function (data) { + socket.broadcast.emit(TOPIC_DELETED, data) + }) - socket.broadcast.emit('maps-' + mapId + '-removeSynapse', data); - }); + socket.on(CREATE_SYNAPSE, function (data) { + var mapId = data.mapid + delete data.mapid + //socket.broadcast.emit('maps-' + mapId + '-newSynapse', data) + socket.broadcast.emit(SYNAPSE_CREATED, data) + }) - }); + socket.on(UPDATE_SYNAPSE, function (data) { + socket.broadcast.emit(SYNAPSE_UPDATED, data) + }) + + socket.on(REMOVE_SYNAPSE, function (data) { + var mapId = data.mapid + delete data.mapid + //socket.broadcast.emit('maps-' + mapId + '-removeSynapse', data) + socket.broadcast.emit(SYNAPSE_REMOVED, data) + }) + + socket.on(DELETE_SYNAPSE, function (data) { + //socket.broadcast.emit('deleteSynapseFromServer', data) + socket.broadcast.emit(SYNAPSE_DELETED, data) + }) + + socket.on(UPDATE_MAP, function (data) { + socket.broadcast.emit(MAP_UPDATED, data) + }) + }) } -start(); +start()