diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 3e19bbc2..f93849fa 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -25,6 +25,7 @@ //= require ./src/views/room //= require ./src/JIT //= require ./src/Metamaps +//= require ./src/Metamaps.Realtime //= require ./src/Metamaps.Control //= require ./src/Metamaps.Filter //= require ./src/Metamaps.Listeners diff --git a/app/assets/javascripts/src/Metamaps.Realtime.js b/app/assets/javascripts/src/Metamaps.Realtime.js new file mode 100644 index 00000000..b43a9b96 --- /dev/null +++ b/app/assets/javascripts/src/Metamaps.Realtime.js @@ -0,0 +1,1199 @@ +/* global Metamaps, $ */ + +/* + * Metamaps.Realtime.js + * + * Dependencies: + * - Metamaps.Active + * - Metamaps.Backbone + * - Metamaps.Backbone + * - Metamaps.Control + * - Metamaps.Erb + * - Metamaps.GlobalUI + * - Metamaps.JIT + * - Metamaps.Map + * - Metamaps.Mapper + * - Metamaps.Mappers + * - Metamaps.Mappings + * - Metamaps.Messages + * - Metamaps.Synapses + * - Metamaps.Topic + * - Metamaps.Topics + * - Metamaps.Util + * - Metamaps.Views + * - Metamaps.Visualize + */ + +Metamaps.Realtime = { + videoId: 'video-wrapper', + socket: null, + webrtc: null, + readyToCall: false, + mappersOnMap: {}, + disconnected: false, + chatOpen: false, + status: true, // stores whether realtime is True/On or False/Off, + broadcastingStatus: false, + inConversation: false, + localVideo: null, + init: function () { + var self = Metamaps.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() + } else self.disconnected = false + }) + self.socket.on('disconnect', function () { + self.disconnected = true + }) + + if (Metamaps.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: Metamaps.Active.Mapper.id + }) + + var $video = $('').attr('id', self.videoId) + self.localVideo = { + $video: $video, + view: new Metamaps.Views.videoView($video[0], $('body'), 'me', true, { + DOUBLE_CLICK_TOLERANCE: 200, + avatar: Metamaps.Active.Mapper ? Metamaps.Active.Mapper.get('image') : '' + }) + } + + self.room = new Metamaps.Views.room({ + webrtc: self.webrtc, + socket: self.socket, + username: Metamaps.Active.Mapper ? Metamaps.Active.Mapper.get('name') : '', + image: Metamaps.Active.Mapper ? Metamaps.Active.Mapper.get('image') : '', + room: 'global', + $video: self.localVideo.$video, + myVideoView: self.localVideo.view, + config: { DOUBLE_CLICK_TOLERANCE: 200 } + }) + self.room.videoAdded(self.handleVideoAdded) + + self.room.chat.$container.hide() + $('body').prepend(self.room.chat.$container) + } // if Metamaps.Active.Mapper + }, + addJuntoListeners: function () { + var self = Metamaps.Realtime + + $(document).on(Metamaps.Views.chatView.events.openTray, function () { + $('.main').addClass('compressed') + self.chatOpen = true + self.positionPeerIcons() + }) + $(document).on(Metamaps.Views.chatView.events.closeTray, function () { + $('.main').removeClass('compressed') + self.chatOpen = false + self.positionPeerIcons() + }) + $(document).on(Metamaps.Views.chatView.events.videosOn, function () { + $('#wrapper').removeClass('hideVideos') + }) + $(document).on(Metamaps.Views.chatView.events.videosOff, function () { + $('#wrapper').addClass('hideVideos') + }) + $(document).on(Metamaps.Views.chatView.events.cursorsOn, function () { + $('#wrapper').removeClass('hideCursors') + }) + $(document).on(Metamaps.Views.chatView.events.cursorsOff, function () { + $('#wrapper').addClass('hideCursors') + }) + }, + handleVideoAdded: function (v, id) { + var self = Metamaps.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 = Metamaps.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 = Metamaps.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 = Metamaps.Realtime + + if (Metamaps.Active.Map && Metamaps.Active.Mapper) { + if (Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)) { + self.turnOn() + self.setupSocket() + } else { + self.attachMapListener() + } + self.room.addMessages(new Metamaps.Backbone.MessageCollection(Metamaps.Messages), true) + } + }, + endActiveMap: function () { + var self = Metamaps.Realtime + + $(document).off('mousemove') + self.socket.removeAllListeners() + if (self.inConversation) self.leaveCall() + self.socket.emit('endMapperNotify') + $('.collabCompass').remove() + self.status = false + self.room.leave() + self.room.chat.$container.hide() + self.room.chat.close() + }, + turnOn: function (notify) { + var self = Metamaps.Realtime + + if (notify) self.sendRealtimeOn() + self.status = true + $('.collabCompass').show() + self.room.chat.$container.show() + self.room.room = 'map-' + Metamaps.Active.Map.id + self.checkForACallToJoin() + + self.activeMapper = { + id: Metamaps.Active.Mapper.id, + name: Metamaps.Active.Mapper.get('name'), + username: Metamaps.Active.Mapper.get('name'), + image: Metamaps.Active.Mapper.get('image'), + color: Metamaps.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 = Metamaps.Realtime + self.socket.emit('checkForCall', { room: self.room.room, mapid: Metamaps.Active.Map.id }) + }, + promptToJoin: function () { + var self = Metamaps.Realtime + + var notifyText = "There's a conversation happening, want to join?" + notifyText += ' ' + notifyText += ' ' + Metamaps.GlobalUI.notifyUser(notifyText, true) + self.room.conversationInProgress() + }, + conversationHasBegun: function () { + var self = Metamaps.Realtime + + if (self.inConversation) return + var notifyText = "There's a conversation starting, want to join?" + notifyText += ' ' + notifyText += ' ' + Metamaps.GlobalUI.notifyUser(notifyText, true) + self.room.conversationInProgress() + }, + countOthersInConversation: function () { + var self = Metamaps.Realtime + var count = 0 + + for (var key in self.mappersOnMap) { + if (self.mappersOnMap[key].inConversation) count++ + } + return count + }, + mapperJoinedCall: function (id) { + var self = Metamaps.Realtime + var mapper = self.mappersOnMap[id] + + if (mapper) { + if (self.inConversation) { + var username = mapper.name + var notifyText = username + ' joined the call' + Metamaps.GlobalUI.notifyUser(notifyText) + } + + mapper.inConversation = true + self.room.chat.mapperJoinedCall(id) + } + }, + mapperLeftCall: function (id) { + var self = Metamaps.Realtime + var mapper = self.mappersOnMap[id] + + if (mapper) { + if (self.inConversation) { + var username = mapper.name + var notifyText = username + ' left the call' + Metamaps.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 = Metamaps.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 = Metamaps.Realtime + + self.room.chat.sound.stop('sessioninvite') + 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 += ' ' + Metamaps.GlobalUI.notifyUser(notifyText, true) + }, + invitedToJoin: function (inviter) { + var self = Metamaps.Realtime + + self.room.chat.sound.stop('sessioninvite') + self.room.chat.sound.play('sessioninvite') + + var username = self.mappersOnMap[inviter].name + var notifyText = username + ' is inviting you to the conversation. Join?' + notifyText += ' ' + notifyText += ' ' + Metamaps.GlobalUI.notifyUser(notifyText, true) + }, + acceptCall: function (userid) { + var self = Metamaps.Realtime + self.room.chat.sound.stop('sessioninvite') + self.socket.emit('callAccepted', { + mapid: Metamaps.Active.Map.id, + invited: Metamaps.Active.Mapper.id, + inviter: userid + }) + $.post('/maps/' + Metamaps.Active.Map.id + '/events/conversation') + self.joinCall() + Metamaps.GlobalUI.clearNotify() + }, + denyCall: function (userid) { + var self = Metamaps.Realtime + self.room.chat.sound.stop('sessioninvite') + self.socket.emit('callDenied', { + mapid: Metamaps.Active.Map.id, + invited: Metamaps.Active.Mapper.id, + inviter: userid + }) + Metamaps.GlobalUI.clearNotify() + }, + denyInvite: function (userid) { + var self = Metamaps.Realtime + self.room.chat.sound.stop('sessioninvite') + self.socket.emit('inviteDenied', { + mapid: Metamaps.Active.Map.id, + invited: Metamaps.Active.Mapper.id, + inviter: userid + }) + Metamaps.GlobalUI.clearNotify() + }, + inviteACall: function (userid) { + var self = Metamaps.Realtime + self.socket.emit('inviteACall', { + mapid: Metamaps.Active.Map.id, + inviter: Metamaps.Active.Mapper.id, + invited: userid + }) + self.room.chat.invitationPending(userid) + Metamaps.GlobalUI.clearNotify() + }, + inviteToJoin: function (userid) { + var self = Metamaps.Realtime + self.socket.emit('inviteToJoin', { + mapid: Metamaps.Active.Map.id, + inviter: Metamaps.Active.Mapper.id, + invited: userid + }) + self.room.chat.invitationPending(userid) + }, + callAccepted: function (userid) { + var self = Metamaps.Realtime + + var username = self.mappersOnMap[userid].name + Metamaps.GlobalUI.notifyUser('Conversation starting...') + self.joinCall() + self.room.chat.invitationAnswered(userid) + }, + callDenied: function (userid) { + var self = Metamaps.Realtime + + var username = self.mappersOnMap[userid].name + Metamaps.GlobalUI.notifyUser(username + " didn't accept your invitation") + self.room.chat.invitationAnswered(userid) + }, + inviteDenied: function (userid) { + var self = Metamaps.Realtime + + var username = self.mappersOnMap[userid].name + Metamaps.GlobalUI.notifyUser(username + " didn't accept your invitation") + self.room.chat.invitationAnswered(userid) + }, + joinCall: function () { + var self = Metamaps.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 && self.status) { + $('#wrapper').append(self.localVideo.view.$container) + } + self.room.join() + }) + self.inConversation = true + self.socket.emit('mapperJoinedCall', { + mapid: Metamaps.Active.Map.id, + id: Metamaps.Active.Mapper.id + }) + self.webrtc.startLocalVideo() + Metamaps.GlobalUI.clearNotify() + self.room.chat.mapperJoinedCall(Metamaps.Active.Mapper.id) + }, + leaveCall: function () { + var self = Metamaps.Realtime + + self.socket.emit('mapperLeftCall', { + mapid: Metamaps.Active.Map.id, + id: Metamaps.Active.Mapper.id + }) + + self.room.chat.mapperLeftCall(Metamaps.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() + } + }, + turnOff: function (silent) { + var self = Metamaps.Realtime + + if (self.status) { + if (!silent) self.sendRealtimeOff() + // $(".rtMapperSelf").removeClass('littleRtOn').addClass('littleRtOff') + // $('.rtOn').removeClass('active') + // $('.rtOff').addClass('active') + self.status = false + // $(".sidebarCollaborateIcon").removeClass("blue") + $('.collabCompass').hide() + $('#' + self.videoId).remove() + } + }, + setupSocket: function () { + var self = Metamaps.Realtime + var socket = Metamaps.Realtime.socket + var myId = Metamaps.Active.Mapper.id + + socket.emit('newMapperNotify', { + userid: myId, + username: Metamaps.Active.Mapper.get('name'), + userimage: Metamaps.Active.Mapper.get('image'), + mapid: Metamaps.Active.Map.id + }) + + socket.on(myId + '-' + Metamaps.Active.Map.id + '-invitedToCall', self.invitedToCall) // new call + socket.on(myId + '-' + Metamaps.Active.Map.id + '-invitedToJoin', self.invitedToJoin) // call already in progress + socket.on(myId + '-' + Metamaps.Active.Map.id + '-callAccepted', self.callAccepted) + socket.on(myId + '-' + Metamaps.Active.Map.id + '-callDenied', self.callDenied) + socket.on(myId + '-' + Metamaps.Active.Map.id + '-inviteDenied', self.inviteDenied) + + // receive word that there's a conversation in progress + socket.on('maps-' + Metamaps.Active.Map.id + '-callInProgress', self.promptToJoin) + socket.on('maps-' + Metamaps.Active.Map.id + '-callStarting', self.conversationHasBegun) + + socket.on('maps-' + Metamaps.Active.Map.id + '-mapperJoinedCall', self.mapperJoinedCall) + socket.on('maps-' + Metamaps.Active.Map.id + '-mapperLeftCall', self.mapperLeftCall) + + // if you're the 'new guy' update your list with who's already online + socket.on(myId + '-' + Metamaps.Active.Map.id + '-UpdateMapperList', self.updateMapperList) + + // receive word that there's a new mapper on the map + socket.on('maps-' + Metamaps.Active.Map.id + '-newmapper', self.newPeerOnMap) + + // receive word that a mapper left the map + socket.on('maps-' + Metamaps.Active.Map.id + '-lostmapper', self.lostPeerOnMap) + + // receive word that there's a mapper turned on realtime + socket.on('maps-' + Metamaps.Active.Map.id + '-newrealtime', self.newCollaborator) + + // receive word that there's a mapper turned on realtime + socket.on('maps-' + Metamaps.Active.Map.id + '-lostrealtime', self.lostCollaborator) + + // + socket.on('maps-' + Metamaps.Active.Map.id + '-topicDrag', self.topicDrag) + + // + socket.on('maps-' + Metamaps.Active.Map.id + '-newTopic', self.newTopic) + + // + socket.on('maps-' + Metamaps.Active.Map.id + '-newMessage', self.newMessage) + + // + socket.on('maps-' + Metamaps.Active.Map.id + '-removeTopic', self.removeTopic) + + // + socket.on('maps-' + Metamaps.Active.Map.id + '-newSynapse', self.newSynapse) + + // + socket.on('maps-' + Metamaps.Active.Map.id + '-removeSynapse', self.removeSynapse) + + // update mapper compass position + socket.on('maps-' + Metamaps.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 = Metamaps.Util.pixelsToCoords(pixels) + self.sendCoords(coords) + } + $(document).mousemove(sendCoords) + + var zoom = function (event, e) { + if (e) { + var pixels = { + x: e.pageX, + y: e.pageY + } + var coords = Metamaps.Util.pixelsToCoords(pixels) + self.sendCoords(coords) + } + self.positionPeerIcons() + } + $(document).on(Metamaps.JIT.events.zoom, zoom) + + $(document).on(Metamaps.JIT.events.pan, self.positionPeerIcons) + + var sendTopicDrag = function (event, positions) { + self.sendTopicDrag(positions) + } + $(document).on(Metamaps.JIT.events.topicDrag, sendTopicDrag) + + var sendNewTopic = function (event, data) { + self.sendNewTopic(data) + } + $(document).on(Metamaps.JIT.events.newTopic, sendNewTopic) + + var sendDeleteTopic = function (event, data) { + self.sendDeleteTopic(data) + } + $(document).on(Metamaps.JIT.events.deleteTopic, sendDeleteTopic) + + var sendRemoveTopic = function (event, data) { + self.sendRemoveTopic(data) + } + $(document).on(Metamaps.JIT.events.removeTopic, sendRemoveTopic) + + var sendNewSynapse = function (event, data) { + self.sendNewSynapse(data) + } + $(document).on(Metamaps.JIT.events.newSynapse, sendNewSynapse) + + var sendDeleteSynapse = function (event, data) { + self.sendDeleteSynapse(data) + } + $(document).on(Metamaps.JIT.events.deleteSynapse, sendDeleteSynapse) + + var sendRemoveSynapse = function (event, data) { + self.sendRemoveSynapse(data) + } + $(document).on(Metamaps.JIT.events.removeSynapse, sendRemoveSynapse) + + var sendNewMessage = function (event, data) { + self.sendNewMessage(data) + } + $(document).on(Metamaps.Views.room.events.newMessage, sendNewMessage) + }, + attachMapListener: function () { + var self = Metamaps.Realtime + var socket = Metamaps.Realtime.socket + + socket.on('mapChangeFromServer', self.mapChange) + }, + sendRealtimeOn: function () { + var self = Metamaps.Realtime + var socket = Metamaps.Realtime.socket + + // send this new mapper back your details, and the awareness that you're online + var update = { + username: Metamaps.Active.Mapper.get('name'), + userid: Metamaps.Active.Mapper.id, + mapid: Metamaps.Active.Map.id + } + socket.emit('notifyStartRealtime', update) + }, + sendRealtimeOff: function () { + var self = Metamaps.Realtime + var socket = Metamaps.Realtime.socket + + // send this new mapper back your details, and the awareness that you're online + var update = { + username: Metamaps.Active.Mapper.get('name'), + userid: Metamaps.Active.Mapper.id, + mapid: Metamaps.Active.Map.id + } + socket.emit('notifyStopRealtime', update) + }, + updateMapperList: function (data) { + var self = Metamaps.Realtime + var socket = Metamaps.Realtime.socket + + // data.userid + // data.username + // data.userimage + // data.userrealtime + + self.mappersOnMap[data.userid] = { + id: data.userid, + name: data.username, + username: data.username, + image: data.userimage, + color: Metamaps.Util.getPastelColor(), + realtime: data.userrealtime, + inConversation: data.userinconversation, + coords: { + x: 0, + y: 0 + } + } + + if (data.userid !== Metamaps.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, !self.status) + } + }, + newPeerOnMap: function (data) { + var self = Metamaps.Realtime + var socket = Metamaps.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: Metamaps.Util.getPastelColor(), + realtime: true, + coords: { + x: 0, + y: 0 + }, + } + + // create an item for them in the realtime box + if (data.userid !== Metamaps.Active.Mapper.id && self.status) { + 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, !self.status) + + var notifyMessage = data.username + ' just joined the map' + if (firstOtherPerson) { + notifyMessage += ' ' + } + Metamaps.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: Metamaps.Active.Mapper.get('name'), + userimage: Metamaps.Active.Mapper.get('image'), + userid: Metamaps.Active.Mapper.id, + userrealtime: self.status, + userinconversation: self.inConversation, + mapid: Metamaps.Active.Map.id + } + socket.emit('updateNewMapperList', update) + } + }, + createCompass: function (name, id, image, color, hide) { + var str = '

