From f58db49bc10de09b37fa67524407517894434fd5 Mon Sep 17 00:00:00 2001 From: Connor Turland Date: Mon, 27 Oct 2014 12:03:55 -0400 Subject: [PATCH] realtime --- CHANGELOG.md | 4 + app/assets/javascripts/src/Metamaps.JIT.js | 21 +- app/assets/javascripts/src/Metamaps.js | 451 ++++++++++++++++++--- app/assets/stylesheets/uservoice.css | 3 +- app/controllers/maps_controller.rb | 4 +- realtime/realtime-server.js | 28 +- 6 files changed, 446 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67150012..c8168bb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.6 +- Backbone.js in use for client side models +- Realtime depends entirely on sockets for transmitting changes, rather than server using redis to push + ## 2.5 - Initial release diff --git a/app/assets/javascripts/src/Metamaps.JIT.js b/app/assets/javascripts/src/Metamaps.JIT.js index 4ad81402..4ecda48b 100644 --- a/app/assets/javascripts/src/Metamaps.JIT.js +++ b/app/assets/javascripts/src/Metamaps.JIT.js @@ -3,8 +3,10 @@ Metamaps.JIT = { mouseMove: 'Metamaps:JIT:events:mouseMove', topicDrag: 'Metamaps:JIT:events:topicDrag', newTopic: 'Metamaps:JIT:events:newTopic', + deleteTopic: 'Metamaps:JIT:events:deleteTopic', removeTopic: 'Metamaps:JIT:events:removeTopic', newSynapse: 'Metamaps:JIT:events:newSynapse', + deleteSynapse: 'Metamaps:JIT:events:deleteSynapse', removeSynapse: 'Metamaps:JIT:events:removeSynapse', pan: 'Metamaps:JIT:events:pan', zoom: 'Metamaps:JIT:events:zoom', @@ -915,7 +917,24 @@ Metamaps.JIT = { tempInit = false; } else if (!tempInit && node && !node.nodeFrom) { // this means you dragged an existing node, autosave that to the database - if (Metamaps.Active.Map) { + + // check whether to save mappings + var checkWhetherToSave = function() { + var map = Metamaps.Active.Map; + var mapper = Metamaps.Active.Mapper; + // this case + // covers when it is a public map owned by you + // and also when it's a private map + var activeMappersMap = map.authorizePermissionChange(mapper); + var commonsMap = map.get('permission') === 'commons'; + var realtimeOn = Metamaps.Realtime.status; + + // don't save if commons map, and you have realtime off, + // even if you're map creator + return map && mapper && ((commonsMap && realtimeOn) || (activeMappersMap && !commonsMap)); + } + + if (checkWhetherToSave()) { mapping = node.getData('mapping'); mapping.save({ xloc: node.getPos().x, diff --git a/app/assets/javascripts/src/Metamaps.js b/app/assets/javascripts/src/Metamaps.js index 734efdb5..4928ad8f 100644 --- a/app/assets/javascripts/src/Metamaps.js +++ b/app/assets/javascripts/src/Metamaps.js @@ -65,7 +65,7 @@ Metamaps.Selected = { var self = Metamaps.Selected; self.Nodes = []; - self.edges = []; + self.Edges = []; }, Nodes: [], Edges: [] @@ -111,6 +111,36 @@ Metamaps.Backbone.init = function () { toJSON: function (options) { return _.omit(this.attributes, this.blacklist); }, + save: function (key, val, options) { + + var attrs; + + // Handle both `"key", value` and `{key: value}` -style arguments. + if (key == null || typeof key === 'object') { + attrs = key; + options = val; + } else { + (attrs = {})[key] = val; + } + + var newOptions = options || {}; + var s = newOptions.success; + + var permBefore = this.get('permission'); + + newOptions.success = function (model, response, opt) { + if (s) s(model, response, opt); + model.trigger('saved'); + + if (permBefore === 'private' && model.get('permission') !== 'private') { + model.trigger('noLongerPrivate'); + } + else if (permBefore !== 'private' && model.get('permission') === 'private') { + model.trigger('nowPrivate'); + } + }; + return Backbone.Model.prototype.save.call(this, attrs, newOptions); + }, initialize: function () { if (this.isNew()) { this.set({ @@ -121,13 +151,27 @@ Metamaps.Backbone.init = function () { }); } + this.on('changeByOther', this.updateCardView); + this.on('change', this.updateNodeView); + this.on('saved', this.savedEvent); + this.on('nowPrivate', function(){ + var removeTopicData = { + topicid: this.id + }; + + $(document).trigger(Metamaps.JIT.events.removeTopic, [removeTopicData]); + }); + this.on('noLongerPrivate', function(){ + var newTopicData = { + mappingid: this.getMapping().id, + topicid: this.id + }; + + $(document).trigger(Metamaps.JIT.events.newTopic, [newTopicData]); + }); + this.on('change:metacode_id', Metamaps.Filter.checkMetacodes, this); - var updateName = function () { - if (this.get('node')) this.get('node').name = this.get('name'); - if (Metamaps.Visualize) Metamaps.Visualize.mGraph.plot(); - }; - this.on('change:name', updateName, this); }, authorizeToEdit: function (mapper) { if (mapper && (this.get('permission') === "commons" || this.get('user_id') === mapper.get('id'))) return true; @@ -183,6 +227,41 @@ Metamaps.Backbone.init = function () { return node; }, + savedEvent: function() { + Metamaps.Realtime.sendTopicChange(this); + }, + updateViews: function() { + var onPageWithTopicCard = Metamaps.Active.Map || Metamaps.Active.Topic; + var node = this.get('node'); + // update topic card, if this topic is the one open there + if (onPageWithTopicCard && this == Metamaps.TopicCard.openTopicCard) { + Metamaps.TopicCard.showCard(node); + } + + // update the node on the map + if (onPageWithTopicCard && node) { + node.name = this.get('name'); + Metamaps.Visualize.mGraph.plot(); + } + }, + updateCardView: function() { + var onPageWithTopicCard = Metamaps.Active.Map || Metamaps.Active.Topic; + var node = this.get('node'); + // update topic card, if this topic is the one open there + if (onPageWithTopicCard && this == Metamaps.TopicCard.openTopicCard) { + Metamaps.TopicCard.showCard(node); + } + }, + updateNodeView: function() { + var onPageWithTopicCard = Metamaps.Active.Map || Metamaps.Active.Topic; + var node = this.get('node'); + + // update the node on the map + if (onPageWithTopicCard && node) { + node.name = this.get('name'); + Metamaps.Visualize.mGraph.plot(); + } + } }); self.TopicCollection = Backbone.Collection.extend({ @@ -196,6 +275,36 @@ Metamaps.Backbone.init = function () { toJSON: function (options) { return _.omit(this.attributes, this.blacklist); }, + save: function (key, val, options) { + + var attrs; + + // Handle both `"key", value` and `{key: value}` -style arguments. + if (key == null || typeof key === 'object') { + attrs = key; + options = val; + } else { + (attrs = {})[key] = val; + } + + var newOptions = options || {}; + var s = newOptions.success; + + var permBefore = this.get('permission'); + + newOptions.success = function (model, response, opt) { + if (s) s(model, response, opt); + model.trigger('saved'); + + if (permBefore === 'private' && model.get('permission') !== 'private') { + model.trigger('noLongerPrivate'); + } + else if (permBefore !== 'private' && model.get('permission') === 'private') { + model.trigger('nowPrivate'); + } + }; + return Backbone.Model.prototype.save.call(this, attrs, newOptions); + }, initialize: function () { if (this.isNew()) { this.set({ @@ -204,6 +313,24 @@ Metamaps.Backbone.init = function () { "category": "from-to" }); } + + this.on('changeByOther', this.updateCardView); + this.on('change', this.updateEdgeView); + this.on('saved', this.savedEvent); + this.on('noLongerPrivate', function(){ + var newSynapseData = { + mappingid: this.getMapping().id, + synapseid: this.id + }; + + $(document).trigger(Metamaps.JIT.events.newSynapse, [newSynapseData]); + }); + this.on('nowPrivate', function(){ + $(document).trigger(Metamaps.JIT.events.removeSynapse, [{ + synapseid: this.id + }]); + }); + this.on('change:desc', Metamaps.Filter.checkSynapses, this); }, prepareLiForFilter: function () { @@ -277,6 +404,31 @@ Metamaps.Backbone.init = function () { return edge; }, + savedEvent: function() { + Metamaps.Realtime.sendSynapseChange(this); + }, + updateViews: function() { + this.updateCardView(); + this.updateEdgeView(); + }, + updateCardView: function() { + var onPageWithSynapseCard = Metamaps.Active.Map || Metamaps.Active.Topic; + var edge = this.get('edge'); + + // update synapse card, if this synapse is the one open there + if (onPageWithSynapseCard && edge == Metamaps.SynapseCard.openSynapseCard) { + Metamaps.SynapseCard.showCard(edge); + } + }, + updateEdgeView: function() { + var onPageWithSynapseCard = Metamaps.Active.Map || Metamaps.Active.Topic; + var edge = this.get('edge'); + + // update the edge on the map + if (onPageWithSynapseCard && edge) { + Metamaps.Visualize.mGraph.plot(); + } + } }); self.SynapseCollection = Backbone.Collection.extend({ @@ -850,7 +1002,6 @@ Metamaps.TopicCard = { $('.metacodeSelect li li').click(metacodeLiClick); var bipName = $(showCard).find('.best_in_place_name'); - bipName.best_in_place(); bipName.bind("best_in_place:activate", function () { var $el = bipName.find('textarea'); var el = $el[0]; @@ -872,12 +1023,14 @@ Metamaps.TopicCard = { 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(); topic.set("desc", desc); + topic.trigger('saved'); }); } @@ -1108,6 +1261,7 @@ Metamaps.SynapseCard = { } else { synapse.set("desc", desc); } + synapse.trigger('saved'); Metamaps.Control.selectEdge(synapse.get('edge')); Metamaps.Visualize.mGraph.plot(); }); @@ -1333,7 +1487,7 @@ Metamaps.Visualize = { if (self.type == "RGraph") { self.mGraph.graph.eachNode(function (n) { topic = Metamaps.Topics.get(n.id); - topic.set('node', n); + topic.set({ node: n }, { silent: true }); topic.updateNode(); n.eachAdjacency(function (edge) { @@ -1343,7 +1497,7 @@ Metamaps.Visualize = { l = edge.getData('synapseIDs').length; for (i = 0; i < l; i++) { synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]); - synapse.set('edge', edge); + synapse.set({ edge: edge }, { silent: true }); synapse.updateEdge(); } } @@ -1358,7 +1512,7 @@ Metamaps.Visualize = { self.mGraph.graph.eachNode(function (n) { topic = Metamaps.Topics.get(n.id); - topic.set('node', n); + topic.set({ node: n }, { silent: true }); topic.updateNode(); mapping = topic.getMapping(); @@ -1369,7 +1523,7 @@ Metamaps.Visualize = { l = edge.getData('synapseIDs').length; for (i = 0; i < l; i++) { synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]); - synapse.set('edge', edge); + synapse.set({ edge: edge }, { silent: true }); synapse.updateEdge(); } } @@ -1510,18 +1664,26 @@ Metamaps.Util = { return Math.sqrt(Math.pow((p2.x - p1.x), 2) + Math.pow((p2.y - p1.y), 2)); }, coordsToPixels: function (coords) { - 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; + 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 canvas = Metamaps.Visualize.mGraph.canvas, @@ -1597,10 +1759,10 @@ Metamaps.Realtime = { init: function () { var self = Metamaps.Realtime; - var turnOn = function () { - self.turnOn(true); - } - $(".rtOn").click(turnOn); + var reenableRealtime = function () { + self.reenableRealtime(); + }; + $(".rtOn").click(reenableRealtime); $(".rtOff").click(self.turnOff); $('.sidebarCollaborateIcon').click(self.toggleBox); @@ -1612,7 +1774,9 @@ Metamaps.Realtime = { var railsEnv = $('body').data('env'); var whichToConnect = railsEnv === 'development' ? self.stringForLocalhost : self.stringForHeroku; self.socket = io.connect(whichToConnect); - self.startActiveMap(); + self.socket.on('connect', function () { + self.startActiveMap(); + }); }, toggleBox: function (event) { var self = Metamaps.Realtime; @@ -1650,31 +1814,34 @@ Metamaps.Realtime = { startActiveMap: function () { var self = Metamaps.Realtime; - var mapperm = Metamaps.Active.Map && Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); + if (Metamaps.Active.Map) { + var commonsMap = Metamaps.Active.Map.get('permission') === 'commons'; - var start = function() { - if (mapperm) { + if (commonsMap) { self.turnOn(); self.setupSocket(); } } - - if (!self.socket.connected) { - self.socket.socket.connect(); - } - self.socket.on('connect', function () { - start(); - }); }, endActiveMap: function () { var self = Metamaps.Realtime; $(document).off(Metamaps.JIT.events.mouseMove); - self.socket.disconnect(); self.socket.removeAllListeners(); + self.socket.emit('endMapperNotify'); $(".collabCompass").remove(); self.status = false; }, + 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; @@ -1741,6 +1908,14 @@ Metamaps.Realtime = { // 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); + socket.on('mapChangeFromServer', self.mapChange); // local event listeners that trigger events var sendCoords = function (event, coords) { @@ -1773,6 +1948,11 @@ Metamaps.Realtime = { }; $(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); }; @@ -1783,6 +1963,11 @@ Metamaps.Realtime = { }; $(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); }; @@ -1849,7 +2034,7 @@ Metamaps.Realtime = { $('.realtimeMapperList ul').append(mapperListItem); // create a div for the collaborators compass - self.createCompass(data.username, data.userid, data.userimage, self.mappersOnMap[data.userid].color); + self.createCompass(data.username, data.userid, data.userimage, self.mappersOnMap[data.userid].color, !self.status); } }, newPeerOnMap: function (data) { @@ -1883,7 +2068,7 @@ Metamaps.Realtime = { $('.realtimeMapperList ul').append(mapperListItem); // create a div for the collaborators compass - self.createCompass(data.username, data.userid, data.userimage, self.mappersOnMap[data.userid].color); + self.createCompass(data.username, data.userid, data.userimage, self.mappersOnMap[data.userid].color, !self.status); Metamaps.GlobalUI.notifyUser(data.username + ' just joined the map'); @@ -1899,7 +2084,7 @@ Metamaps.Realtime = { socket.emit('updateNewMapperList', update); } }, - createCompass: function(name, id, image, color) { + createCompass: function(name, id, image, color, hide) { var str = '

