'
+ },
+ source: existingSynapseBloodhound,
+ }]
+ )
+
+ $('#synapse_desc').keyup(function (e) {
+ var ESC = 27, BACKSPACE = 8, DELETE = 46
+ if (e.keyCode === BACKSPACE && $(this).val() === '' ||
+ e.keyCode === DELETE && $(this).val() === '' ||
+ e.keyCode === ESC) {
+ Metamaps.Create.newSynapse.hide()
+ } // if
+ Metamaps.Create.newSynapse.description = $(this).val()
+ })
+
+ $('#synapse_desc').focusout(function () {
+ if (Metamaps.Create.newSynapse.beingCreated) {
+ Metamaps.Synapse.createSynapseLocally()
+ }
+ })
+
+ $('#synapse_desc').bind('typeahead:select', function (event, datum, dataset) {
+ if (datum.id) { // if they clicked on an existing synapse get it
+ Metamaps.Synapse.getSynapseFromAutocomplete(datum.id)
+ } else {
+ Metamaps.Create.newSynapse.description = datum.value
+ Metamaps.Synapse.createSynapseLocally()
+ }
+ })
+ },
+ beingCreated: false,
+ description: null,
+ topic1id: null,
+ topic2id: null,
+ newSynapseId: null,
+ open: function () {
+ $('#new_synapse').fadeIn(100, function () {
+ $('#synapse_desc').focus()
+ })
+ Metamaps.Create.newSynapse.beingCreated = true
+ },
+ hide: function () {
+ $('#new_synapse').fadeOut('fast')
+ $('#synapse_desc').typeahead('val', '')
+ Metamaps.Create.newSynapse.beingCreated = false
+ Metamaps.Create.newTopic.addSynapse = false
+ Metamaps.Create.newSynapse.topic1id = 0
+ Metamaps.Create.newSynapse.topic2id = 0
+ Metamaps.Mouse.synapseStartCoordinates = []
+ Metamaps.Visualize.mGraph.plot()
+ },
+ }
+}; // end Metamaps.Create
diff --git a/app/assets/javascripts/src/Metamaps.GlobalUI.js.erb b/app/assets/javascripts/src/Metamaps.GlobalUI.js
similarity index 98%
rename from app/assets/javascripts/src/Metamaps.GlobalUI.js.erb
rename to app/assets/javascripts/src/Metamaps.GlobalUI.js
index 690bba1f..d8b91a31 100644
--- a/app/assets/javascripts/src/Metamaps.GlobalUI.js.erb
+++ b/app/assets/javascripts/src/Metamaps.GlobalUI.js
@@ -93,6 +93,7 @@ Metamaps.GlobalUI = {
if (Metamaps.Active.Mapper) Metamaps.Active.Mapper = new Metamaps.Backbone.Mapper(Metamaps.Active.Mapper);
var myCollection = Metamaps.Maps.Mine ? Metamaps.Maps.Mine : [];
+ var sharedCollection = Metamaps.Maps.Shared ? Metamaps.Maps.Shared : [];
var mapperCollection = [];
var mapperOptionsObj = {id: 'mapper', sortBy: 'updated_at' };
if (Metamaps.Maps.Mapper) {
@@ -102,6 +103,7 @@ Metamaps.GlobalUI = {
var featuredCollection = Metamaps.Maps.Featured ? Metamaps.Maps.Featured : [];
var activeCollection = Metamaps.Maps.Active ? Metamaps.Maps.Active : [];
Metamaps.Maps.Mine = new Metamaps.Backbone.MapsCollection(myCollection, {id: 'mine', sortBy: 'updated_at' });
+ Metamaps.Maps.Shared = new Metamaps.Backbone.MapsCollection(sharedCollection, {id: 'shared', sortBy: 'updated_at' });
// 'Mapper' refers to another mapper
Metamaps.Maps.Mapper = new Metamaps.Backbone.MapsCollection(mapperCollection, mapperOptionsObj);
Metamaps.Maps.Featured = new Metamaps.Backbone.MapsCollection(featuredCollection, {id: 'featured', sortBy: 'updated_at' });
@@ -501,7 +503,7 @@ Metamaps.GlobalUI.Search = {
return Hogan.compile(topicheader + $('#topicSearchTemplate').html()).render({
value: "No results",
label: "No results",
- typeImageURL: "<%= asset_path('icons/wildcard.png') %>",
+ typeImageURL: Metamaps.Erb['icons/wildcard.png'],
rtype: "noresult"
});
},
@@ -569,7 +571,7 @@ Metamaps.GlobalUI.Search = {
value: "No results",
label: "No results",
rtype: "noresult",
- profile: "<%= asset_path('user.png') %>",
+ profile: Metamaps.Erb['user.png']
});
},
header: mapperheader,
diff --git a/app/assets/javascripts/src/Metamaps.JIT.js.erb b/app/assets/javascripts/src/Metamaps.JIT.js
similarity index 99%
rename from app/assets/javascripts/src/Metamaps.JIT.js.erb
rename to app/assets/javascripts/src/Metamaps.JIT.js
index 4b72b714..01b321f7 100644
--- a/app/assets/javascripts/src/Metamaps.JIT.js.erb
+++ b/app/assets/javascripts/src/Metamaps.JIT.js
@@ -29,10 +29,10 @@ Metamaps.JIT = {
$('.takeScreenshot').click(Metamaps.Map.exportImage)
self.topicDescImage = new Image()
- self.topicDescImage.src = '<%= asset_path('topic_description_signifier.png') %>'
+ self.topicDescImage.src = Metamaps.Erb['topic_description_signifier.png']
self.topicLinkImage = new Image()
- self.topicLinkImage.src = '<%= asset_path('topic_link_signifier.png') %>'
+ self.topicLinkImage.src = Metamaps.Erb['topic_link_signifier.png']
},
/**
* convert our topic JSON into something JIT can use
diff --git a/app/assets/javascripts/src/Metamaps.Map.js.erb b/app/assets/javascripts/src/Metamaps.Map.js
similarity index 78%
rename from app/assets/javascripts/src/Metamaps.Map.js.erb
rename to app/assets/javascripts/src/Metamaps.Map.js
index 0207d081..34374614 100644
--- a/app/assets/javascripts/src/Metamaps.Map.js.erb
+++ b/app/assets/javascripts/src/Metamaps.Map.js
@@ -4,17 +4,18 @@
* Metamaps.Map.js.erb
*
* Dependencies:
- * Metamaps.Create
- * Metamaps.Filter
- * Metamaps.JIT
- * Metamaps.Loading
- * Metamaps.Maps
- * Metamaps.Realtime
- * Metamaps.Router
- * Metamaps.Selected
- * Metamaps.SynapseCard
- * Metamaps.TopicCard
- * Metamaps.Visualize
+ * - Metamaps.Create
+ * - Metamaps.Erb
+ * - Metamaps.Filter
+ * - Metamaps.JIT
+ * - Metamaps.Loading
+ * - Metamaps.Maps
+ * - Metamaps.Realtime
+ * - Metamaps.Router
+ * - Metamaps.Selected
+ * - Metamaps.SynapseCard
+ * - Metamaps.TopicCard
+ * - Metamaps.Visualize
* - Metamaps.Active
* - Metamaps.Backbone
* - Metamaps.GlobalUI
@@ -64,6 +65,7 @@ Metamaps.Map = {
var start = function (data) {
Metamaps.Active.Map = new bb.Map(data.map)
Metamaps.Mappers = new bb.MapperCollection(data.mappers)
+ Metamaps.Collaborators = new bb.MapperCollection(data.collaborators)
Metamaps.Topics = new bb.TopicCollection(data.topics)
Metamaps.Synapses = new bb.SynapseCollection(data.synapses)
Metamaps.Mappings = new bb.MappingCollection(data.mappings)
@@ -180,13 +182,13 @@ Metamaps.Map = {
Metamaps.Router.home()
Metamaps.GlobalUI.notifyUser('Sorry! That map has been changed to Private.')
},
- commonsToPublic: function () {
+ cantEditNow: function () {
Metamaps.Realtime.turnOff(true); // true is for 'silence'
Metamaps.GlobalUI.notifyUser('Map was changed to Public. Editing is disabled.')
Metamaps.Active.Map.trigger('changeByOther')
},
- publicToCommons: function () {
- var confirmString = 'This map permission has been changed to Commons! '
+ canEditNow: function () {
+ var confirmString = "You've been granted permission to edit this map. "
confirmString += 'Do you want to reload and enable realtime collaboration?'
var c = confirm(confirmString)
if (c) {
@@ -419,7 +421,7 @@ Metamaps.Map.InfoBox = {
isOpen: false,
changing: false,
selectingPermission: false,
- changePermissionText: "
As the creator, you can change the permission of this map, but the permissions of the topics and synapses on it must be changed independently.
",
+ changePermissionText: "
As the creator, you can change the permission of this map, and the permission of all the topics and synapses you have authority to change will change as well.
'
+ }
return string
},
updateNumbers: function () {
var self = Metamaps.Map.InfoBox
var mapper = Metamaps.Active.Mapper
+ var relevantPeople = Metamaps.Active.Map.get('permission') === 'commons' ? Metamaps.Mappers : Metamaps.Collaborators
var contributors_class = ''
- if (Metamaps.Mappers.length === 2) contributors_class = 'multiple mTwo'
- else if (Metamaps.Mappers.length > 2) contributors_class = 'multiple'
+ if (relevantPeople.length === 2) contributors_class = 'multiple mTwo'
+ else if (relevantPeople.length > 2) contributors_class = 'multiple'
- var contributors_image = "<%= asset_path('user.png') %>"
- if (Metamaps.Mappers.length > 0) {
+ var contributors_image = Metamaps.Erb['user.png']
+ if (relevantPeople.length > 0) {
// get the first contributor and use their image
- contributors_image = Metamaps.Mappers.models[0].get('image')
+ contributors_image = relevantPeople.models[0].get('image')
}
$('.mapContributors img').attr('src', contributors_image).removeClass('multiple mTwo').addClass(contributors_class)
- $('.mapContributors span').text(Metamaps.Mappers.length)
+ $('.mapContributors span').text(relevantPeople.length)
$('.mapContributors .tip').html(self.createContributorList())
+ self.addTypeahead()
+ $('.mapContributors .tip').unbind().click(function (event) {
+ event.stopPropagation()
+ })
$('.mapTopics').text(Metamaps.Topics.length)
$('.mapSynapses').text(Metamaps.Synapses.length)
@@ -623,19 +727,10 @@ Metamaps.Map.InfoBox = {
self.selectingPermission = false
var permission = $(this).attr('class')
- var permBefore = Metamaps.Active.Map.get('permission')
Metamaps.Active.Map.save({
permission: permission
})
Metamaps.Active.Map.updateMapWrapper()
- if (permBefore !== 'commons' && permission === 'commons') {
- Metamaps.Realtime.setupSocket()
- Metamaps.Realtime.turnOn()
- }
- else if (permBefore === 'commons' && permission === 'public') {
- Metamaps.Realtime.turnOff(true); // true is to 'silence'
- // the notification that would otherwise be sent
- }
shareable = permission === 'private' ? '' : 'shareable'
$('.mapPermission').removeClass('commons public private minimize').addClass(permission)
$('.mapPermission .permissionSelect').remove()
@@ -656,6 +751,7 @@ Metamaps.Map.InfoBox = {
Metamaps.Maps.Active.remove(map)
Metamaps.Maps.Featured.remove(map)
Metamaps.Maps.Mine.remove(map)
+ Metamaps.Maps.Shared.remove(map)
map.destroy()
Metamaps.Router.home()
Metamaps.GlobalUI.notifyUser('Map eliminated!')
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.Router.js b/app/assets/javascripts/src/Metamaps.Router.js
index 6f673b61..5e574637 100644
--- a/app/assets/javascripts/src/Metamaps.Router.js
+++ b/app/assets/javascripts/src/Metamaps.Router.js
@@ -87,7 +87,7 @@
// either 'featured', 'mapper', or 'active'
var capitalize = section.charAt(0).toUpperCase() + section.slice(1)
- if (section === 'featured' || section === 'active') {
+ if (section === 'shared' || section === 'featured' || section === 'active') {
document.title = 'Explore ' + capitalize + ' Maps | Metamaps'
} else if (section === 'mapper') {
$.ajax({
diff --git a/app/assets/javascripts/src/Metamaps.SynapseCard.js b/app/assets/javascripts/src/Metamaps.SynapseCard.js
new file mode 100644
index 00000000..f71601e5
--- /dev/null
+++ b/app/assets/javascripts/src/Metamaps.SynapseCard.js
@@ -0,0 +1,288 @@
+/* global Metamaps, $ */
+
+/*
+ * Metamaps.SynapseCard.js
+ *
+ * Dependencies:
+ * - Metamaps.Active
+ * - Metamaps.Control
+ * - Metamaps.Mapper
+ * - Metamaps.Visualize
+ */
+Metamaps.SynapseCard = {
+ openSynapseCard: null,
+ showCard: function (edge, e) {
+ var self = Metamaps.SynapseCard
+
+ // reset so we don't interfere with other edges, but first, save its x and y
+ var myX = $('#edit_synapse').css('left')
+ var myY = $('#edit_synapse').css('top')
+ $('#edit_synapse').remove()
+
+ // so label is missing while editing
+ Metamaps.Control.deselectEdge(edge)
+
+ var index = edge.getData('displayIndex') ? edge.getData('displayIndex') : 0
+ var synapse = edge.getData('synapses')[index]; // for now, just get the first synapse
+
+ // create the wrapper around the form elements, including permissions
+ // classes to make best_in_place happy
+ var edit_div = document.createElement('div')
+ edit_div.innerHTML = ''
+ edit_div.setAttribute('id', 'edit_synapse')
+ if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) {
+ edit_div.className = 'permission canEdit'
+ edit_div.className += synapse.authorizePermissionChange(Metamaps.Active.Mapper) ? ' yourEdge' : ''
+ } else {
+ edit_div.className = 'permission cannotEdit'
+ }
+ $('#wrapper').append(edit_div)
+
+ self.populateShowCard(edge, synapse)
+
+ // drop it in the right spot, activate it
+ $('#edit_synapse').css('position', 'absolute')
+ if (e) {
+ $('#edit_synapse').css('left', e.clientX)
+ $('#edit_synapse').css('top', e.clientY)
+ } else {
+ $('#edit_synapse').css('left', myX)
+ $('#edit_synapse').css('top', myY)
+ }
+ // $('#edit_synapse_name').click() //required in case name is empty
+ // $('#edit_synapse_name input').focus()
+ $('#edit_synapse').show()
+
+ self.openSynapseCard = edge
+ },
+
+ hideCard: function () {
+ $('#edit_synapse').remove()
+ Metamaps.SynapseCard.openSynapseCard = null
+ },
+
+ populateShowCard: function (edge, synapse) {
+ var self = Metamaps.SynapseCard
+
+ self.add_synapse_count(edge)
+ self.add_desc_form(synapse)
+ self.add_drop_down(edge, synapse)
+ self.add_user_info(synapse)
+ self.add_perms_form(synapse)
+ self.add_direction_form(synapse)
+ },
+ add_synapse_count: function (edge) {
+ var count = edge.getData('synapses').length
+
+ $('#editSynUpperBar').append('
' + count + '
')
+ },
+ add_desc_form: function (synapse) {
+ var data_nil = 'Click to add description.'
+
+ // TODO make it so that this would work even in sandbox mode,
+ // currently with Best_in_place it won't
+
+ // desc editing form
+ $('#editSynUpperBar').append('')
+ $('#edit_synapse_desc').attr('class', 'best_in_place best_in_place_desc')
+ $('#edit_synapse_desc').attr('data-object', 'synapse')
+ $('#edit_synapse_desc').attr('data-attribute', 'desc')
+ $('#edit_synapse_desc').attr('data-type', 'textarea')
+ $('#edit_synapse_desc').attr('data-nil', data_nil)
+ $('#edit_synapse_desc').attr('data-url', '/synapses/' + synapse.id)
+ $('#edit_synapse_desc').html(synapse.get('desc'))
+
+ // if edge data is blank or just whitespace, populate it with data_nil
+ if ($('#edit_synapse_desc').html().trim() == '') {
+ if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) {
+ $('#edit_synapse_desc').html(data_nil)
+ } else {
+ $('#edit_synapse_desc').html('(no description)')
+ }
+ }
+
+ $('#edit_synapse_desc').bind('ajax:success', function () {
+ var desc = $(this).html()
+ if (desc == data_nil) {
+ synapse.set('desc', '')
+ } else {
+ synapse.set('desc', desc)
+ }
+ synapse.trigger('saved')
+ Metamaps.Control.selectEdge(synapse.get('edge'))
+ Metamaps.Visualize.mGraph.plot()
+ })
+ },
+ add_drop_down: function (edge, synapse) {
+ var list, i, synapses, l, desc
+
+ synapses = edge.getData('synapses')
+ l = synapses.length
+
+ if (l > 1) {
+ // append the element that you click to show dropdown select
+ $('#editSynUpperBar').append('')
+ $('#dropdownSynapses').click(function (e) {
+ e.preventDefault()
+ e.stopPropagation() // stop it from immediately closing it again
+ $('#switchSynapseList').toggle()
+ })
+ // hide the dropdown again if you click anywhere else on the synapse card
+ $('#edit_synapse').click(function () {
+ $('#switchSynapseList').hide()
+ })
+
+ // generate the list of other synapses
+ list = '
'
+ for (i = 0; i < l; i++) {
+ if (synapses[i] !== synapse) { // don't add the current one to the list
+ desc = synapses[i].get('desc')
+ desc = desc === '' || desc === null ? '(no description)' : desc
+ list += '
' + desc + '
'
+ }
+ }
+ list += '
'
+ // add the list of the other synapses
+ $('#editSynLowerBar').append(list)
+
+ // attach click listeners to list items that
+ // will cause it to switch the displayed synapse
+ // when you click it
+ $('#switchSynapseList li').click(function (e) {
+ e.stopPropagation()
+ var index = parseInt($(this).attr('data-synapse-index'))
+ edge.setData('displayIndex', index)
+ Metamaps.Visualize.mGraph.plot()
+ Metamaps.SynapseCard.showCard(edge, false)
+ })
+ }
+ },
+ add_user_info: function (synapse) {
+ var u = '
'
+ u += ''
+ u += '
' + synapse.get('user_name') + '
'
+ $('#editSynLowerBar').append(u)
+
+ // get mapper image
+ var setMapperImage = function (mapper) {
+ $('#edgeUser img').attr('src', mapper.get('image'))
+ }
+ Metamaps.Mapper.get(synapse.get('user_id'), setMapperImage)
+ },
+
+ add_perms_form: function (synapse) {
+ // permissions - if owner, also allow permission editing
+ $('#editSynLowerBar').append('')
+
+ // ability to change permission
+ var selectingPermission = false
+ var permissionLiClick = function (event) {
+ selectingPermission = false
+ var permission = $(this).attr('class')
+ synapse.save({
+ permission: permission,
+ defer_to_map_id: null
+ })
+ $('#edit_synapse .mapPerm').removeClass('co pu pr minimize').addClass(permission.substring(0, 2))
+ $('#edit_synapse .permissionSelect').remove()
+ event.stopPropagation()
+ }
+
+ var openPermissionSelect = function (event) {
+ if (!selectingPermission) {
+ selectingPermission = true
+ $(this).addClass('minimize') // this line flips the drop down arrow to a pull up arrow
+ if ($(this).hasClass('co')) {
+ $(this).append('
')
+ } else if ($(this).hasClass('pu')) {
+ $(this).append('
')
+ } else if ($(this).hasClass('pr')) {
+ $(this).append('
')
+ }
+ $('#edit_synapse .permissionSelect li').click(permissionLiClick)
+ event.stopPropagation()
+ }
+ }
+
+ var hidePermissionSelect = function () {
+ selectingPermission = false
+ $('#edit_synapse.yourEdge .mapPerm').removeClass('minimize') // this line flips the pull up arrow to a drop down arrow
+ $('#edit_synapse .permissionSelect').remove()
+ }
+
+ if (synapse.authorizePermissionChange(Metamaps.Active.Mapper)) {
+ $('#edit_synapse.yourEdge .mapPerm').click(openPermissionSelect)
+ $('#edit_synapse').click(hidePermissionSelect)
+ }
+ }, // add_perms_form
+
+ add_direction_form: function (synapse) {
+ // directionality checkboxes
+ $('#editSynLowerBar').append('')
+ $('#editSynLowerBar').append('')
+
+ var edge = synapse.get('edge')
+
+ // determine which node is to the left and the right
+ // if directly in a line, top is left
+ if (edge.nodeFrom.pos.x < edge.nodeTo.pos.x ||
+ edge.nodeFrom.pos.x == edge.nodeTo.pos.x &&
+ edge.nodeFrom.pos.y < edge.nodeTo.pos.y) {
+ var left = edge.nodeTo.getData('topic')
+ var right = edge.nodeFrom.getData('topic')
+ } else {
+ var left = edge.nodeFrom.getData('topic')
+ var right = edge.nodeTo.getData('topic')
+ }
+
+ /*
+ * One node is actually on the left onscreen. Call it left, & the other right.
+ * If category is from-to, and that node is first, check the 'right' checkbox.
+ * Else check the 'left' checkbox since the arrow is incoming.
+ */
+
+ var directionCat = synapse.get('category'); // both, none, from-to
+ if (directionCat == 'from-to') {
+ var from_to = [synapse.get('node1_id'), synapse.get('node2_id')]
+ if (from_to[0] == left.id) {
+ // check left checkbox
+ $('#edit_synapse_left').addClass('checked')
+ } else {
+ // check right checkbox
+ $('#edit_synapse_right').addClass('checked')
+ }
+ } else if (directionCat == 'both') {
+ // check both checkboxes
+ $('#edit_synapse_left').addClass('checked')
+ $('#edit_synapse_right').addClass('checked')
+ }
+
+ if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) {
+ $('#edit_synapse_left, #edit_synapse_right').click(function () {
+ $(this).toggleClass('checked')
+
+ var leftChecked = $('#edit_synapse_left').is('.checked')
+ var rightChecked = $('#edit_synapse_right').is('.checked')
+
+ var dir = synapse.getDirection()
+ var dirCat = 'none'
+ if (leftChecked && rightChecked) {
+ dirCat = 'both'
+ } else if (!leftChecked && rightChecked) {
+ dirCat = 'from-to'
+ dir = [right.id, left.id]
+ } else if (leftChecked && !rightChecked) {
+ dirCat = 'from-to'
+ dir = [left.id, right.id]
+ }
+
+ synapse.save({
+ category: dirCat,
+ node1_id: dir[0],
+ node2_id: dir[1]
+ })
+ Metamaps.Visualize.mGraph.plot()
+ })
+ } // if
+ } // add_direction_form
+}; // end Metamaps.SynapseCard
diff --git a/app/assets/javascripts/src/Metamaps.Topic.js b/app/assets/javascripts/src/Metamaps.Topic.js
index dc242f7d..da1bfc8c 100644
--- a/app/assets/javascripts/src/Metamaps.Topic.js
+++ b/app/assets/javascripts/src/Metamaps.Topic.js
@@ -299,7 +299,8 @@ Metamaps.Topic = {
var topic = new Metamaps.Backbone.Topic({
name: Metamaps.Create.newTopic.name,
- metacode_id: metacode.id
+ metacode_id: metacode.id,
+ defer_to_map_id: Metamaps.Active.Map.id
})
Metamaps.Topics.add(topic)
diff --git a/app/assets/javascripts/src/Metamaps.TopicCard.js b/app/assets/javascripts/src/Metamaps.TopicCard.js
new file mode 100644
index 00000000..194433db
--- /dev/null
+++ b/app/assets/javascripts/src/Metamaps.TopicCard.js
@@ -0,0 +1,451 @@
+/* global Metamaps, $ */
+
+/*
+ * Metamaps.TopicCard.js
+ *
+ * Dependencies:
+ * - Metamaps.Active
+ * - Metamaps.GlobalUI
+ * - Metamaps.Mapper
+ * - Metamaps.Metacodes
+ * - Metamaps.Router
+ * - Metamaps.Util
+ * - Metamaps.Visualize
+ */
+Metamaps.TopicCard = {
+ openTopicCard: null, // stores the topic that's currently open
+ authorizedToEdit: false, // stores boolean for edit permission for open topic card
+ init: function () {
+ var self = Metamaps.TopicCard
+
+ // initialize best_in_place editing
+ $('.authenticated div.permission.canEdit .best_in_place').best_in_place()
+
+ Metamaps.TopicCard.generateShowcardHTML = Hogan.compile($('#topicCardTemplate').html())
+
+ // initialize topic card draggability and resizability
+ $('.showcard').draggable({
+ handle: '.metacodeImage'
+ })
+
+ embedly('on', 'card.rendered', self.embedlyCardRendered)
+ },
+ /**
+ * Will open the Topic Card for the node that it's passed
+ * @param {$jit.Graph.Node} node
+ */
+ showCard: function (node) {
+ var self = Metamaps.TopicCard
+
+ var topic = node.getData('topic')
+
+ self.openTopicCard = topic
+ self.authorizedToEdit = topic.authorizeToEdit(Metamaps.Active.Mapper)
+ // populate the card that's about to show with the right topics data
+ self.populateShowCard(topic)
+ $('.showcard').fadeIn('fast')
+ },
+ hideCard: function () {
+ var self = Metamaps.TopicCard
+
+ $('.showcard').fadeOut('fast')
+ self.openTopicCard = null
+ self.authorizedToEdit = false
+ },
+ embedlyCardRendered: function (iframe) {
+ var self = Metamaps.TopicCard
+
+ $('#embedlyLinkLoader').hide()
+
+ // means that the embedly call returned 404 not found
+ if ($('#embedlyLink')[0]) {
+ $('#embedlyLink').css('display', 'block').fadeIn('fast')
+ $('.embeds').addClass('nonEmbedlyLink')
+ }
+
+ $('.CardOnGraph').addClass('hasAttachment')
+ if (self.authorizedToEdit) {
+ $('.embeds').append('')
+ $('#linkremove').click(self.removeLink)
+ }
+ },
+ removeLink: function () {
+ var self = Metamaps.TopicCard
+ self.openTopicCard.save({
+ link: null
+ })
+ $('.embeds').empty().removeClass('nonEmbedlyLink')
+ $('#addLinkInput input').val('')
+ $('.attachments').removeClass('hidden')
+ $('.CardOnGraph').removeClass('hasAttachment')
+ },
+ bindShowCardListeners: function (topic) {
+ var self = Metamaps.TopicCard
+ var showCard = document.getElementById('showcard')
+
+ var authorized = self.authorizedToEdit
+
+ // get mapper image
+ var setMapperImage = function (mapper) {
+ $('.contributorIcon').attr('src', mapper.get('image'))
+ }
+ Metamaps.Mapper.get(topic.get('user_id'), setMapperImage)
+
+ // starting embed.ly
+ var resetFunc = function () {
+ $('#addLinkInput input').val('')
+ $('#addLinkInput input').focus()
+ }
+ var inputEmbedFunc = function (event) {
+ var element = this
+ setTimeout(function () {
+ var text = $(element).val()
+ if (event.type == 'paste' || (event.type == 'keyup' && event.which == 13)) {
+ // TODO evaluate converting this to '//' no matter what (infer protocol)
+ if (text.slice(0, 7) !== 'http://' &&
+ text.slice(0, 8) !== 'https://' &&
+ text.slice(0, 2) !== '//') {
+ text = '//' + text
+ }
+ topic.save({
+ link: text
+ })
+ var embedlyEl = $('', {
+ id: 'embedlyLink',
+ 'data-card-description': '0',
+ href: text
+ }).html(text)
+ $('.attachments').addClass('hidden')
+ $('.embeds').append(embedlyEl)
+ $('.embeds').append('')
+ var loader = new CanvasLoader('embedlyLinkLoader')
+ loader.setColor('#4fb5c0'); // default is '#000000'
+ loader.setDiameter(28) // default is 40
+ loader.setDensity(41) // default is 40
+ loader.setRange(0.9); // default is 1.3
+ loader.show() // Hidden by default
+ var e = embedly('card', document.getElementById('embedlyLink'))
+ if (!e) {
+ self.handleInvalidLink()
+ }
+ }
+ }, 100)
+ }
+ $('#addLinkReset').click(resetFunc)
+ $('#addLinkInput input').bind('paste keyup', inputEmbedFunc)
+
+ // initialize the link card, if there is a link
+ if (topic.get('link') && topic.get('link') !== '') {
+ var loader = new CanvasLoader('embedlyLinkLoader')
+ loader.setColor('#4fb5c0'); // default is '#000000'
+ loader.setDiameter(28) // default is 40
+ loader.setDensity(41) // default is 40
+ loader.setRange(0.9); // default is 1.3
+ loader.show() // Hidden by default
+ var e = embedly('card', document.getElementById('embedlyLink'))
+ if (!e) {
+ self.handleInvalidLink()
+ }
+ }
+
+ var selectingMetacode = false
+ // attach the listener that shows the metacode title when you hover over the image
+ $('.showcard .metacodeImage').mouseenter(function () {
+ $('.showcard .icon').css('z-index', '4')
+ $('.showcard .metacodeTitle').show()
+ })
+ $('.showcard .linkItem.icon').mouseleave(function () {
+ if (!selectingMetacode) {
+ $('.showcard .metacodeTitle').hide()
+ $('.showcard .icon').css('z-index', '1')
+ }
+ })
+
+ var metacodeLiClick = function () {
+ selectingMetacode = false
+ var metacodeId = parseInt($(this).attr('data-id'))
+ var metacode = Metamaps.Metacodes.get(metacodeId)
+ $('.CardOnGraph').find('.metacodeTitle').html(metacode.get('name'))
+ .append('')
+ .attr('class', 'metacodeTitle mbg' + metacode.id)
+ $('.CardOnGraph').find('.metacodeImage').css('background-image', 'url(' + metacode.get('icon') + ')')
+ topic.save({
+ metacode_id: metacode.id
+ })
+ Metamaps.Visualize.mGraph.plot()
+ $('.metacodeSelect').hide().removeClass('onRightEdge onBottomEdge')
+ $('.metacodeTitle').hide()
+ $('.showcard .icon').css('z-index', '1')
+ }
+
+ var openMetacodeSelect = function (event) {
+ var windowWidth
+ var showcardLeft
+ var TOPICCARD_WIDTH = 300
+ var METACODESELECT_WIDTH = 404
+ var distanceFromEdge
+
+ var MAX_METACODELIST_HEIGHT = 270
+ var windowHeight
+ var showcardTop
+ var topicTitleHeight
+ var distanceFromBottom
+
+ if (!selectingMetacode) {
+ selectingMetacode = true
+
+ // this is to make sure the metacode
+ // select is accessible onscreen, when opened
+ // while topic card is close to the right
+ // edge of the screen
+ windowWidth = $(window).width()
+ showcardLeft = parseInt($('.showcard').css('left'))
+ distanceFromEdge = windowWidth - (showcardLeft + TOPICCARD_WIDTH)
+ if (distanceFromEdge < METACODESELECT_WIDTH) {
+ $('.metacodeSelect').addClass('onRightEdge')
+ }
+
+ // this is to make sure the metacode
+ // select is accessible onscreen, when opened
+ // while topic card is close to the bottom
+ // edge of the screen
+ windowHeight = $(window).height()
+ showcardTop = parseInt($('.showcard').css('top'))
+ topicTitleHeight = $('.showcard .title').height() + parseInt($('.showcard .title').css('padding-top')) + parseInt($('.showcard .title').css('padding-bottom'))
+ heightOfSetList = $('.showcard .metacodeSelect').height()
+ distanceFromBottom = windowHeight - (showcardTop + topicTitleHeight)
+ if (distanceFromBottom < MAX_METACODELIST_HEIGHT) {
+ $('.metacodeSelect').addClass('onBottomEdge')
+ }
+
+ $('.metacodeSelect').show()
+ event.stopPropagation()
+ }
+ }
+
+ var hideMetacodeSelect = function () {
+ selectingMetacode = false
+ $('.metacodeSelect').hide().removeClass('onRightEdge onBottomEdge')
+ $('.metacodeTitle').hide()
+ $('.showcard .icon').css('z-index', '1')
+ }
+
+ if (authorized) {
+ $('.showcard .metacodeTitle').click(openMetacodeSelect)
+ $('.showcard').click(hideMetacodeSelect)
+ $('.metacodeSelect > ul > li').click(function (event) {
+ event.stopPropagation()
+ })
+ $('.metacodeSelect li li').click(metacodeLiClick)
+
+ var bipName = $(showCard).find('.best_in_place_name')
+ bipName.bind('best_in_place:activate', function () {
+ var $el = bipName.find('textarea')
+ var el = $el[0]
+
+ $el.attr('maxlength', '140')
+
+ $('.showcard .title').append('')
+
+ var callback = function (data) {
+ $('.nameCounter.forTopic').html(data.all + '/140')
+ }
+ Countable.live(el, callback)
+ })
+ bipName.bind('best_in_place:deactivate', function () {
+ $('.nameCounter.forTopic').remove()
+ })
+
+ // bind best_in_place ajax callbacks
+ bipName.bind('ajax:success', function () {
+ var name = Metamaps.Util.decodeEntities($(this).html())
+ topic.set('name', name)
+ topic.trigger('saved')
+ })
+
+ $(showCard).find('.best_in_place_desc').bind('ajax:success', function () {
+ this.innerHTML = this.innerHTML.replace(/\r/g, '')
+ var desc = $(this).html() === $(this).data('nil') ? '' : $(this).html()
+ topic.set('desc', desc)
+ topic.trigger('saved')
+ })
+ }
+
+ var permissionLiClick = function (event) {
+ selectingPermission = false
+ var permission = $(this).attr('class')
+ topic.save({
+ permission: permission,
+ defer_to_map_id: null
+ })
+ $('.showcard .mapPerm').removeClass('co pu pr minimize').addClass(permission.substring(0, 2))
+ $('.showcard .permissionSelect').remove()
+ event.stopPropagation()
+ }
+
+ var openPermissionSelect = function (event) {
+ if (!selectingPermission) {
+ selectingPermission = true
+ $(this).addClass('minimize') // this line flips the drop down arrow to a pull up arrow
+ if ($(this).hasClass('co')) {
+ $(this).append('
')
+ } else if ($(this).hasClass('pu')) {
+ $(this).append('
')
+ } else if ($(this).hasClass('pr')) {
+ $(this).append('
')
+ }
+ $('.showcard .permissionSelect li').click(permissionLiClick)
+ event.stopPropagation()
+ }
+ }
+
+ var hidePermissionSelect = function () {
+ selectingPermission = false
+ $('.showcard .yourTopic .mapPerm').removeClass('minimize') // this line flips the pull up arrow to a drop down arrow
+ $('.showcard .permissionSelect').remove()
+ }
+ // ability to change permission
+ var selectingPermission = false
+ if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) {
+ $('.showcard .yourTopic .mapPerm').click(openPermissionSelect)
+ $('.showcard').click(hidePermissionSelect)
+ }
+
+ $('.links .mapCount').unbind().click(function (event) {
+ $('.mapCount .tip').toggle()
+ $('.showcard .hoverTip').toggleClass('hide')
+ event.stopPropagation()
+ })
+ $('.mapCount .tip').unbind().click(function (event) {
+ event.stopPropagation()
+ })
+ $('.showcard').unbind('.hideTip').bind('click.hideTip', function () {
+ $('.mapCount .tip').hide()
+ $('.showcard .hoverTip').removeClass('hide')
+ })
+
+ $('.mapCount .tip li a').click(Metamaps.Router.intercept)
+
+ var originalText = $('.showMore').html()
+ $('.mapCount .tip .showMore').unbind().toggle(
+ function (event) {
+ $('.extraText').toggleClass('hideExtra')
+ $('.showMore').html('Show less...')
+ },
+ function (event) {
+ $('.extraText').toggleClass('hideExtra')
+ $('.showMore').html(originalText)
+ })
+
+ $('.mapCount .tip showMore').unbind().click(function (event) {
+ event.stopPropagation()
+ })
+ },
+ handleInvalidLink: function () {
+ var self = Metamaps.TopicCard
+
+ self.removeLink()
+ Metamaps.GlobalUI.notifyUser('Invalid link')
+ },
+ populateShowCard: function (topic) {
+ var self = Metamaps.TopicCard
+
+ var showCard = document.getElementById('showcard')
+
+ $(showCard).find('.permission').remove()
+
+ var topicForTemplate = self.buildObject(topic)
+ var html = self.generateShowcardHTML.render(topicForTemplate)
+
+ if (topic.authorizeToEdit(Metamaps.Active.Mapper)) {
+ var perm = document.createElement('div')
+
+ var string = 'permission canEdit'
+ if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) string += ' yourTopic'
+ perm.className = string
+ perm.innerHTML = html
+ showCard.appendChild(perm)
+ } else {
+ var perm = document.createElement('div')
+ perm.className = 'permission cannotEdit'
+ perm.innerHTML = html
+ showCard.appendChild(perm)
+ }
+
+ Metamaps.TopicCard.bindShowCardListeners(topic)
+ },
+ generateShowcardHTML: null, // will be initialized into a Hogan template within init function
+ // generateShowcardHTML
+ buildObject: function (topic) {
+ var self = Metamaps.TopicCard
+
+ var nodeValues = {}
+
+ var authorized = topic.authorizeToEdit(Metamaps.Active.Mapper)
+
+ if (!authorized) {
+ } else {
+ }
+
+ var desc_nil = 'Click to add description...'
+
+ nodeValues.attachmentsHidden = ''
+ if (topic.get('link') && topic.get('link') !== '') {
+ nodeValues.embeds = ''
+ nodeValues.embeds += topic.get('link')
+ nodeValues.embeds += ''
+ nodeValues.attachmentsHidden = 'hidden'
+ nodeValues.hasAttachment = 'hasAttachment'
+ } else {
+ nodeValues.embeds = ''
+ nodeValues.hasAttachment = ''
+ }
+
+ if (authorized) {
+ nodeValues.attachments = '
'
+ nodeValues.attachments += '
'
+ nodeValues.attachments += '
'
+ } else {
+ nodeValues.attachmentsHidden = 'hidden'
+ nodeValues.attachments = ''
+ }
+
+ var inmapsAr = topic.get('inmaps')
+ var inmapsLinks = topic.get('inmapsLinks')
+ nodeValues.inmaps = ''
+ if (inmapsAr.length < 6) {
+ for (i = 0; i < inmapsAr.length; i++) {
+ var url = '/maps/' + inmapsLinks[i]
+ nodeValues.inmaps += '
';
- }
- }
- nodeValues.permission = topic.get("permission");
- nodeValues.mk_permission = topic.get("permission").substring(0, 2);
- nodeValues.map_count = topic.get("map_count").toString();
- nodeValues.synapse_count = topic.get("synapse_count").toString();
- nodeValues.id = topic.isNew() ? topic.cid : topic.id;
- nodeValues.metacode = topic.getMetacode().get("name");
- nodeValues.metacode_class = 'mbg' + topic.get('metacode_id');
- nodeValues.imgsrc = topic.getMetacode().get("icon");
- nodeValues.name = topic.get("name");
- nodeValues.userid = topic.get("user_id");
- nodeValues.username = topic.get("user_name");
- nodeValues.date = topic.getDate();
- // the code for this is stored in /views/main/_metacodeOptions.html.erb
- nodeValues.metacode_select = $('#metacodeOptions').html();
- nodeValues.desc_nil = desc_nil;
- nodeValues.desc = (topic.get("desc") == "" && authorized) ? desc_nil : topic.get("desc");
- return nodeValues;
- }
-}; // end Metamaps.TopicCard
-
-
-/*
- *
- * SYNAPSECARD
- *
- */
-Metamaps.SynapseCard = {
- openSynapseCard: null,
- showCard: function (edge, e) {
- var self = Metamaps.SynapseCard;
-
- //reset so we don't interfere with other edges, but first, save its x and y
- var myX = $('#edit_synapse').css('left');
- var myY = $('#edit_synapse').css('top');
- $('#edit_synapse').remove();
-
- //so label is missing while editing
- Metamaps.Control.deselectEdge(edge);
-
- var index = edge.getData("displayIndex") ? edge.getData("displayIndex") : 0;
- var synapse = edge.getData('synapses')[index]; // for now, just get the first synapse
-
- //create the wrapper around the form elements, including permissions
- //classes to make best_in_place happy
- var edit_div = document.createElement('div');
- edit_div.innerHTML = '';
- edit_div.setAttribute('id', 'edit_synapse');
- if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) {
- edit_div.className = 'permission canEdit';
- edit_div.className += synapse.authorizePermissionChange(Metamaps.Active.Mapper) ? ' yourEdge' : '';
- } else {
- edit_div.className = 'permission cannotEdit';
- }
- $('#wrapper').append(edit_div);
-
- self.populateShowCard(edge, synapse);
-
- //drop it in the right spot, activate it
- $('#edit_synapse').css('position', 'absolute');
- if (e) {
- $('#edit_synapse').css('left', e.clientX);
- $('#edit_synapse').css('top', e.clientY);
- } else {
- $('#edit_synapse').css('left', myX);
- $('#edit_synapse').css('top', myY);
- }
- //$('#edit_synapse_name').click(); //required in case name is empty
- //$('#edit_synapse_name input').focus();
- $('#edit_synapse').show();
-
- self.openSynapseCard = edge;
- },
-
- hideCard: function () {
- $('#edit_synapse').remove();
- Metamaps.SynapseCard.openSynapseCard = null;
- },
-
- populateShowCard: function (edge, synapse) {
- var self = Metamaps.SynapseCard;
-
- self.add_synapse_count(edge);
- self.add_desc_form(synapse);
- self.add_drop_down(edge, synapse);
- self.add_user_info(synapse);
- self.add_perms_form(synapse);
- self.add_direction_form(synapse);
- },
- add_synapse_count: function (edge) {
- var count = edge.getData("synapses").length;
-
- $('#editSynUpperBar').append('
' + count + '
')
- },
- add_desc_form: function (synapse) {
- var data_nil = 'Click to add description.';
-
- // TODO make it so that this would work even in sandbox mode,
- // currently with Best_in_place it won't
-
- //desc editing form
- $('#editSynUpperBar').append('');
- $('#edit_synapse_desc').attr('class', 'best_in_place best_in_place_desc');
- $('#edit_synapse_desc').attr('data-object', 'synapse');
- $('#edit_synapse_desc').attr('data-attribute', 'desc');
- $('#edit_synapse_desc').attr('data-type', 'textarea');
- $('#edit_synapse_desc').attr('data-nil', data_nil);
- $('#edit_synapse_desc').attr('data-url', '/synapses/' + synapse.id);
- $('#edit_synapse_desc').html(synapse.get("desc"));
-
- //if edge data is blank or just whitespace, populate it with data_nil
- if ($('#edit_synapse_desc').html().trim() == '') {
- if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) {
- $('#edit_synapse_desc').html(data_nil);
- }
- else {
- $('#edit_synapse_desc').html("(no description)");
- }
- }
-
- $('#edit_synapse_desc').bind("ajax:success", function () {
- var desc = $(this).html();
- if (desc == data_nil) {
- synapse.set("desc", '');
- } else {
- synapse.set("desc", desc);
- }
- synapse.trigger('saved');
- Metamaps.Control.selectEdge(synapse.get('edge'));
- Metamaps.Visualize.mGraph.plot();
- });
- },
- add_drop_down: function (edge, synapse) {
- var list, i, synapses, l, desc;
-
- synapses = edge.getData("synapses");
- l = synapses.length;
-
- if (l > 1) {
- // append the element that you click to show dropdown select
- $('#editSynUpperBar').append('');
- $('#dropdownSynapses').click(function(e){
- e.preventDefault();
- e.stopPropagation(); // stop it from immediately closing it again
- $('#switchSynapseList').toggle();
- });
- // hide the dropdown again if you click anywhere else on the synapse card
- $('#edit_synapse').click(function(){
- $('#switchSynapseList').hide();
- });
-
- // generate the list of other synapses
- list = '
';
- for (i = 0; i < l; i++) {
- if (synapses[i] !== synapse) { // don't add the current one to the list
- desc = synapses[i].get('desc');
- desc = desc === "" || desc === null ? "(no description)" : desc;
- list += '
' + desc + '
';
- }
- }
- list += '
'
- // add the list of the other synapses
- $('#editSynLowerBar').append(list);
-
- // attach click listeners to list items that
- // will cause it to switch the displayed synapse
- // when you click it
- $('#switchSynapseList li').click(function(e){
- e.stopPropagation();
- var index = parseInt($(this).attr('data-synapse-index'));
- edge.setData('displayIndex', index);
- Metamaps.Visualize.mGraph.plot();
- Metamaps.SynapseCard.showCard(edge, false);
- });
- }
- },
- add_user_info: function (synapse) {
- var u = '
';
- u += ''
- u += '
' + synapse.get("user_name") + '
';
- $('#editSynLowerBar').append(u);
-
- // get mapper image
- var setMapperImage = function (mapper) {
- $('#edgeUser img').attr('src', mapper.get('image'));
- };
- Metamaps.Mapper.get(synapse.get('user_id'), setMapperImage);
- },
-
- add_perms_form: function (synapse) {
- //permissions - if owner, also allow permission editing
- $('#editSynLowerBar').append('');
-
- // ability to change permission
- var selectingPermission = false;
- var permissionLiClick = function (event) {
- selectingPermission = false;
- var permission = $(this).attr('class');
- synapse.save({
- permission: permission
- });
- $('#edit_synapse .mapPerm').removeClass('co pu pr minimize').addClass(permission.substring(0, 2));
- $('#edit_synapse .permissionSelect').remove();
- event.stopPropagation();
- };
-
- var openPermissionSelect = function (event) {
- if (!selectingPermission) {
- selectingPermission = true;
- $(this).addClass('minimize'); // this line flips the drop down arrow to a pull up arrow
- if ($(this).hasClass('co')) {
- $(this).append('
');
- } else if ($(this).hasClass('pu')) {
- $(this).append('
');
- } else if ($(this).hasClass('pr')) {
- $(this).append('
');
- }
- $('#edit_synapse .permissionSelect li').click(permissionLiClick);
- event.stopPropagation();
- }
- };
-
- var hidePermissionSelect = function () {
- selectingPermission = false;
- $('#edit_synapse.yourEdge .mapPerm').removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow
- $('#edit_synapse .permissionSelect').remove();
- };
-
- if (synapse.authorizePermissionChange(Metamaps.Active.Mapper)) {
- $('#edit_synapse.yourEdge .mapPerm').click(openPermissionSelect);
- $('#edit_synapse').click(hidePermissionSelect);
- }
- }, //add_perms_form
-
- add_direction_form: function (synapse) {
- //directionality checkboxes
- $('#editSynLowerBar').append('');
- $('#editSynLowerBar').append('');
-
- var edge = synapse.get('edge');
-
- //determine which node is to the left and the right
- //if directly in a line, top is left
- if (edge.nodeFrom.pos.x < edge.nodeTo.pos.x ||
- edge.nodeFrom.pos.x == edge.nodeTo.pos.x &&
- edge.nodeFrom.pos.y < edge.nodeTo.pos.y) {
- var left = edge.nodeTo.getData("topic");
- var right = edge.nodeFrom.getData("topic");
- } else {
- var left = edge.nodeFrom.getData("topic");
- var right = edge.nodeTo.getData("topic");
- }
-
- /*
- * One node is actually on the left onscreen. Call it left, & the other right.
- * If category is from-to, and that node is first, check the 'right' checkbox.
- * Else check the 'left' checkbox since the arrow is incoming.
- */
-
- var directionCat = synapse.get('category'); //both, none, from-to
- if (directionCat == 'from-to') {
- var from_to = [synapse.get("node1_id"), synapse.get("node2_id")];
- if (from_to[0] == left.id) {
- //check left checkbox
- $('#edit_synapse_left').addClass('checked');
- } else {
- //check right checkbox
- $('#edit_synapse_right').addClass('checked');
- }
- } else if (directionCat == 'both') {
- //check both checkboxes
- $('#edit_synapse_left').addClass('checked');
- $('#edit_synapse_right').addClass('checked');
- }
-
- if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) {
- $('#edit_synapse_left, #edit_synapse_right').click(function () {
-
- $(this).toggleClass('checked');
-
- var leftChecked = $('#edit_synapse_left').is('.checked');
- var rightChecked = $('#edit_synapse_right').is('.checked');
-
- var dir = synapse.getDirection();
- var dirCat = 'none';
- if (leftChecked && rightChecked) {
- dirCat = 'both';
- } else if (!leftChecked && rightChecked) {
- dirCat = 'from-to';
- dir = [right.id, left.id];
- } else if (leftChecked && !rightChecked) {
- dirCat = 'from-to';
- dir = [left.id, right.id];
- }
-
- synapse.save({
- category: dirCat,
- node1_id: dir[0],
- node2_id: dir[1]
- });
- Metamaps.Visualize.mGraph.plot();
- });
- } // if
- } //add_direction_form
-}; // end Metamaps.SynapseCard
-
-
-////////////////////// END TOPIC AND SYNAPSE CARDS //////////////////////////////////
-
-
-/*
- *
- * VISUALIZE
- *
- */
-Metamaps.Visualize = {
- mGraph: null, // a reference to the graph object.
- cameraPosition: null, // stores the camera position when using a 3D visualization
- type: "ForceDirected", // the type of graph we're building, could be "RGraph", "ForceDirected", or "ForceDirected3D"
- loadLater: false, // indicates whether there is JSON that should be loaded right in the offset, or whether to wait till the first topic is created
- init: function () {
- var self = Metamaps.Visualize;
- // disable awkward dragging of the canvas element that would sometimes happen
- $('#infovis-canvas').on('dragstart', function (event) {
- event.preventDefault();
- });
-
- // prevent touch events on the canvas from default behaviour
- $("#infovis-canvas").bind('touchstart', function (event) {
- event.preventDefault();
- self.mGraph.events.touched = true;
- });
-
- // prevent touch events on the canvas from default behaviour
- $("#infovis-canvas").bind('touchmove', function (event) {
- //Metamaps.JIT.touchPanZoomHandler(event);
- });
-
- // prevent touch events on the canvas from default behaviour
- $("#infovis-canvas").bind('touchend touchcancel', function (event) {
- lastDist = 0;
- if (!self.mGraph.events.touchMoved && !Metamaps.Touch.touchDragNode) Metamaps.TopicCard.hideCurrentCard();
- self.mGraph.events.touched = self.mGraph.events.touchMoved = false;
- Metamaps.Touch.touchDragNode = false;
- });
- },
- computePositions: function () {
- var self = Metamaps.Visualize,
- mapping;
-
- if (self.type == "RGraph") {
- var i, l, startPos, endPos, topic, synapse;
-
- self.mGraph.graph.eachNode(function (n) {
- topic = Metamaps.Topics.get(n.id);
- topic.set({ node: n }, { silent: true });
- topic.updateNode();
-
- n.eachAdjacency(function (edge) {
- if(!edge.getData('init')) {
- edge.setData('init', true);
-
- l = edge.getData('synapseIDs').length;
- for (i = 0; i < l; i++) {
- synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]);
- synapse.set({ edge: edge }, { silent: true });
- synapse.updateEdge();
- }
- }
- });
-
- var pos = n.getPos();
- pos.setc(-200, -200);
- });
- self.mGraph.compute('end');
- } else if (self.type == "ForceDirected") {
- var i, l, startPos, endPos, topic, synapse;
-
- self.mGraph.graph.eachNode(function (n) {
- topic = Metamaps.Topics.get(n.id);
- topic.set({ node: n }, { silent: true });
- topic.updateNode();
- mapping = topic.getMapping();
-
- n.eachAdjacency(function (edge) {
- if(!edge.getData('init')) {
- edge.setData('init', true);
-
- l = edge.getData('synapseIDs').length;
- for (i = 0; i < l; i++) {
- synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]);
- synapse.set({ edge: edge }, { silent: true });
- synapse.updateEdge();
- }
- }
- });
-
- startPos = new $jit.Complex(0, 0);
- endPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc'));
- n.setPos(startPos, 'start');
- n.setPos(endPos, 'end');
- });
- } else if (self.type == "ForceDirected3D") {
- self.mGraph.compute();
- }
- },
- /**
- * render does the heavy lifting of creating the engine that renders the graph with the properties we desire
- *
- */
- render: function () {
- var self = Metamaps.Visualize, RGraphSettings, FDSettings;
-
- if (self.type == "RGraph" && (!self.mGraph || self.mGraph instanceof $jit.ForceDirected)) {
-
- RGraphSettings = $.extend(true, {}, Metamaps.JIT.ForceDirected.graphSettings);
-
- $jit.RGraph.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings);
- $jit.RGraph.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings);
-
- RGraphSettings.width = $(document).width();
- RGraphSettings.height = $(document).height();
- RGraphSettings.background = Metamaps.JIT.RGraph.background;
- RGraphSettings.levelDistance = Metamaps.JIT.RGraph.levelDistance;
-
- self.mGraph = new $jit.RGraph(RGraphSettings);
-
- } else if (self.type == "ForceDirected" && (!self.mGraph || self.mGraph instanceof $jit.RGraph)) {
-
- FDSettings = $.extend(true, {}, Metamaps.JIT.ForceDirected.graphSettings);
-
- $jit.ForceDirected.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings);
- $jit.ForceDirected.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings);
-
- FDSettings.width = $('body').width();
- FDSettings.height = $('body').height();
-
- self.mGraph = new $jit.ForceDirected(FDSettings);
-
- } else if (self.type == "ForceDirected3D" && !self.mGraph) {
- // init ForceDirected3D
- self.mGraph = new $jit.ForceDirected3D(Metamaps.JIT.ForceDirected3D.graphSettings);
- self.cameraPosition = self.mGraph.canvas.canvases[0].camera.position;
- }
- else {
- self.mGraph.graph.empty();
- }
-
- function runAnimation() {
- Metamaps.Loading.hide();
- // load JSON data, if it's not empty
- if (!self.loadLater) {
- //load JSON data.
- var rootIndex = 0;
- if (Metamaps.Active.Topic) {
- var node = _.find(Metamaps.JIT.vizData, function(node){
- return node.id === Metamaps.Active.Topic.id;
- });
- rootIndex = _.indexOf(Metamaps.JIT.vizData, node);
- }
- self.mGraph.loadJSON(Metamaps.JIT.vizData, rootIndex);
- //compute positions and plot.
- self.computePositions();
- self.mGraph.busy = true;
- if (self.type == "RGraph") {
- self.mGraph.fx.animate(Metamaps.JIT.RGraph.animate);
- } else if (self.type == "ForceDirected") {
- self.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout);
- } else if (self.type == "ForceDirected3D") {
- self.mGraph.animate(Metamaps.JIT.ForceDirected.animateFDLayout);
- }
- }
- }
- // hold until all the needed metacode images are loaded
- // hold for a maximum of 80 passes, or 4 seconds of waiting time
- var tries = 0;
- function hold() {
- var unique = _.uniq(Metamaps.Topics.models, function (metacode) { return metacode.get('metacode_id'); }),
- requiredMetacodes = _.map(unique, function (metacode) { return metacode.get('metacode_id'); }),
- loadedCount = 0;
-
- _.each(requiredMetacodes, function (metacode_id) {
- var metacode = Metamaps.Metacodes.get(metacode_id),
- img = metacode ? metacode.get('image') : false;
-
- if (img && (img.complete || (typeof img.naturalWidth !== "undefined" && img.naturalWidth !== 0))) {
- loadedCount += 1;
- }
- });
-
- if (loadedCount === requiredMetacodes.length || tries > 80) runAnimation();
- else setTimeout(function(){ tries++; hold() }, 50);
- }
- hold();
-
- // update the url now that the map is ready
- clearTimeout(Metamaps.Router.timeoutId);
- Metamaps.Router.timeoutId = setTimeout(function(){
- var m = Metamaps.Active.Map;
- var t = Metamaps.Active.Topic;
-
- if (m && window.location.pathname !== "/maps/" + m.id) {
- Metamaps.Router.navigate("/maps/" + m.id);
- }
- else if (t && window.location.pathname !== "/topics/" + t.id) {
- Metamaps.Router.navigate("/topics/" + t.id);
- }
- }, 800);
-
- }
-}; // end Metamaps.Visualize
-
-
-/*
- *
- * UTIL
- *
- */
-Metamaps.Util = {
- // helper function to determine how many lines are needed
- // Line Splitter Function
- // copyright Stephen Chapman, 19th April 2006
- // you may copy this code but please keep the copyright notice as well
- splitLine: function (st, n) {
- var b = '';
- var s = st ? st : '';
- while (s.length > n) {
- var c = s.substring(0, n);
- var d = c.lastIndexOf(' ');
- var e = c.lastIndexOf('\n');
- if (e != -1) d = e;
- if (d == -1) d = n;
- b += c.substring(0, d) + '\n';
- s = s.substring(d + 1);
- }
- return b + s;
- },
- nowDateFormatted: function () {
- var date = new Date(Date.now());
- var month = (date.getMonth() + 1) < 10 ? '0' + (date.getMonth() + 1) : (date.getMonth() + 1);
- var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate();
- var year = date.getFullYear();
-
- return month + '/' + day + '/' + year;
- },
- decodeEntities: function (desc) {
- var str, temp = document.createElement('p');
- temp.innerHTML = desc; //browser handles the topics
- str = temp.textContent || temp.innerText;
- temp = null; //delete the element;
- return str;
- }, //decodeEntities
- getDistance: function (p1, p2) {
- return Math.sqrt(Math.pow((p2.x - p1.x), 2) + Math.pow((p2.y - p1.y), 2));
- },
- coordsToPixels: function (coords) {
- if (Metamaps.Visualize.mGraph) {
- var canvas = Metamaps.Visualize.mGraph.canvas,
- s = canvas.getSize(),
- p = canvas.getPos(),
- ox = canvas.translateOffsetX,
- oy = canvas.translateOffsetY,
- sx = canvas.scaleOffsetX,
- sy = canvas.scaleOffsetY;
- var pixels = {
- x: (coords.x / (1/sx)) + p.x + s.width/2 + ox,
- y: (coords.y / (1/sy)) + p.y + s.height/2 + oy
- };
- return pixels;
- }
- else {
- return {
- x: 0,
- y: 0
- };
- }
- },
- pixelsToCoords: function (pixels) {
- var coords;
- if (Metamaps.Visualize.mGraph) {
- var canvas = Metamaps.Visualize.mGraph.canvas,
- s = canvas.getSize(),
- p = canvas.getPos(),
- ox = canvas.translateOffsetX,
- oy = canvas.translateOffsetY,
- sx = canvas.scaleOffsetX,
- sy = canvas.scaleOffsetY;
- coords = {
- x: (pixels.x - p.x - s.width/2 - ox) * (1/sx),
- y: (pixels.y - p.y - s.height/2 - oy) * (1/sy),
- };
- }
- else {
- coords = {
- x: 0,
- y: 0
- };
- }
- return coords;
- },
- getPastelColor: function () {
- var r = (Math.round(Math.random()* 127) + 127).toString(16);
- var g = (Math.round(Math.random()* 127) + 127).toString(16);
- var b = (Math.round(Math.random()* 127) + 127).toString(16);
- return Metamaps.Util.colorLuminance('#' + r + g + b, -0.4);
- },
- // darkens a hex value by 'lum' percentage
- colorLuminance: function (hex, lum) {
-
- // validate hex string
- hex = String(hex).replace(/[^0-9a-f]/gi, '');
- if (hex.length < 6) {
- hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
- }
- lum = lum || 0;
-
- // convert to decimal and change luminosity
- var rgb = "#", c, i;
- for (i = 0; i < 3; i++) {
- c = parseInt(hex.substr(i*2,2), 16);
- c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
- rgb += ("00"+c).substr(c.length);
- }
-
- return rgb;
- },
- generateOptionsList: function (data) {
- var newlist = "";
- for (var i = 0; i < data.length; i++) {
- newlist = newlist + '';
- }
- return newlist;
- },
- checkURLisImage: function (url) {
- // when the page reloads the following regular expression will be screwed up
- // please replace it with this one before you save: /*backslashhere*.(jpeg|jpg|gif|png)$/
- return (url.match(/\.(jpeg|jpg|gif|png)$/) != null);
- },
- checkURLisYoutubeVideo: function (url) {
- 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) {
- var commonsMap = Metamaps.Active.Map.get('permission') === 'commons';
- var publicMap = Metamaps.Active.Map.get('permission') === 'public';
-
- if (commonsMap) {
- self.turnOn();
- self.setupSocket();
- }
- else if (publicMap) {
- 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();
- },
- reenableRealtime: function() {
- var confirmString = "The layout of your map has fallen out of sync with the saved copy. ";
- confirmString += "To save your changes without overwriting the map, hit 'Cancel' and ";
- confirmString += "then use 'Save to new map'. ";
- confirmString += "Do you want to discard your changes and enable realtime?";
- var c = confirm(confirmString);
- if (c) {
- Metamaps.Router.maps(Metamaps.Active.Map.id);
- }
- },
- turnOn: function (notify) {
- var self = Metamaps.Realtime;
-
- if (notify) self.sendRealtimeOn();
- //$(".rtMapperSelf").removeClass('littleRtOff').addClass('littleRtOn');
- //$('.rtOn').addClass('active');
- //$('.rtOff').removeClass('active');
- self.status = true;
- //$(".sidebarCollaborateIcon").addClass("blue");
- $(".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 permBefore = map.get('permission');
- var idBefore = map.id;
- map.fetch({
- success: function (model, response) {
-
- var idNow = model.id;
- var permNow = model.get('permission');
- if (idNow !== idBefore) {
- Metamaps.Map.leavePrivateMap(); // this means the map has been changed to private
- }
- else if (permNow === 'public' && permBefore === 'commons') {
- Metamaps.Map.commonsToPublic();
- }
- else if (permNow === 'commons' && permBefore === 'public') {
- Metamaps.Map.publicToCommons();
- }
- 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
+ reset: function () {
+ var self = Metamaps.Selected
+
+ self.Nodes = []
+ self.Edges = []
+ },
+ Nodes: [],
+ Edges: []
+}
diff --git a/app/assets/javascripts/src/check-canvas-support.js b/app/assets/javascripts/src/check-canvas-support.js
new file mode 100644
index 00000000..90afdde1
--- /dev/null
+++ b/app/assets/javascripts/src/check-canvas-support.js
@@ -0,0 +1,15 @@
+// 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),
+ typeOfCanvas = typeof HTMLCanvasElement,
+ nativeCanvasSupport = (typeOfCanvas == 'object' || typeOfCanvas == 'function'),
+ textSupport = nativeCanvasSupport && (typeof document.createElement('canvas').getContext('2d').fillText == 'function')
+ // I'm setting this based on the fact that ExCanvas provides text support for IE
+ // and that as of today iPhone/iPad current text support is lame
+ labelType = (!nativeCanvasSupport || (textSupport && !iStuff)) ? 'Native' : 'HTML'
+ nativeTextSupport = labelType == 'Native'
+ useGradients = nativeCanvasSupport
+ animate = !(iStuff || !nativeCanvasSupport)
+})()
diff --git a/app/assets/stylesheets/application.css.erb b/app/assets/stylesheets/application.css.erb
index 5970c18b..6c76dd9e 100644
--- a/app/assets/stylesheets/application.css.erb
+++ b/app/assets/stylesheets/application.css.erb
@@ -1569,6 +1569,11 @@ h3.filterBox {
background-repeat: no-repeat;
text-align: left;
}
+
+.commonsMap .mapContributors {
+ visibility: hidden;
+}
+
.mapContributors {
position: relative;
height: 30px;
@@ -1576,6 +1581,7 @@ h3.filterBox {
padding: 0;
width: 64px;
}
+
#mapContribs {
float: left;
border: 2px solid #424242;
@@ -1591,7 +1597,7 @@ h3.filterBox {
#mapContribs.multiple {
box-shadow: 1px 1px 0 0 #B5B5B5,3px 2px 0 0 #424242,4px 3px 0 0 #B5B5B5,5px 4px 0 0 #424242;
}
-.mapContributors span {
+.mapContributors span.count {
height: 20px;
padding-top: 5px;
padding-left: 8px;
@@ -1626,10 +1632,11 @@ h3.filterBox {
.mapContributors .tip {
top: 45px;
left: -10px;
+ min-width: 200px;
}
.mapContributors .tip ul {
- max-height: 188px;
+ max-height: 144px;
overflow-y: auto;
}
@@ -1652,7 +1659,7 @@ h3.filterBox {
.mapContributors .tip li a {
color: white;
}
-.mapContributors div:after {
+.mapContributors div.tip:after {
content: '';
position: absolute;
top: -4px;
@@ -1672,6 +1679,90 @@ h3.filterBox {
border-radius: 14px;
}
+.mapContributors span.twitter-typeahead {
+ padding: 0;
+}
+
+.collabSearchField {
+ text-align: left;
+}
+
+.collabNameWrapper {
+ float: left;
+}
+
+.collabIconWrapper img.icon {
+ position: relative;
+ top: 0;
+ left: 0;
+}
+
+.collabIconWrapper {
+ position: relative;
+ float: left;
+ padding: 0 4px;
+}
+
+.mapContributors .collabName {
+ font-weight: normal;
+ font-size: 14px;
+ line-height: 28px;
+ color: #424242;
+ padding: 0 4px;
+}
+
+span.removeCollaborator {
+ position: absolute;
+ top: 11px;
+ right: 8px;
+ height: 16px;
+ width: 16px;
+ background-image: url(<%= asset_data_uri('removecollab_sprite.png') %>);
+ cursor: pointer;
+}
+
+span.removeCollaborator:hover {
+ background-position: -16px 0;
+}
+
+span.addCollab {
+ width: 16px;
+ height: 16px;
+ background-image: url(<%= asset_data_uri('addcollab_sprite.png') %>);
+ display: inline-block;
+ vertical-align: middle;
+ margin: 0 12px 0 10px;
+}
+
+input.collaboratorSearchField {
+ background: #FFFFFF;
+ height: 14px;
+ margin: 0;
+ padding: 10px 6px;
+ border: none;
+ border-radius: 2px;
+ outline: none;
+ font-size: 14px;
+ line-height: 14px;
+ color: #424242;
+ font-family: 'din-medium', helvetica, sans-serif;
+}
+
+.tt-dataset.tt-dataset-collaborators {
+ padding: 2px;
+ background: #E0E0E0;
+ min-width: 156px;
+ border-radius: 2px;
+}
+
+.tt-dataset.tt-dataset .collabResult {
+ padding: 4px;
+}
+
+.collabResult.tt-suggestion.tt-cursor, .collabResult.tt-suggestion:hover {
+ background-color: #CCCCCC;
+}
+
.mapInfoBox .mapPermission .tooltips {
top: -20px;
right: 36px;
diff --git a/app/assets/stylesheets/clean.css.erb b/app/assets/stylesheets/clean.css.erb
index 5665716a..47e18a94 100644
--- a/app/assets/stylesheets/clean.css.erb
+++ b/app/assets/stylesheets/clean.css.erb
@@ -681,6 +681,10 @@
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>);
background-position: 0 0;
}
+.exploreMapsCenter .sharedMaps .exploreMapsIcon {
+ background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>);
+ background-position: -96px 0;
+}
.exploreMapsCenter .activeMaps .exploreMapsIcon {
background-image: url(<%= asset_data_uri 'exploremaps_sprite.png' %>);
background-position: -32px 0;
@@ -698,6 +702,9 @@
.featuredMaps:hover .exploreMapsIcon, .featuredMaps.active .exploreMapsIcon {
background-position: -64px -32px;
}
+.sharedMaps:hover .exploreMapsIcon, .sharedMaps.active .exploreMapsIcon {
+ background-position: -96px -32px;
+}
.mapsWrapper {
/*overflow-y: auto; */
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index c1e9e26d..5db7d62a 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -32,7 +32,11 @@ class ApplicationController < ActionController::Base
end
def handle_unauthorized
- head :forbidden # TODO make this better
+ if authenticated?
+ head :forbidden # TODO make this better
+ else
+ redirect_to new_user_session_path, notice: "Try signing in to do that."
+ end
end
private
diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb
index f7d9a84a..87264c4c 100644
--- a/app/controllers/main_controller.rb
+++ b/app/controllers/main_controller.rb
@@ -4,7 +4,7 @@ class MainController < ApplicationController
include UsersHelper
include SynapsesHelper
- after_action :verify_policy_scoped, except: :requestinvite
+ after_action :verify_policy_scoped, except: [:requestinvite, :searchmappers]
respond_to :html, :json
@@ -133,9 +133,9 @@ class MainController < ApplicationController
#remove "mapper:" if appended at beginning
term = term[7..-1] if term.downcase[0..6] == "mapper:"
search = term.downcase + '%'
+
skip_policy_scope # TODO builder = policy_scope(User)
- builder = User
- builder = builder.where('LOWER("name") like ?', search)
+ builder = User.where('LOWER("name") like ?', search)
@mappers = builder.order(:name)
else
@mappers = []
diff --git a/app/controllers/mappings_controller.rb b/app/controllers/mappings_controller.rb
index 936ffcc2..9a5f1f1f 100644
--- a/app/controllers/mappings_controller.rb
+++ b/app/controllers/mappings_controller.rb
@@ -45,6 +45,13 @@ class MappingsController < ApplicationController
@mapping = Mapping.find(params[:id])
authorize @mapping
+ mappable = @mapping.mappable
+ if mappable.defer_to_map
+ mappable.permission = mappable.defer_to_map.permission
+ mappable.defer_to_map_id = nil
+ mappable.save
+ end
+
@mapping.destroy
head :no_content
diff --git a/app/controllers/maps_controller.rb b/app/controllers/maps_controller.rb
index 91eb97e1..b605b5ad 100644
--- a/app/controllers/maps_controller.rb
+++ b/app/controllers/maps_controller.rb
@@ -1,8 +1,7 @@
class MapsController < ApplicationController
-
- before_action :require_user, only: [:create, :update, :screenshot, :events, :destroy]
- after_action :verify_authorized, except: [:activemaps, :featuredmaps, :mymaps, :usermaps, :events]
- after_action :verify_policy_scoped, only: [:activemaps, :featuredmaps, :mymaps, :usermaps]
+ before_action :require_user, only: [:create, :update, :access, :screenshot, :events, :destroy]
+ after_action :verify_authorized, except: [:activemaps, :featuredmaps, :mymaps, :sharedmaps, :usermaps, :events]
+ after_action :verify_policy_scoped, only: [:activemaps, :featuredmaps, :mymaps, :sharedmaps, :usermaps]
respond_to :html, :json, :csv
@@ -53,6 +52,21 @@ class MapsController < ApplicationController
end
end
+ # GET /explore/shared
+ def sharedmaps
+ return redirect_to activemaps_url if !authenticated?
+
+ page = params[:page].present? ? params[:page] : 1
+ @maps = policy_scope(
+ Map.where("maps.id IN (?)", current_user.shared_maps.map(&:id))
+ ).order("updated_at DESC").page(page).per(20)
+
+ respond_to do |format|
+ format.html { respond_with(@maps, @user) }
+ format.json { render json: @maps }
+ end
+ end
+
# GET /explore/mapper/:id
def usermaps
page = params[:page].present? ? params[:page] : 1
@@ -74,15 +88,13 @@ class MapsController < ApplicationController
respond_to do |format|
format.html {
@allmappers = @map.contributors
- @alltopics = @map.topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && current_user.id != t.user_id)) }
- @allsynapses = @map.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && current_user.id != s.user_id)) }
- @allmappings = @map.mappings.to_a.delete_if {|m|
- object = m.mappable
- !object || (object.permission == "private" && (!authenticated? || (authenticated? && current_user.id != object.user_id)))
- }
+ @allcollaborators = @map.editors
+ @alltopics = @map.topics.to_a.delete_if {|t| not policy(t).show? }
+ @allsynapses = @map.synapses.to_a.delete_if {|s| not policy(s).show? }
+ @allmappings = @map.mappings.to_a.delete_if {|m| not policy(m).show? }
@allmessages = @map.messages.sort_by(&:created_at)
- respond_with(@allmappers, @allmappings, @allsynapses, @alltopics, @allmessages, @map)
+ respond_with(@allmappers, @allcollaborators, @allmappings, @allsynapses, @alltopics, @allmessages, @map)
}
format.json { render json: @map }
format.csv { redirect_to action: :export, format: :csv }
@@ -127,12 +139,11 @@ class MapsController < ApplicationController
authorize @map
@allmappers = @map.contributors
- @alltopics = @map.topics.to_a.delete_if {|t| t.permission == "private" && (!authenticated? || (authenticated? && current_user.id != t.user_id)) }
- @allsynapses = @map.synapses.to_a.delete_if {|s| s.permission == "private" && (!authenticated? || (authenticated? && current_user.id != s.user_id)) }
- @allmappings = @map.mappings.to_a.delete_if {|m|
- object = m.mappable
- !object || (object.permission == "private" && (!authenticated? || (authenticated? && current_user.id != object.user_id)))
- }
+ @allcollaborators = @map.editors
+ @alltopics = @map.topics.to_a.delete_if {|t| not policy(t).show? }
+ @allsynapses = @map.synapses.to_a.delete_if {|s| not policy(s).show? }
+ @allmappings = @map.mappings.to_a.delete_if {|m| not policy(m).show? }
+
@json = Hash.new()
@json['map'] = @map
@@ -140,6 +151,7 @@ class MapsController < ApplicationController
@json['synapses'] = @allsynapses
@json['mappings'] = @allmappings
@json['mappers'] = @allmappers
+ @json['collaborators'] = @allcollaborators
@json['messages'] = @map.messages.sort_by(&:created_at)
respond_to do |format|
@@ -215,6 +227,36 @@ class MapsController < ApplicationController
end
end
+ # POST maps/:id/access
+ def access
+ @map = Map.find(params[:id])
+ authorize @map
+ userIds = params[:access] || []
+ added = userIds.select { |uid|
+ user = User.find(uid)
+ if user.nil? || (current_user && user == current_user)
+ false
+ else
+ not @map.collaborators.include?(user)
+ end
+ }
+ removed = @map.collaborators.select { |user| not userIds.include?(user.id.to_s) }.map(&:id)
+ added.each { |uid|
+ um = UserMap.create({ user_id: uid.to_i, map_id: @map.id })
+ user = User.find(uid.to_i)
+ MapMailer.invite_to_edit_email(@map, current_user, user).deliver_later
+ }
+ removed.each { |uid|
+ @map.user_maps.select{ |um| um.user_id == uid }.each{ |um| um.destroy }
+ }
+
+ respond_to do |format|
+ format.json do
+ render :json => { :message => "Successfully altered edit permissions" }
+ end
+ end
+ end
+
# POST maps/:id/upload_screenshot
def screenshot
@map = Map.find(params[:id])
diff --git a/app/controllers/synapses_controller.rb b/app/controllers/synapses_controller.rb
index 4440872f..b8ccfc5f 100644
--- a/app/controllers/synapses_controller.rb
+++ b/app/controllers/synapses_controller.rb
@@ -51,7 +51,7 @@ class SynapsesController < ApplicationController
def destroy
@synapse = Synapse.find(params[:id])
authorize @synapse
- @synapse.delete
+ @synapse.destroy
respond_to do |format|
format.json { head :no_content }
diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb
index 9911a7fe..88e9cef4 100644
--- a/app/controllers/topics_controller.rb
+++ b/app/controllers/topics_controller.rb
@@ -150,7 +150,7 @@ puts @allsynapses.length
@topic = Topic.find(params[:id])
authorize @topic
- @topic.delete
+ @topic.destroy
respond_to do |format|
format.json { head :no_content }
end
@@ -159,6 +159,6 @@ puts @allsynapses.length
private
def topic_params
- params.require(:topic).permit(:id, :name, :desc, :link, :permission, :user_id, :metacode_id)
+ params.require(:topic).permit(:id, :name, :desc, :link, :permission, :user_id, :metacode_id, :defer_to_map_id)
end
end
diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb
new file mode 100644
index 00000000..0d9431a3
--- /dev/null
+++ b/app/mailers/application_mailer.rb
@@ -0,0 +1,4 @@
+class ApplicationMailer < ActionMailer::Base
+ default from: "team@metamaps.cc"
+ layout 'mailer'
+end
diff --git a/app/mailers/map_mailer.rb b/app/mailers/map_mailer.rb
new file mode 100644
index 00000000..dd164cf3
--- /dev/null
+++ b/app/mailers/map_mailer.rb
@@ -0,0 +1,10 @@
+class MapMailer < ApplicationMailer
+ default from: "team@metamaps.cc"
+
+ def invite_to_edit_email(map, inviter, invitee)
+ @inviter = inviter
+ @map = map
+ subject = @map.name + ' - Invitation to edit'
+ mail(to: invitee.email, subject: subject)
+ end
+end
diff --git a/app/models/map.rb b/app/models/map.rb
index d9eb6a18..8ffe14d6 100644
--- a/app/models/map.rb
+++ b/app/models/map.rb
@@ -8,6 +8,9 @@ class Map < ActiveRecord::Base
has_many :synapses, through: :synapsemappings, source: :mappable, source_type: "Synapse"
has_many :messages, as: :resource, dependent: :destroy
+ has_many :user_maps, dependent: :destroy
+ has_many :collaborators, through: :user_maps, source: :user
+
has_many :webhooks, as: :hookable
has_many :events, -> { includes :user }, as: :eventable, dependent: :destroy
@@ -45,6 +48,10 @@ class Map < ActiveRecord::Base
return contributors
end
+ def editors
+ collaborators + [self.user]
+ end
+
def topic_count
topics.length
end
@@ -65,6 +72,10 @@ class Map < ActiveRecord::Base
contributors.length
end
+ def collaborator_ids
+ collaborators.map(&:id)
+ end
+
def screenshot_url
screenshot.url(:thumb)
end
@@ -78,7 +89,7 @@ class Map < ActiveRecord::Base
end
def as_json(options={})
- json = super(:methods =>[:user_name, :user_image, :topic_count, :synapse_count, :contributor_count, :screenshot_url], :except => [:screenshot_content_type, :screenshot_file_size, :screenshot_file_name, :screenshot_updated_at])
+ json = super(:methods =>[:user_name, :user_image, :topic_count, :synapse_count, :contributor_count, :collaborator_ids, :screenshot_url], :except => [:screenshot_content_type, :screenshot_file_size, :screenshot_file_name, :screenshot_updated_at])
json[:created_at_clean] = created_at_str
json[:updated_at_clean] = updated_at_str
json
diff --git a/app/models/synapse.rb b/app/models/synapse.rb
index 540376bb..7e361af3 100644
--- a/app/models/synapse.rb
+++ b/app/models/synapse.rb
@@ -1,5 +1,6 @@
class Synapse < ActiveRecord::Base
belongs_to :user
+ belongs_to :defer_to_map, :class_name => 'Map', :foreign_key => 'defer_to_map_id'
belongs_to :topic1, :class_name => "Topic", :foreign_key => "node1_id"
belongs_to :topic2, :class_name => "Topic", :foreign_key => "node2_id"
@@ -32,9 +33,29 @@ class Synapse < ActiveRecord::Base
end
# :nocov:
+ # :nocov:
+ def collaborator_ids
+ if defer_to_map
+ defer_to_map.editors.select{|mapper| not mapper == self.user }.map(&:id)
+ else
+ []
+ end
+ end
+ # :nocov:
+
+ # :nocov:
+ def calculated_permission
+ if defer_to_map
+ defer_to_map.permission
+ else
+ permission
+ end
+ end
+ # :nocov:
+
# :nocov:
def as_json(options={})
- super(:methods =>[:user_name, :user_image])
+ super(:methods =>[:user_name, :user_image, :calculated_permission, :collaborator_ids])
end
# :nocov:
diff --git a/app/models/topic.rb b/app/models/topic.rb
index f1a73c1b..61f405de 100644
--- a/app/models/topic.rb
+++ b/app/models/topic.rb
@@ -2,6 +2,7 @@ class Topic < ActiveRecord::Base
include TopicsHelper
belongs_to :user
+ belongs_to :defer_to_map, :class_name => 'Map', :foreign_key => 'defer_to_map_id'
has_many :synapses1, :class_name => 'Synapse', :foreign_key => 'node1_id', dependent: :destroy
has_many :synapses2, :class_name => 'Synapse', :foreign_key => 'node2_id', dependent: :destroy
@@ -11,6 +12,8 @@ class Topic < ActiveRecord::Base
has_many :mappings, as: :mappable, dependent: :destroy
has_many :maps, :through => :mappings
+ belongs_to :metacode
+
validates :permission, presence: true
validates :permission, inclusion: { in: Perm::ISSIONS.map(&:to_s) }
@@ -39,8 +42,6 @@ class Topic < ActiveRecord::Base
topics1 + topics2
end
- belongs_to :metacode
-
scope :relatives1, ->(topic_id = nil) {
includes(:topics1)
.where('synapses.node1_id = ?', topic_id)
@@ -77,8 +78,24 @@ class Topic < ActiveRecord::Base
maps.map(&:id)
end
+ def calculated_permission
+ if defer_to_map
+ defer_to_map.permission
+ else
+ permission
+ end
+ end
+
def as_json(options={})
- super(:methods =>[:user_name, :user_image, :map_count, :synapse_count, :inmaps, :inmapsLinks])
+ super(:methods =>[:user_name, :user_image, :map_count, :synapse_count, :inmaps, :inmapsLinks, :calculated_permission, :collaborator_ids])
+ end
+
+ def collaborator_ids
+ if defer_to_map
+ defer_to_map.editors.select{|mapper| not mapper == self.user }.map(&:id)
+ else
+ []
+ end
end
# TODO move to a decorator?
diff --git a/app/models/user.rb b/app/models/user.rb
index 10a0a71c..a38d7177 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -7,7 +7,9 @@ class User < ActiveRecord::Base
has_many :maps
has_many :mappings
has_many :tokens
-
+ has_many :user_maps, dependent: :destroy
+ has_many :shared_maps, through: :user_maps, source: :map
+
after_create :generate_code
devise :database_authenticatable, :recoverable, :rememberable, :trackable, :registerable
diff --git a/app/models/user_map.rb b/app/models/user_map.rb
new file mode 100644
index 00000000..5e91ecc2
--- /dev/null
+++ b/app/models/user_map.rb
@@ -0,0 +1,4 @@
+class UserMap < ActiveRecord::Base
+ belongs_to :map
+ belongs_to :user
+end
diff --git a/app/policies/map_policy.rb b/app/policies/map_policy.rb
index b1ece0e3..17943086 100644
--- a/app/policies/map_policy.rb
+++ b/app/policies/map_policy.rb
@@ -4,7 +4,8 @@ class MapPolicy < ApplicationPolicy
visible = ['public', 'commons']
permission = 'maps.permission IN (?)'
if user
- scope.where(permission + ' OR maps.user_id = ?', visible, user.id)
+ shared_maps = user.shared_maps.map(&:id)
+ scope.where(permission + ' OR maps.id IN (?) OR maps.user_id = ?', visible, shared_maps, user.id)
else
scope.where(permission, visible)
end
@@ -28,7 +29,7 @@ class MapPolicy < ApplicationPolicy
end
def show?
- record.permission == 'commons' || record.permission == 'public' || record.user == user
+ record.permission == 'commons' || record.permission == 'public' || record.collaborators.include?(user) || record.user == user
end
def export?
@@ -48,7 +49,12 @@ class MapPolicy < ApplicationPolicy
end
def update?
- user.present? && (record.permission == 'commons' || record.user == user)
+ user.present? && (record.permission == 'commons' || record.collaborators.include?(user) || record.user == user)
+ end
+
+ def access?
+ # note that this is to edit access
+ user.present? && record.user == user
end
def screenshot?
diff --git a/app/policies/synapse_policy.rb b/app/policies/synapse_policy.rb
index 042c9a75..9b1a8524 100644
--- a/app/policies/synapse_policy.rb
+++ b/app/policies/synapse_policy.rb
@@ -4,7 +4,7 @@ class SynapsePolicy < ApplicationPolicy
visible = ['public', 'commons']
permission = 'synapses.permission IN (?)'
if user
- scope.where(permission + ' OR synapses.user_id = ?', visible, user.id)
+ scope.where(permission + ' OR synapses.defer_to_map_id IN (?) OR synapses.user_id = ?', visible, user.shared_maps.map(&:id), user.id)
else
scope.where(permission, visible)
end
@@ -17,14 +17,29 @@ class SynapsePolicy < ApplicationPolicy
end
def show?
- record.permission == 'commons' || record.permission == 'public' || record.user == user
+ if record.defer_to_map.present?
+ map_policy.show?
+ else
+ record.permission == 'commons' || record.permission == 'public' || record.user == user
+ end
end
def update?
- user.present? && (record.permission == 'commons' || record.user == user)
+ if not user.present?
+ false
+ elsif record.defer_to_map.present?
+ map_policy.update?
+ else
+ record.permission == 'commons' || record.user == user
+ end
end
def destroy?
record.user == user || admin_override
end
+
+ # Helpers
+ def map_policy
+ @map_policy ||= Pundit.policy(user, record.defer_to_map)
+ end
end
diff --git a/app/policies/topic_policy.rb b/app/policies/topic_policy.rb
index 335a2ed2..2eb2abb6 100644
--- a/app/policies/topic_policy.rb
+++ b/app/policies/topic_policy.rb
@@ -4,7 +4,7 @@ class TopicPolicy < ApplicationPolicy
visible = ['public', 'commons']
permission = 'topics.permission IN (?)'
if user
- scope.where(permission + ' OR topics.user_id = ?', visible, user.id)
+ scope.where(permission + ' OR topics.defer_to_map_id IN (?) OR topics.user_id = ?', visible, user.shared_maps.map(&:id), user.id)
else
scope.where(permission, visible)
end
@@ -16,11 +16,21 @@ class TopicPolicy < ApplicationPolicy
end
def show?
- record.permission == 'commons' || record.permission == 'public' || record.user == user
+ if record.defer_to_map.present?
+ map_policy.show?
+ else
+ record.permission == 'commons' || record.permission == 'public' || record.user == user
+ end
end
def update?
- user.present? && (record.permission == 'commons' || record.user == user)
+ if not user.present?
+ false
+ elsif record.defer_to_map.present?
+ map_policy.update?
+ else
+ record.permission == 'commons' || record.user == user
+ end
end
def destroy?
@@ -42,4 +52,9 @@ class TopicPolicy < ApplicationPolicy
def relatives?
show?
end
+
+ # Helpers
+ def map_policy
+ @map_policy ||= Pundit.policy(user, record.defer_to_map)
+ end
end
diff --git a/app/serializers/new_map_serializer.rb b/app/serializers/new_map_serializer.rb
index 9b2ff400..3c56f82f 100644
--- a/app/serializers/new_map_serializer.rb
+++ b/app/serializers/new_map_serializer.rb
@@ -12,5 +12,6 @@ class NewMapSerializer < ActiveModel::Serializer
has_many :synapses, serializer: NewSynapseSerializer
has_many :mappings, serializer: NewMappingSerializer
has_many :contributors, root: :users, serializer: NewUserSerializer
+ has_many :collaborators, root: :users, serializer: NewUserSerializer
end
diff --git a/app/views/layouts/_templates.html.erb b/app/views/layouts/_templates.html.erb
index b3099ad9..7e5a6293 100644
--- a/app/views/layouts/_templates.html.erb
+++ b/app/views/layouts/_templates.html.erb
@@ -12,7 +12,7 @@
- {{contributor_count}}
+ {{contributor_count}}
{{{contributor_list}}}
@@ -181,6 +181,18 @@
+
+
diff --git a/app/views/maps/show.html.erb b/app/views/maps/show.html.erb
index 5d6859b7..9a74b337 100644
--- a/app/views/maps/show.html.erb
+++ b/app/views/maps/show.html.erb
@@ -10,6 +10,7 @@
Metamaps.currentPage = <%= @map.id.to_s %>;
Metamaps.Active.Map = <%= @map.to_json.html_safe %>;
Metamaps.Mappers = <%= @allmappers.to_json.html_safe %>;
+ Metamaps.Collaborators = <%= @allcollaborators.to_json.html_safe %>;
Metamaps.Topics = <%= @alltopics.to_json.html_safe %>;
Metamaps.Synapses = <%= @allsynapses.to_json.html_safe %>;
Metamaps.Mappings = <%= @allmappings.to_json.html_safe %>;
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 593fbd3c..bad33ab9 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -30,7 +30,7 @@ Metamaps::Application.configure do
port: ENV['SMTP_PORT'],
user_name: ENV['SMTP_USERNAME'],
password: ENV['SMTP_PASSWORD'],
- #domain: ENV['SMTP_DOMAIN']
+ domain: ENV['SMTP_DOMAIN'],
authentication: 'plain',
enable_starttls_auto: true,
openssl_verify_mode: 'none' }
@@ -41,6 +41,8 @@ Metamaps::Application.configure do
# Print deprecation notices to the Rails logger
config.active_support.deprecation = :log
+ config.action_mailer.preview_path = '/vagrant/spec/mailers/previews'
+
# Expands the lines which load the assets
config.assets.debug = true
end
diff --git a/config/routes.rb b/config/routes.rb
index 345547f4..a60749b7 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -40,12 +40,13 @@ Metamaps::Application.routes.draw do
post 'maps/:id/events/:event', to: 'maps#events'
get 'maps/:id/contains', to: 'maps#contains', as: :contains
post 'maps/:id/upload_screenshot', to: 'maps#screenshot', as: :screenshot
+ post 'maps/:id/access', to: 'maps#access', as: :access, defaults: {format: :json}
get 'explore/active', to: 'maps#activemaps'
get 'explore/featured', to: 'maps#featuredmaps'
get 'explore/mine', to: 'maps#mymaps'
+ get 'explore/shared', to: 'maps#sharedmaps'
get 'explore/mapper/:id', to: 'maps#usermaps'
-
devise_for :users, controllers: { registrations: 'users/registrations', passwords: 'users/passwords', sessions: 'devise/sessions' }, :skip => :sessions
diff --git a/db/migrate/20160331181959_create_user_maps.rb b/db/migrate/20160331181959_create_user_maps.rb
new file mode 100644
index 00000000..2af6e87a
--- /dev/null
+++ b/db/migrate/20160331181959_create_user_maps.rb
@@ -0,0 +1,10 @@
+class CreateUserMaps < ActiveRecord::Migration
+ def change
+ create_table :user_maps do |t|
+ t.references :user, index: true
+ t.references :map, index: true
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20160401133937_add_defers_to_map_to_topics_and_synapses.rb b/db/migrate/20160401133937_add_defers_to_map_to_topics_and_synapses.rb
new file mode 100644
index 00000000..afcd6df2
--- /dev/null
+++ b/db/migrate/20160401133937_add_defers_to_map_to_topics_and_synapses.rb
@@ -0,0 +1,6 @@
+class AddDefersToMapToTopicsAndSynapses < ActiveRecord::Migration
+ def change
+ add_column :topics, :defer_to_map_id, :integer
+ add_column :synapses, :defer_to_map_id, :integer
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 1ad745a2..d3dc9634 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20160318141618) do
+ActiveRecord::Schema.define(version: 20160401133937) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -180,8 +180,9 @@ ActiveRecord::Schema.define(version: 20160318141618) do
t.integer "node1_id"
t.integer "node2_id"
t.integer "user_id"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "defer_to_map_id"
end
add_index "synapses", ["node1_id", "node1_id"], name: "index_synapses_on_node1_id_and_node1_id", using: :btree
@@ -217,11 +218,22 @@ ActiveRecord::Schema.define(version: 20160318141618) do
t.string "audio_content_type"
t.integer "audio_file_size"
t.datetime "audio_updated_at"
+ t.integer "defer_to_map_id"
end
add_index "topics", ["metacode_id"], name: "index_topics_on_metacode_id", using: :btree
add_index "topics", ["user_id"], name: "index_topics_on_user_id", using: :btree
+ create_table "user_maps", force: :cascade do |t|
+ t.integer "user_id"
+ t.integer "map_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "user_maps", ["map_id"], name: "index_user_maps_on_map_id", using: :btree
+ add_index "user_maps", ["user_id"], name: "index_user_maps_on_user_id", using: :btree
+
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
diff --git a/public/famous/main.js b/public/famous/main.js
index 2a147e83..7ec38c5c 100644
--- a/public/famous/main.js
+++ b/public/famous/main.js
@@ -303,7 +303,7 @@ Metamaps.Famous.build = function () {
var loggedIn = Metamaps.Active.Mapper ? 'Auth' : '';
- if (section === "mine" || section === "active" || section === "featured") {
+ if (section === "mine" || section === "shared" || section === "active" || section === "featured") {
f.explore.surf.setContent(templates[section + loggedIn + 'Content']);
}
else if (section === "mapper") {
diff --git a/public/famous/templates.js b/public/famous/templates.js
index 66e6b9ac..fa1024bb 100644
--- a/public/famous/templates.js
+++ b/public/famous/templates.js
@@ -19,16 +19,17 @@ t.logoContent += '';
/* logged in explore maps bars */
t.mineAuthContent = 'My Maps';
+ t.mineAuthContent += 'Shared With Me';
t.mineAuthContent += 'Recently Active';
- t.mineAuthContent += 'Featured';
+
+ t.sharedAuthContent = 'My Maps';
+ t.sharedAuthContent += 'Shared With Me';
+ t.sharedAuthContent += 'Recently Active';
t.activeAuthContent = 'My Maps';
+ t.activeAuthContent += 'Shared With Me';
t.activeAuthContent += 'Recently Active';
- t.activeAuthContent += 'Featured';
- t.featuredAuthContent = 'My Maps';
- t.featuredAuthContent += 'Recently Active';
- t.featuredAuthContent += 'Featured';
/* apps bars */
t.registeredAppsContent = 'Registered Apps';
diff --git a/spec/mailers/map_mailer_spec.rb b/spec/mailers/map_mailer_spec.rb
new file mode 100644
index 00000000..8572c8f5
--- /dev/null
+++ b/spec/mailers/map_mailer_spec.rb
@@ -0,0 +1,5 @@
+require "rails_helper"
+
+RSpec.describe MapMailer, type: :mailer do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/mailers/previews/map_mailer_preview.rb b/spec/mailers/previews/map_mailer_preview.rb
new file mode 100644
index 00000000..60310bf4
--- /dev/null
+++ b/spec/mailers/previews/map_mailer_preview.rb
@@ -0,0 +1,6 @@
+# Preview all emails at http://localhost:3000/rails/mailers/map_mailer
+class MapMailerPreview < ActionMailer::Preview
+ def invite_to_edit_email
+ MapMailer.invite_to_edit_email(Map.first, User.first, User.second)
+ end
+end