' + name + '

' + str += '
' + $('#compass' + id).remove() + $('
', { + id: 'compass' + id, + class: 'collabCompass' + }).html(str).appendTo('#wrapper') + if (hide) { + $('#compass' + id).hide() + } + $('#compass' + id + ' img').css({ + 'border': '2px solid ' + color + }) + $('#compass' + id + ' p').css({ + 'background-color': color + }) + }, + lostPeerOnMap: function (data) { + var self = Metamaps.Realtime + var socket = Metamaps.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) + + Metamaps.GlobalUI.notifyUser(data.username + ' just left the map') + + if ((self.inConversation && self.countOthersInConversation() === 0) || + (!self.inConversation && self.countOthersInConversation() === 1)) { + self.callEnded() + } + }, + newCollaborator: function (data) { + var self = Metamaps.Realtime + var socket = Metamaps.Realtime.socket + + // data.userid + // data.username + + self.mappersOnMap[data.userid].realtime = true + + // $('#mapper' + data.userid).removeClass('littleRtOff').addClass('littleRtOn') + $('#compass' + data.userid).show() + + Metamaps.GlobalUI.notifyUser(data.username + ' just turned on realtime') + }, + lostCollaborator: function (data) { + var self = Metamaps.Realtime + var socket = Metamaps.Realtime.socket + + // data.userid + // data.username + + self.mappersOnMap[data.userid].realtime = false + + // $('#mapper' + data.userid).removeClass('littleRtOn').addClass('littleRtOff') + $('#compass' + data.userid).hide() + + Metamaps.GlobalUI.notifyUser(data.username + ' just turned off realtime') + }, + updatePeerCoords: function (data) { + var self = Metamaps.Realtime + var socket = Metamaps.Realtime.socket + + self.mappersOnMap[data.userid].coords = {x: data.usercoords.x,y: data.usercoords.y} + self.positionPeerIcon(data.userid) + }, + positionPeerIcons: function () { + var self = Metamaps.Realtime + var socket = Metamaps.Realtime.socket + + if (self.status) { // if i have realtime turned on + for (var key in self.mappersOnMap) { + var mapper = self.mappersOnMap[key] + if (mapper.realtime) { + self.positionPeerIcon(key) + } + } + } + }, + positionPeerIcon: function (id) { + var self = Metamaps.Realtime + var socket = Metamaps.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 = Metamaps.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 = Metamaps.Realtime + var socket = Metamaps.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 = Metamaps.Realtime + var socket = Metamaps.Realtime.socket + + var map = Metamaps.Active.Map + var mapper = Metamaps.Active.Mapper + + if (self.status && map.authorizeToEdit(mapper) && socket) { + var update = { + usercoords: coords, + userid: Metamaps.Active.Mapper.id, + mapid: Metamaps.Active.Map.id + } + socket.emit('updateMapperCoords', update) + } + }, + sendTopicDrag: function (positions) { + var self = Metamaps.Realtime + var socket = self.socket + + if (Metamaps.Active.Map && self.status) { + positions.mapid = Metamaps.Active.Map.id + socket.emit('topicDrag', positions) + } + }, + topicDrag: function (positions) { + var self = Metamaps.Realtime + var socket = self.socket + + var topic + var node + + if (Metamaps.Active.Map && self.status) { + 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 + Metamaps.Visualize.mGraph.plot() + } + }, + sendTopicChange: function (topic) { + var self = Metamaps.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 = Metamaps.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 = Metamaps.Realtime + var socket = self.socket + + var data = { + mapId: map.id + } + + socket.emit('mapChangeFromClient', data) + }, + mapChange: function (data) { + var map = Metamaps.Active.Map + var isActiveMap = map && data.mapId === map.id + if (isActiveMap) { + var couldEditBefore = map.authorizeToEdit(Metamaps.Active.Mapper) + var idBefore = map.id + map.fetch({ + success: function (model, response) { + var idNow = model.id + var canEditNow = model.authorizeToEdit(Metamaps.Active.Mapper) + if (idNow !== idBefore) { + Metamaps.Map.leavePrivateMap() // this means the map has been changed to private + } + else if (couldEditBefore && !canEditNow) { + Metamaps.Map.cantEditNow() + } + else if (!couldEditBefore && canEditNow) { + Metamaps.Map.canEditNow() + } else { + model.fetchContained() + model.trigger('changeByOther') + } + } + }) + } + }, + // newMessage + sendNewMessage: function (data) { + var self = Metamaps.Realtime + var socket = self.socket + + var message = data.attributes + message.mapid = Metamaps.Active.Map.id + socket.emit('newMessage', message) + }, + newMessage: function (data) { + var self = Metamaps.Realtime + var socket = self.socket + + self.room.addMessages(new Metamaps.Backbone.MessageCollection(data)) + }, + // newTopic + sendNewTopic: function (data) { + var self = Metamaps.Realtime + var socket = self.socket + + if (Metamaps.Active.Map && self.status) { + data.mapperid = Metamaps.Active.Mapper.id + data.mapid = Metamaps.Active.Map.id + socket.emit('newTopic', data) + } + }, + newTopic: function (data) { + var topic, mapping, mapper, mapperCallback, cancel + + var self = Metamaps.Realtime + var socket = self.socket + + if (!self.status) return + + function waitThenRenderTopic () { + if (topic && mapping && mapper) { + Metamaps.Topic.renderTopic(mapping, topic, false, false) + } + else if (!cancel) { + setTimeout(waitThenRenderTopic, 10) + } + } + + mapper = Metamaps.Mappers.get(data.mapperid) + if (mapper === undefined) { + mapperCallback = function (m) { + Metamaps.Mappers.add(m) + mapper = m + } + Metamaps.Mapper.get(data.mapperid, mapperCallback) + } + $.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 = Metamaps.Realtime + var socket = self.socket + + if (Metamaps.Active.Map) { + socket.emit('deleteTopicFromClient', data) + } + }, + // removeTopic + sendRemoveTopic: function (data) { + var self = Metamaps.Realtime + var socket = self.socket + + if (Metamaps.Active.Map) { + data.mapid = Metamaps.Active.Map.id + socket.emit('removeTopic', data) + } + }, + removeTopic: function (data) { + var self = Metamaps.Realtime + var socket = self.socket + + if (!self.status) return + + var topic = Metamaps.Topics.get(data.mappableid) + if (topic) { + var node = topic.get('node') + var mapping = topic.getMapping() + Metamaps.Control.hideNode(node.id) + Metamaps.Topics.remove(topic) + Metamaps.Mappings.remove(mapping) + } + }, + // newSynapse + sendNewSynapse: function (data) { + var self = Metamaps.Realtime + var socket = self.socket + + if (Metamaps.Active.Map) { + data.mapperid = Metamaps.Active.Mapper.id + data.mapid = Metamaps.Active.Map.id + socket.emit('newSynapse', data) + } + }, + newSynapse: function (data) { + var topic1, topic2, node1, node2, synapse, mapping, cancel + + var self = Metamaps.Realtime + var socket = self.socket + + if (!self.status) return + + function waitThenRenderSynapse () { + if (synapse && mapping && mapper) { + topic1 = synapse.getTopic1() + node1 = topic1.get('node') + topic2 = synapse.getTopic2() + node2 = topic2.get('node') + + Metamaps.Synapse.renderSynapse(mapping, synapse, node1, node2, false) + } + else if (!cancel) { + setTimeout(waitThenRenderSynapse, 10) + } + } + + mapper = Metamaps.Mappers.get(data.mapperid) + if (mapper === undefined) { + mapperCallback = function (m) { + Metamaps.Mappers.add(m) + mapper = m + } + Metamaps.Mapper.get(data.mapperid, mapperCallback) + } + $.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 = Metamaps.Realtime + var socket = self.socket + + if (Metamaps.Active.Map) { + data.mapid = Metamaps.Active.Map.id + socket.emit('deleteSynapseFromClient', data) + } + }, + // removeSynapse + sendRemoveSynapse: function (data) { + var self = Metamaps.Realtime + var socket = self.socket + + if (Metamaps.Active.Map) { + data.mapid = Metamaps.Active.Map.id + socket.emit('removeSynapse', data) + } + }, + removeSynapse: function (data) { + var self = Metamaps.Realtime + var socket = self.socket + + if (!self.status) return + + 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) { + Metamaps.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) + } + }, +}; // end Metamaps.Realtime diff --git a/app/assets/javascripts/src/Metamaps.js.erb b/app/assets/javascripts/src/Metamaps.js.erb index 77d5cbef..d656ba2e 100644 --- a/app/assets/javascripts/src/Metamaps.js.erb +++ b/app/assets/javascripts/src/Metamaps.js.erb @@ -1,7 +1,5 @@ // TODO document this user agent function - var labelType, useGradients, nativeTextSupport, animate; - (function () { var ua = navigator.userAgent, iStuff = ua.match(/iPhone/i) || ua.match(/iPad/i), @@ -22,6 +20,9 @@ Metamaps.tempNode = null; Metamaps.tempInit = false; Metamaps.tempNode2 = null; Metamaps.VERSION = '<%= METAMAPS_VERSION %>' +Metamaps.Erb = {} +Metamaps.Erb['REALTIME_SERVER'] = '<%= ENV['REALTIME_SERVER'] %>' +Metamaps.Erb['junto_spinner_darkgrey.gif'] = '<%= asset_path('junto_spinner_darkgrey.gif') %>' Metamaps.Settings = { embed: false, // indicates that the app is on a page that is optimized for embedding in iFrames on other web pages @@ -1928,1190 +1929,3 @@ Metamaps.Util = { return (url.match(/^https?:\/\/(?:www\.)?youtube.com\/watch\?(?=[^?]*v=\w+)(?:[^\s?]+)?$/) != null); } }; // end Metamaps.Util - -/* - * - * REALTIME - * - */ -Metamaps.Realtime = { - videoId: 'video-wrapper', - socket: null, - webrtc: null, - readyToCall: false, - mappersOnMap: {}, - disconnected: false, - chatOpen: false, - status: true, // stores whether realtime is True/On or False/Off, - broadcastingStatus: false, - inConversation: false, - localVideo: null, - init: function () { - var self = Metamaps.Realtime; - - self.addJuntoListeners(); - - self.socket = new SocketIoConnection({ url: '<%= ENV['REALTIME_SERVER'] %>' }); - self.socket.on('connect', function () { - console.log('connected'); - if (!self.disconnected) { - self.startActiveMap(); - } else self.disconnected = false; - }); - self.socket.on('disconnect', function () { - self.disconnected = true; - }); - - if (Metamaps.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: Metamaps.Active.Mapper.id - }); - - var - $video = $('').attr('id', self.videoId); - self.localVideo = { - $video: $video, - view: new Metamaps.Views.videoView($video[0], $('body'), 'me', true, { - DOUBLE_CLICK_TOLERANCE: 200, - avatar: Metamaps.Active.Mapper ? Metamaps.Active.Mapper.get('image') : '' - }) - }; - - self.room = new Metamaps.Views.room({ - webrtc: self.webrtc, - socket: self.socket, - username: Metamaps.Active.Mapper ? Metamaps.Active.Mapper.get('name') : '', - image: Metamaps.Active.Mapper ? Metamaps.Active.Mapper.get('image') : '', - room: 'global', - $video: self.localVideo.$video, - myVideoView: self.localVideo.view, - config: { DOUBLE_CLICK_TOLERANCE: 200 } - }); - self.room.videoAdded(self.handleVideoAdded); - - self.room.chat.$container.hide(); - $('body').prepend(self.room.chat.$container); - } // if Metamaps.Active.Mapper - }, - addJuntoListeners: function () { - var self = Metamaps.Realtime; - - $(document).on(Metamaps.Views.chatView.events.openTray, function () { - $('.main').addClass('compressed'); - self.chatOpen = true; - self.positionPeerIcons(); - }); - $(document).on(Metamaps.Views.chatView.events.closeTray, function () { - $('.main').removeClass('compressed'); - self.chatOpen = false; - self.positionPeerIcons(); - }); - $(document).on(Metamaps.Views.chatView.events.videosOn, function () { - $('#wrapper').removeClass('hideVideos'); - }); - $(document).on(Metamaps.Views.chatView.events.videosOff, function () { - $('#wrapper').addClass('hideVideos'); - }); - $(document).on(Metamaps.Views.chatView.events.cursorsOn, function () { - $('#wrapper').removeClass('hideCursors'); - }); - $(document).on(Metamaps.Views.chatView.events.cursorsOff, function () { - $('#wrapper').addClass('hideCursors'); - }); - }, - handleVideoAdded: function (v, id) { - var self = Metamaps.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 = Metamaps.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 = Metamaps.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 = Metamaps.Realtime; - - if (Metamaps.Active.Map && Metamaps.Active.Mapper) { - - if (Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper)) { - self.turnOn(); - self.setupSocket(); - } - else { - self.attachMapListener(); - } - self.room.addMessages(new Metamaps.Backbone.MessageCollection(Metamaps.Messages), true); - } - }, - endActiveMap: function () { - var self = Metamaps.Realtime; - - $(document).off('mousemove'); - self.socket.removeAllListeners(); - if (self.inConversation) self.leaveCall(); - self.socket.emit('endMapperNotify'); - $(".collabCompass").remove(); - self.status = false; - self.room.leave(); - self.room.chat.$container.hide(); - self.room.chat.close(); - }, - turnOn: function (notify) { - var self = Metamaps.Realtime; - - if (notify) self.sendRealtimeOn(); - self.status = true; - $(".collabCompass").show(); - self.room.chat.$container.show(); - self.room.room = 'map-' + Metamaps.Active.Map.id; - self.checkForACallToJoin(); - - self.activeMapper = { - id: Metamaps.Active.Mapper.id, - name: Metamaps.Active.Mapper.get('name'), - username: Metamaps.Active.Mapper.get('name'), - image: Metamaps.Active.Mapper.get('image'), - color: Metamaps.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 = Metamaps.Realtime; - self.socket.emit('checkForCall', { room: self.room.room, mapid: Metamaps.Active.Map.id }); - }, - promptToJoin: function () { - var self = Metamaps.Realtime; - - var notifyText = 'There\'s a conversation happening, want to join?'; - notifyText += ' '; - notifyText += ' '; - Metamaps.GlobalUI.notifyUser(notifyText, true); - self.room.conversationInProgress(); - }, - conversationHasBegun: function () { - var self = Metamaps.Realtime; - - if (self.inConversation) return; - var notifyText = 'There\'s a conversation starting, want to join?'; - notifyText += ' '; - notifyText += ' '; - Metamaps.GlobalUI.notifyUser(notifyText, true); - self.room.conversationInProgress(); - }, - countOthersInConversation: function () { - var self = Metamaps.Realtime; - var count = 0; - - for (var key in self.mappersOnMap) { - if (self.mappersOnMap[key].inConversation) count++; - } - return count; - }, - mapperJoinedCall: function (id) { - var self = Metamaps.Realtime; - var mapper = self.mappersOnMap[id]; - - if (mapper) { - if (self.inConversation) { - var username = mapper.name; - var notifyText = username + ' joined the call'; - Metamaps.GlobalUI.notifyUser(notifyText); - } - - mapper.inConversation = true; - self.room.chat.mapperJoinedCall(id); - } - }, - mapperLeftCall: function (id) { - var self = Metamaps.Realtime; - var mapper = self.mappersOnMap[id]; - - if (mapper) { - if (self.inConversation) { - var username = mapper.name; - var notifyText = username + ' left the call'; - Metamaps.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 = Metamaps.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 = Metamaps.Realtime; - - self.room.chat.sound.stop('sessioninvite'); - self.room.chat.sound.play('sessioninvite'); - - var username = self.mappersOnMap[inviter].name; - var notifyText = "' style='display: inline-block; margin-top: -12px; vertical-align: top;' />"; - notifyText += username + ' is inviting you to a conversation. Join live?'; - notifyText += ' '; - notifyText += ' '; - Metamaps.GlobalUI.notifyUser(notifyText, true); - }, - invitedToJoin: function (inviter) { - var self = Metamaps.Realtime; - - self.room.chat.sound.stop('sessioninvite'); - self.room.chat.sound.play('sessioninvite'); - - var username = self.mappersOnMap[inviter].name; - var notifyText = username + ' is inviting you to the conversation. Join?'; - notifyText += ' '; - notifyText += ' '; - Metamaps.GlobalUI.notifyUser(notifyText, true); - }, - acceptCall: function (userid) { - var self = Metamaps.Realtime; - self.room.chat.sound.stop('sessioninvite'); - self.socket.emit('callAccepted', { - mapid: Metamaps.Active.Map.id, - invited: Metamaps.Active.Mapper.id, - inviter: userid - }); - $.post('/maps/' + Metamaps.Active.Map.id + '/events/conversation'); - self.joinCall(); - Metamaps.GlobalUI.clearNotify(); - }, - denyCall: function (userid) { - var self = Metamaps.Realtime; - self.room.chat.sound.stop('sessioninvite'); - self.socket.emit('callDenied', { - mapid: Metamaps.Active.Map.id, - invited: Metamaps.Active.Mapper.id, - inviter: userid - }); - Metamaps.GlobalUI.clearNotify(); - }, - denyInvite: function (userid) { - var self = Metamaps.Realtime; - self.room.chat.sound.stop('sessioninvite'); - self.socket.emit('inviteDenied', { - mapid: Metamaps.Active.Map.id, - invited: Metamaps.Active.Mapper.id, - inviter: userid - }); - Metamaps.GlobalUI.clearNotify(); - }, - inviteACall: function (userid) { - var self = Metamaps.Realtime; - self.socket.emit('inviteACall', { - mapid: Metamaps.Active.Map.id, - inviter: Metamaps.Active.Mapper.id, - invited: userid - }); - self.room.chat.invitationPending(userid); - Metamaps.GlobalUI.clearNotify(); - }, - inviteToJoin: function (userid) { - var self = Metamaps.Realtime; - self.socket.emit('inviteToJoin', { - mapid: Metamaps.Active.Map.id, - inviter: Metamaps.Active.Mapper.id, - invited: userid - }); - self.room.chat.invitationPending(userid); - }, - callAccepted: function (userid) { - var self = Metamaps.Realtime; - - var username = self.mappersOnMap[userid].name; - Metamaps.GlobalUI.notifyUser('Conversation starting...'); - self.joinCall(); - self.room.chat.invitationAnswered(userid); - }, - callDenied: function (userid) { - var self = Metamaps.Realtime; - - var username = self.mappersOnMap[userid].name; - Metamaps.GlobalUI.notifyUser(username + ' didn\'t accept your invitation'); - self.room.chat.invitationAnswered(userid); - }, - inviteDenied: function (userid) { - var self = Metamaps.Realtime; - - var username = self.mappersOnMap[userid].name; - Metamaps.GlobalUI.notifyUser(username + ' didn\'t accept your invitation'); - self.room.chat.invitationAnswered(userid); - }, - joinCall: function () { - var self = Metamaps.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 && self.status) { - $('#wrapper').append(self.localVideo.view.$container); - } - self.room.join(); - }); - self.inConversation = true; - self.socket.emit('mapperJoinedCall', { - mapid: Metamaps.Active.Map.id, - id: Metamaps.Active.Mapper.id - }); - self.webrtc.startLocalVideo(); - Metamaps.GlobalUI.clearNotify(); - self.room.chat.mapperJoinedCall(Metamaps.Active.Mapper.id); - }, - leaveCall: function () { - var self = Metamaps.Realtime; - - self.socket.emit('mapperLeftCall', { - mapid: Metamaps.Active.Map.id, - id: Metamaps.Active.Mapper.id - }); - - self.room.chat.mapperLeftCall(Metamaps.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(); - } - }, - turnOff: function (silent) { - var self = Metamaps.Realtime; - - if (self.status) { - if (!silent) self.sendRealtimeOff(); - //$(".rtMapperSelf").removeClass('littleRtOn').addClass('littleRtOff'); - //$('.rtOn').removeClass('active'); - //$('.rtOff').addClass('active'); - self.status = false; - //$(".sidebarCollaborateIcon").removeClass("blue"); - $(".collabCompass").hide(); - $('#' + self.videoId).remove(); - } - }, - setupSocket: function () { - var self = Metamaps.Realtime; - var socket = Metamaps.Realtime.socket; - var myId = Metamaps.Active.Mapper.id; - - socket.emit('newMapperNotify', { - userid: myId, - username: Metamaps.Active.Mapper.get("name"), - userimage: Metamaps.Active.Mapper.get("image"), - mapid: Metamaps.Active.Map.id - }); - - socket.on(myId + '-' + Metamaps.Active.Map.id + '-invitedToCall', self.invitedToCall); // new call - socket.on(myId + '-' + Metamaps.Active.Map.id + '-invitedToJoin', self.invitedToJoin); // call already in progress - socket.on(myId + '-' + Metamaps.Active.Map.id + '-callAccepted', self.callAccepted); - socket.on(myId + '-' + Metamaps.Active.Map.id + '-callDenied', self.callDenied); - socket.on(myId + '-' + Metamaps.Active.Map.id + '-inviteDenied', self.inviteDenied); - - // receive word that there's a conversation in progress - socket.on('maps-' + Metamaps.Active.Map.id + '-callInProgress', self.promptToJoin); - socket.on('maps-' + Metamaps.Active.Map.id + '-callStarting', self.conversationHasBegun); - - socket.on('maps-' + Metamaps.Active.Map.id + '-mapperJoinedCall', self.mapperJoinedCall); - socket.on('maps-' + Metamaps.Active.Map.id + '-mapperLeftCall', self.mapperLeftCall); - - // if you're the 'new guy' update your list with who's already online - socket.on(myId + '-' + Metamaps.Active.Map.id + '-UpdateMapperList', self.updateMapperList); - - // receive word that there's a new mapper on the map - socket.on('maps-' + Metamaps.Active.Map.id + '-newmapper', self.newPeerOnMap); - - // receive word that a mapper left the map - socket.on('maps-' + Metamaps.Active.Map.id + '-lostmapper', self.lostPeerOnMap); - - // receive word that there's a mapper turned on realtime - socket.on('maps-' + Metamaps.Active.Map.id + '-newrealtime', self.newCollaborator); - - // receive word that there's a mapper turned on realtime - socket.on('maps-' + Metamaps.Active.Map.id + '-lostrealtime', self.lostCollaborator); - - // - socket.on('maps-' + Metamaps.Active.Map.id + '-topicDrag', self.topicDrag); - - // - socket.on('maps-' + Metamaps.Active.Map.id + '-newTopic', self.newTopic); - - // - socket.on('maps-' + Metamaps.Active.Map.id + '-newMessage', self.newMessage); - - // - socket.on('maps-' + Metamaps.Active.Map.id + '-removeTopic', self.removeTopic); - - // - socket.on('maps-' + Metamaps.Active.Map.id + '-newSynapse', self.newSynapse); - - // - socket.on('maps-' + Metamaps.Active.Map.id + '-removeSynapse', self.removeSynapse); - - // update mapper compass position - socket.on('maps-' + Metamaps.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 = Metamaps.Util.pixelsToCoords(pixels); - self.sendCoords(coords); - }; - $(document).mousemove(sendCoords); - - var zoom = function (event, e) { - if (e) { - var pixels = { - x: e.pageX, - y: e.pageY - }; - var coords = Metamaps.Util.pixelsToCoords(pixels); - self.sendCoords(coords); - } - self.positionPeerIcons(); - }; - $(document).on(Metamaps.JIT.events.zoom, zoom); - - $(document).on(Metamaps.JIT.events.pan, self.positionPeerIcons); - - var sendTopicDrag = function (event, positions) { - self.sendTopicDrag(positions); - }; - $(document).on(Metamaps.JIT.events.topicDrag, sendTopicDrag); - - var sendNewTopic = function (event, data) { - self.sendNewTopic(data); - }; - $(document).on(Metamaps.JIT.events.newTopic, sendNewTopic); - - var sendDeleteTopic = function (event, data) { - self.sendDeleteTopic(data); - }; - $(document).on(Metamaps.JIT.events.deleteTopic, sendDeleteTopic); - - var sendRemoveTopic = function (event, data) { - self.sendRemoveTopic(data); - }; - $(document).on(Metamaps.JIT.events.removeTopic, sendRemoveTopic); - - var sendNewSynapse = function (event, data) { - self.sendNewSynapse(data); - }; - $(document).on(Metamaps.JIT.events.newSynapse, sendNewSynapse); - - var sendDeleteSynapse = function (event, data) { - self.sendDeleteSynapse(data); - }; - $(document).on(Metamaps.JIT.events.deleteSynapse, sendDeleteSynapse); - - var sendRemoveSynapse = function (event, data) { - self.sendRemoveSynapse(data); - }; - $(document).on(Metamaps.JIT.events.removeSynapse, sendRemoveSynapse); - - var sendNewMessage = function (event, data) { - self.sendNewMessage(data); - }; - $(document).on(Metamaps.Views.room.events.newMessage, sendNewMessage); - - }, - attachMapListener: function(){ - var self = Metamaps.Realtime; - var socket = Metamaps.Realtime.socket; - - socket.on('mapChangeFromServer', self.mapChange); - }, - sendRealtimeOn: function () { - var self = Metamaps.Realtime; - var socket = Metamaps.Realtime.socket; - - // send this new mapper back your details, and the awareness that you're online - var update = { - username: Metamaps.Active.Mapper.get("name"), - userid: Metamaps.Active.Mapper.id, - mapid: Metamaps.Active.Map.id - }; - socket.emit('notifyStartRealtime', update); - }, - sendRealtimeOff: function () { - var self = Metamaps.Realtime; - var socket = Metamaps.Realtime.socket; - - // send this new mapper back your details, and the awareness that you're online - var update = { - username: Metamaps.Active.Mapper.get("name"), - userid: Metamaps.Active.Mapper.id, - mapid: Metamaps.Active.Map.id - }; - socket.emit('notifyStopRealtime', update); - }, - updateMapperList: function (data) { - var self = Metamaps.Realtime; - var socket = Metamaps.Realtime.socket; - - // data.userid - // data.username - // data.userimage - // data.userrealtime - - self.mappersOnMap[data.userid] = { - id: data.userid, - name: data.username, - username: data.username, - image: data.userimage, - color: Metamaps.Util.getPastelColor(), - realtime: data.userrealtime, - inConversation: data.userinconversation, - coords: { - x: 0, - y: 0 - } - }; - - if (data.userid !== Metamaps.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, !self.status); - } - }, - newPeerOnMap: function (data) { - var self = Metamaps.Realtime; - var socket = Metamaps.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: Metamaps.Util.getPastelColor(), - realtime: true, - coords: { - x: 0, - y: 0 - }, - }; - - // create an item for them in the realtime box - if (data.userid !== Metamaps.Active.Mapper.id && self.status) { - 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, !self.status); - - var notifyMessage = data.username + ' just joined the map'; - if (firstOtherPerson) { - notifyMessage += ' '; - } - Metamaps.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: Metamaps.Active.Mapper.get("name"), - userimage: Metamaps.Active.Mapper.get("image"), - userid: Metamaps.Active.Mapper.id, - userrealtime: self.status, - userinconversation: self.inConversation, - mapid: Metamaps.Active.Map.id - }; - socket.emit('updateNewMapperList', update); - } - }, - createCompass: function(name, id, image, color, hide) { - var str = '