'+name+'

'; str += '
'; $('#compass' + id).remove(); @@ -1907,6 +2092,9 @@ Metamaps.Realtime = { id: 'compass' + id, class: 'collabCompass' }).html(str).appendTo('#wrapper'); + if (hide) { + $('#compass' + id).hide(); + } $('#compass' + id + ' img').css({ 'border': '2px solid ' + color }); @@ -2071,6 +2259,75 @@ Metamaps.Realtime = { 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.Topics.get(data.topicId); + if (map) { + var node = topic.get('node'); + topic.fetch({ + success: function (model) { + // must be set using silent:true otherwise + // will trigger a change event and an infinite + // loop with other clients of change events + model.set({ node: node }); + } + }); + }*/ + }, // newTopic sendNewTopic: function (data) { var self = Metamaps.Realtime; @@ -2085,6 +2342,11 @@ Metamaps.Realtime = { newTopic: function (data) { var topic, mapping, mapper, mapperCallback, cancel; + var self = Metamaps.Realtime; + var socket = self.socket; + + if (!self.status) return; + function test() { if (topic && mapping && mapper) { Metamaps.Topic.renderTopic(mapping, topic, false, false); @@ -2126,24 +2388,45 @@ Metamaps.Realtime = { test(); }, // 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 && self.status) { + 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.topicid); + 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 && self.status) { + if (Metamaps.Active.Map) { data.mapperid = Metamaps.Active.Mapper.id; data.mapid = Metamaps.Active.Map.id; socket.emit('newSynapse', data); @@ -2152,6 +2435,11 @@ Metamaps.Realtime = { newSynapse: function (data) { var topic1, topic2, node1, node2, synapse, mapping, cancel; + var self = Metamaps.Realtime; + var socket = self.socket; + + if (!self.status) return; + function test() { if (synapse && mapping && mapper) { topic1 = synapse.getTopic1(); @@ -2196,18 +2484,49 @@ Metamaps.Realtime = { }); test(); }, + // 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 && self.status) { + 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.synapseid); + 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 @@ -2267,8 +2586,14 @@ Metamaps.Control = { }, deleteNode: function (nodeid) { // refers to deleting topics permanently var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid); - Metamaps.Control.deselectNode(node); - Metamaps.Topics.get(nodeid).destroy(); + var topic = node.getData('topic'); + var topicid = topic.id; + var mapping = node.getData('mapping'); + topic.destroy(); + Metamaps.Mappings.remove(mapping); + $(document).trigger(Metamaps.JIT.events.deleteTopic, [{ + topicid: topicid + }]); Metamaps.Control.hideNode(nodeid); }, removeSelectedNodes: function () { // refers to removing topics permanently from a map @@ -2289,8 +2614,14 @@ Metamaps.Control = { var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid); if (mapperm) { - Metamaps.Control.deselectNode(node); - node.getData('mapping').destroy(); + var topic = node.getData('topic'); + var topicid = topic.id; + var mapping = node.getData('mapping'); + mapping.destroy(); + Metamaps.Topics.remove(topic); + $(document).trigger(Metamaps.JIT.events.removeTopic, [{ + topicid: topicid + }]); Metamaps.Control.hideNode(nodeid); } }, @@ -2306,9 +2637,10 @@ Metamaps.Control = { }, hideNode: function (nodeid) { var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid); + var graph = Metamaps.Visualize.mGraph; if (nodeid == Metamaps.Visualize.mGraph.root) { // && Metamaps.Visualize.type === "RGraph" - alert("You can't hide this topic, it is the root of your graph."); - return; + var newroot = _.find(graph.graph.nodes, function(n){ return n.id !== nodeid; }); + graph.root = newroot ? newroot.id : null; } Metamaps.Control.deselectNode(node); @@ -2383,8 +2715,6 @@ Metamaps.Control = { }, deleteEdge: function (edge) { - // TODO make it so that you select which one, of multiple possible synapses you want to delete - if (edge.getData("synapses").length - 1 === 0) { Metamaps.Control.hideEdge(edge); } @@ -2393,6 +2723,7 @@ Metamaps.Control = { var synapse = edge.getData("synapses")[index]; var mapping = edge.getData("mappings")[index]; + var synapseid = synapse.id; synapse.destroy(); // the server will destroy the mapping, we just need to remove it here @@ -2402,6 +2733,9 @@ Metamaps.Control = { if (edge.getData("displayIndex")) { delete edge.data.$displayIndex; } + $(document).trigger(Metamaps.JIT.events.deleteSynapse, [{ + synapseid: synapseid + }]); }, removeSelectedEdges: function () { var l = Metamaps.Selected.Edges.length, @@ -2418,8 +2752,6 @@ Metamaps.Control = { }, removeEdge: function (edge) { - // TODO make it so that you select which one, of multiple possible synapses you want - if (edge.getData("mappings").length - 1 === 0) { Metamaps.Control.hideEdge(edge); } @@ -2428,6 +2760,7 @@ Metamaps.Control = { var synapse = edge.getData("synapses")[index]; var mapping = edge.getData("mappings")[index]; + var synapseid = synapse.id; mapping.destroy(); Metamaps.Synapses.remove(synapse); @@ -2437,6 +2770,9 @@ Metamaps.Control = { if (edge.getData("displayIndex")) { delete edge.data.$displayIndex; } + $(document).trigger(Metamaps.JIT.events.removeSynapse, [{ + synapseid: synapseid + }]); }, hideSelectedEdges: function () { var edge, @@ -3857,6 +4193,7 @@ Metamaps.Map.InfoBox = { var name = $(this).html(); $('.mapName').html(name); Metamaps.Active.Map.set('name', name); + Metamaps.Active.Map.trigger('saved'); }); $('.yourMap .mapPermission').unbind().click(self.onPermissionClick); diff --git a/app/assets/stylesheets/uservoice.css b/app/assets/stylesheets/uservoice.css index 7b2bca26..0f878309 100644 --- a/app/assets/stylesheets/uservoice.css +++ b/app/assets/stylesheets/uservoice.css @@ -3,14 +3,13 @@ div.uv-icon.uv-bottom-left { background-image:url(feedback_sprite.png); - background-color:#222222; color:#FFFFFF; cursor:pointer; height:108px; left:0; margin-left:0px; text-indent:-100000px; - top:25%; + top:65%; width:25px; z-index:100000; opacity: 1; diff --git a/app/controllers/maps_controller.rb b/app/controllers/maps_controller.rb index 72d81656..c3159b63 100644 --- a/app/controllers/maps_controller.rb +++ b/app/controllers/maps_controller.rb @@ -75,7 +75,7 @@ class MapsController < ApplicationController @map = Map.find(params[:id]).authorize_to_show(@current) if not @map - redirect_to root_url and return + redirect_to root_url, notice: "Access denied. That map is private." and return end respond_to do |format| @@ -98,7 +98,7 @@ class MapsController < ApplicationController @map = Map.find(params[:id]).authorize_to_show(@current) if not @map - redirect_to root_url and return + redirect_to root_url, notice: "Access denied. That map is private." and return end @allmappers = @map.contributors diff --git a/realtime/realtime-server.js b/realtime/realtime-server.js index 28db52cf..f62ed3d4 100644 --- a/realtime/realtime-server.js +++ b/realtime/realtime-server.js @@ -66,8 +66,7 @@ function start() { socket.broadcast.emit('maps-' + data.mapid + '-newmapper', newUser); }); - // this will ping everyone on a map that there's a person just left the map - socket.on('disconnect', function () { + var end = function () { var socketUserName, socketUserID; socket.get('userid', function (err, id) { socketUserID = id; @@ -82,7 +81,10 @@ function start() { socket.get('mapid', function (err, mapid) { socket.broadcast.emit('maps-' + mapid + '-lostmapper', data); }); - }); + }; + // this will ping everyone on a map that there's a person just left the map + socket.on('disconnect', end); + socket.on('endMapperNotify', end); // this will ping everyone on a map that someone just turned on realtime socket.on('notifyStartRealtime', function (data) { @@ -127,6 +129,22 @@ function start() { socket.broadcast.emit('maps-' + mapId + '-newTopic', data); }); + socket.on('topicChangeFromClient', function (data) { + socket.broadcast.emit('topicChangeFromServer', data); + }); + + socket.on('synapseChangeFromClient', function (data) { + socket.broadcast.emit('synapseChangeFromServer', data); + }); + + socket.on('mapChangeFromClient', function (data) { + socket.broadcast.emit('mapChangeFromServer', data); + }); + + socket.on('deleteTopicFromClient', function (data) { + socket.broadcast.emit('deleteTopicFromServer', data); + }); + socket.on('removeTopic', function (data) { var mapId = data.mapid; delete data.mapid; @@ -141,6 +159,10 @@ function start() { socket.broadcast.emit('maps-' + mapId + '-newSynapse', data); }); + socket.on('deleteSynapseFromClient', function (data) { + socket.broadcast.emit('deleteSynapseFromServer', data); + }); + socket.on('removeSynapse', function (data) { var mapId = data.mapid; delete data.mapid;