'+name+'

'; - str += '
'; - $('#compass' + id).remove(); - $('
', { - id: 'compass' + id, - class: 'collabCompass' - }).html(str).appendTo('#wrapper'); - if (hide) { - $('#compass' + id).hide(); - } - $('#compass' + id + ' img').css({ - 'border': '2px solid ' + color - }); - $('#compass' + id + ' p').css({ - 'background-color': color - }); - }, - lostPeerOnMap: function (data) { - var self = Metamaps.Realtime; - var socket = Metamaps.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); - - Metamaps.GlobalUI.notifyUser(data.username + ' just left the map'); - - if ((self.inConversation && self.countOthersInConversation() === 0) || - (!self.inConversation && self.countOthersInConversation() === 1)) { - self.callEnded(); - } - }, - newCollaborator: function (data) { - var self = Metamaps.Realtime; - var socket = Metamaps.Realtime.socket; - - // data.userid - // data.username - - self.mappersOnMap[data.userid].realtime = true; - - //$('#mapper' + data.userid).removeClass('littleRtOff').addClass('littleRtOn'); - $('#compass' + data.userid).show(); - - Metamaps.GlobalUI.notifyUser(data.username + ' just turned on realtime'); - }, - lostCollaborator: function (data) { - var self = Metamaps.Realtime; - var socket = Metamaps.Realtime.socket; - - // data.userid - // data.username - - self.mappersOnMap[data.userid].realtime = false; - - //$('#mapper' + data.userid).removeClass('littleRtOn').addClass('littleRtOff'); - $('#compass' + data.userid).hide(); - - Metamaps.GlobalUI.notifyUser(data.username + ' just turned off realtime'); - }, - updatePeerCoords: function (data) { - var self = Metamaps.Realtime; - var socket = Metamaps.Realtime.socket; - - self.mappersOnMap[data.userid].coords={x: data.usercoords.x,y:data.usercoords.y}; - self.positionPeerIcon(data.userid); - }, - positionPeerIcons: function () { - var self = Metamaps.Realtime; - var socket = Metamaps.Realtime.socket; - - if (self.status) { // if i have realtime turned on - for (var key in self.mappersOnMap) { - var mapper = self.mappersOnMap[key]; - if (mapper.realtime) { - self.positionPeerIcon(key); - } - } - } - }, - positionPeerIcon: function (id) { - var self = Metamaps.Realtime; - var socket = Metamaps.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 = Metamaps.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 = Metamaps.Realtime; - var socket = Metamaps.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 = Metamaps.Realtime; - var socket = Metamaps.Realtime.socket; - - var map = Metamaps.Active.Map; - var mapper = Metamaps.Active.Mapper; - - if (self.status && map.authorizeToEdit(mapper) && socket) { - var update = { - usercoords: coords, - userid: Metamaps.Active.Mapper.id, - mapid: Metamaps.Active.Map.id - }; - socket.emit('updateMapperCoords', update); - } - }, - sendTopicDrag: function (positions) { - var self = Metamaps.Realtime; - var socket = self.socket; - - if (Metamaps.Active.Map && self.status) { - positions.mapid = Metamaps.Active.Map.id; - socket.emit('topicDrag', positions); - } - }, - topicDrag: function (positions) { - var self = Metamaps.Realtime; - var socket = self.socket; - - var topic; - var node; - - if (Metamaps.Active.Map && self.status) { - 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 - Metamaps.Visualize.mGraph.plot(); - } - }, - sendTopicChange: function (topic) { - var self = Metamaps.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 = Metamaps.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 = Metamaps.Realtime; - var socket = self.socket; - - var data = { - mapId: map.id - } - - socket.emit('mapChangeFromClient', data); - }, - mapChange: function (data) { - var map = Metamaps.Active.Map; - var isActiveMap = map && data.mapId === map.id; - if (isActiveMap) { - var couldEditBefore = map.authorizeToEdit(Metamaps.Active.Mapper) - var idBefore = map.id; - map.fetch({ - success: function (model, response) { - - var idNow = model.id; - var canEditNow = model.authorizeToEdit(Metamaps.Active.Mapper); - if (idNow !== idBefore) { - Metamaps.Map.leavePrivateMap(); // this means the map has been changed to private - } - else if (couldEditBefore && !canEditNow) { - Metamaps.Map.cantEditNow() - } - else if (!couldEditBefore && canEditNow) { - Metamaps.Map.canEditNow() - } - else { - model.fetchContained(); - model.trigger('changeByOther'); - } - } - }); - } - }, - // newMessage - sendNewMessage: function (data) { - var self = Metamaps.Realtime; - var socket = self.socket; - - var message = data.attributes; - message.mapid = Metamaps.Active.Map.id; - socket.emit('newMessage', message); - }, - newMessage: function (data) { - var self = Metamaps.Realtime; - var socket = self.socket; - - self.room.addMessages(new Metamaps.Backbone.MessageCollection(data)); - }, - // newTopic - sendNewTopic: function (data) { - var self = Metamaps.Realtime; - var socket = self.socket; - - if (Metamaps.Active.Map && self.status) { - data.mapperid = Metamaps.Active.Mapper.id; - data.mapid = Metamaps.Active.Map.id; - socket.emit('newTopic', data); - } - }, - newTopic: function (data) { - var topic, mapping, mapper, mapperCallback, cancel; - - var self = Metamaps.Realtime; - var socket = self.socket; - - if (!self.status) return; - - function waitThenRenderTopic() { - if (topic && mapping && mapper) { - Metamaps.Topic.renderTopic(mapping, topic, false, false); - } - else if (!cancel) { - setTimeout(waitThenRenderTopic, 10); - } - } - - mapper = Metamaps.Mappers.get(data.mapperid); - if (mapper === undefined) { - mapperCallback = function (m) { - Metamaps.Mappers.add(m); - mapper = m; - }; - Metamaps.Mapper.get(data.mapperid, mapperCallback); - } - $.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 = Metamaps.Realtime; - var socket = self.socket; - - if (Metamaps.Active.Map) { - socket.emit('deleteTopicFromClient', data); - } - }, - // removeTopic - sendRemoveTopic: function (data) { - var self = Metamaps.Realtime; - var socket = self.socket; - - if (Metamaps.Active.Map) { - data.mapid = Metamaps.Active.Map.id; - socket.emit('removeTopic', data); - } - }, - removeTopic: function (data) { - var self = Metamaps.Realtime; - var socket = self.socket; - - if (!self.status) return; - - var topic = Metamaps.Topics.get(data.mappableid); - if (topic) { - var node = topic.get('node'); - var mapping = topic.getMapping(); - Metamaps.Control.hideNode(node.id); - Metamaps.Topics.remove(topic); - Metamaps.Mappings.remove(mapping); - } - }, - // newSynapse - sendNewSynapse: function (data) { - var self = Metamaps.Realtime; - var socket = self.socket; - - if (Metamaps.Active.Map) { - data.mapperid = Metamaps.Active.Mapper.id; - data.mapid = Metamaps.Active.Map.id; - socket.emit('newSynapse', data); - } - }, - newSynapse: function (data) { - var topic1, topic2, node1, node2, synapse, mapping, cancel; - - var self = Metamaps.Realtime; - var socket = self.socket; - - if (!self.status) return; - - function waitThenRenderSynapse() { - if (synapse && mapping && mapper) { - topic1 = synapse.getTopic1(); - node1 = topic1.get('node'); - topic2 = synapse.getTopic2(); - node2 = topic2.get('node'); - - Metamaps.Synapse.renderSynapse(mapping, synapse, node1, node2, false); - } - else if (!cancel) { - setTimeout(waitThenRenderSynapse, 10); - } - } - - mapper = Metamaps.Mappers.get(data.mapperid); - if (mapper === undefined) { - mapperCallback = function (m) { - Metamaps.Mappers.add(m); - mapper = m; - }; - Metamaps.Mapper.get(data.mapperid, mapperCallback); - } - $.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 = Metamaps.Realtime; - var socket = self.socket; - - if (Metamaps.Active.Map) { - data.mapid = Metamaps.Active.Map.id; - socket.emit('deleteSynapseFromClient', data); - } - }, - // removeSynapse - sendRemoveSynapse: function (data) { - var self = Metamaps.Realtime; - var socket = self.socket; - - if (Metamaps.Active.Map) { - data.mapid = Metamaps.Active.Map.id; - socket.emit('removeSynapse', data); - } - }, - removeSynapse: function (data) { - var self = Metamaps.Realtime; - var socket = self.socket; - - if (!self.status) return; - - 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) { - Metamaps.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); - } - }, -}; // end Metamaps.Realtime