diff --git a/MacInstallation.md b/MacInstallation.md index c2b9d80d..2d36494e 100644 --- a/MacInstallation.md +++ b/MacInstallation.md @@ -4,9 +4,9 @@ install homebrew \curl -sSL https://get.rvm.io | bash -s stable --rails - rvm install 1.9.3 --with-gcc=clang + rvm install 2.1.3 --with-gcc=clang - rvm use 1.9.3 + rvm use 2.1.3 gem install lunchy diff --git a/app/assets/images/linkedmedia.png b/app/assets/images/linkedmedia.png deleted file mode 100755 index 2009b7a3..00000000 Binary files a/app/assets/images/linkedmedia.png and /dev/null differ diff --git a/app/assets/images/screenshot_sprite.png b/app/assets/images/screenshot_sprite.png new file mode 100644 index 00000000..d3c87c5a Binary files /dev/null and b/app/assets/images/screenshot_sprite.png differ diff --git a/app/assets/images/synapse32padded.png b/app/assets/images/synapse32padded.png new file mode 100644 index 00000000..09e52fc0 Binary files /dev/null and b/app/assets/images/synapse32padded.png differ diff --git a/app/assets/images/synapsestar.png b/app/assets/images/synapsestar.png deleted file mode 100755 index ba9a7dd3..00000000 Binary files a/app/assets/images/synapsestar.png and /dev/null differ diff --git a/app/assets/images/topic_description_signifier.png b/app/assets/images/topic_description_signifier.png new file mode 100644 index 00000000..956db0a3 Binary files /dev/null and b/app/assets/images/topic_description_signifier.png differ diff --git a/app/assets/images/topic_link_signifier.png b/app/assets/images/topic_link_signifier.png new file mode 100644 index 00000000..a8b8b8e5 Binary files /dev/null and b/app/assets/images/topic_link_signifier.png differ diff --git a/app/assets/javascripts/src/JIT.js b/app/assets/javascripts/src/JIT.js index 7483a544..498183ef 100644 --- a/app/assets/javascripts/src/JIT.js +++ b/app/assets/javascripts/src/JIT.js @@ -2477,7 +2477,6 @@ Extras.Classes.Navigation = new Class({ // START METAMAPS CODE jQuery(document).trigger(Metamaps.JIT.events.zoom, [e]); // END METAMAPS CODE - }, onMouseDown: function(e, win, eventInfo) { @@ -7097,7 +7096,7 @@ Graph.Plot = { ctx.restore(); } //END METAMAPS CODE - + aGraph.eachNode(function(node) { var nodeAlpha = node.getData('alpha'); node.eachAdjacency(function(adj) { @@ -7427,6 +7426,7 @@ Graph.Label.Native = new Class({ (end code) */ plotLabel: function(canvas, node, controller) { + var ctx = canvas.getCtx(); var pos = node.pos.getc(true); @@ -7493,7 +7493,7 @@ Graph.Label.Native = new Class({ // START METAMAPS CODE var index; for (index = 0; index < customLabel.length; ++index) { - ctx.fillText(customLabel[index], pos.x, pos.y + node.getData("height") + 8 + (25*index)); + ctx.fillText(customLabel[index], pos.x, pos.y + node.getData("height") + 9 + (25*index)); } // END METAMAPS CODE }, diff --git a/app/assets/javascripts/src/Metamaps.Backbone.js b/app/assets/javascripts/src/Metamaps.Backbone.js index f8c89493..7ed0adfe 100644 --- a/app/assets/javascripts/src/Metamaps.Backbone.js +++ b/app/assets/javascripts/src/Metamaps.Backbone.js @@ -5,6 +5,34 @@ Metamaps.Backbone.Map = Backbone.Model.extend({ 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; + + newOptions.success = function (model, response, opt) { + if (s) s(model, response, opt); + model.trigger('saved'); + }; + return Backbone.Model.prototype.save.call(this, attrs, newOptions); + }, + initialize: function () { + this.on('changeByOther', this.updateView); + this.on('saved', this.savedEvent); + }, + savedEvent: function() { + Metamaps.Realtime.sendMapChange(this); + }, authorizeToEdit: function (mapper) { if (mapper && (this.get('permission') === "commons" || this.get('user_id') === mapper.get('id'))) return true; else return false; @@ -24,11 +52,12 @@ Metamaps.Backbone.Map = Backbone.Model.extend({ that.set('topics', new bb.TopicCollection(data.topics)); that.set('synapses', new bb.SynapseCollection(data.synapses)); that.set('mappings', new bb.MappingCollection(data.mappings)); - } + }; - $.ajax({ + var e = $.ajax({ url: "/maps/" + this.id + "/contains.json", success: start, + error: errorFunc, async: false }); }, @@ -75,6 +104,25 @@ Metamaps.Backbone.Map = Backbone.Model.extend({ screenshot: '' }; return obj; + }, + updateView: function() { + var map = Metamaps.Active.Map; + var isActiveMap = this.id === map.id; + var authorized = map && map.authorizeToEdit(Metamaps.Active.Mapper) ? 'canEditMap' : ''; + var commonsMap = map && map.get('permission') === 'commons' ? 'commonsMap' : ''; + if (isActiveMap) { + Metamaps.Map.InfoBox.updateNameDescPerm(this.get('name'), this.get('desc'), this.get('permission')); + this.updateMapWrapper(); + } + }, + updateMapWrapper: function() { + var map = Metamaps.Active.Map; + var isActiveMap = this.id === map.id; + var authorized = map && map.authorizeToEdit(Metamaps.Active.Mapper) ? 'canEditMap' : ''; + var commonsMap = map && map.get('permission') === 'commons' ? 'commonsMap' : ''; + if (isActiveMap) { + $('.wrapper').removeClass('canEditMap commonsMap').addClass(authorized + ' ' + commonsMap); + } } }); Metamaps.Backbone.MapsCollection = Backbone.Collection.extend({ diff --git a/app/assets/javascripts/src/Metamaps.GlobalUI.js b/app/assets/javascripts/src/Metamaps.GlobalUI.js index fd393c71..0fcc6971 100644 --- a/app/assets/javascripts/src/Metamaps.GlobalUI.js +++ b/app/assets/javascripts/src/Metamaps.GlobalUI.js @@ -223,7 +223,7 @@ Metamaps.GlobalUI.CreateMap = { $(this).parents('.new_map').find('.permText').html(permText); }, submit: function (event) { - event.preventDefault(); + if (event) event.preventDefault(); var self = Metamaps.GlobalUI.CreateMap; @@ -233,23 +233,15 @@ Metamaps.GlobalUI.CreateMap = { } var formId = Metamaps.GlobalUI.lightbox === 'forkmap' ? '#fork_map' : '#new_map'; - var form = $(formId) + var $form = $(formId); - self.newMap.set('name', form.find('#map_name').val()); - self.newMap.set('desc', form.find('#map_desc').val()); + self.newMap.set('name', $form.find('#map_name').val()); + self.newMap.set('desc', $form.find('#map_desc').val()); - // TODO validate map attributes if (self.newMap.get('name').length===0){ - console.log('Empty map name.'); - Metamaps.GlobalUI.notifyUser('map name is mandatory.'); - return; - - } else if (self.newMap.get('name').length>140){ - console.log('map name cannot exceed 140 characteres.'); - Metamaps.GlobalUI.notifyUser('map name cannot exceed 140 characteres.'); + self.throwMapNameError(); return; } - //console.log('self.newMap.get("name").length='+self.newMap.get("name").length.toString()); self.newMap.save(null, { success: self.success @@ -259,6 +251,21 @@ Metamaps.GlobalUI.CreateMap = { Metamaps.GlobalUI.closeLightbox(); Metamaps.GlobalUI.notifyUser('Working...'); }, + throwMapNameError: function () { + var self = Metamaps.GlobalUI.CreateMap; + + var formId = Metamaps.GlobalUI.lightbox === 'forkmap' ? '#fork_map' : '#new_map'; + var $form = $(formId); + + var message = $("
Please enter a map name...
"); + + $form.find('#map_name').after(message); + setTimeout(function(){ + message.fadeOut('fast', function(){ + message.remove(); + }); + }, 5000); + }, success: function (model) { var self = Metamaps.GlobalUI.CreateMap; diff --git a/app/assets/javascripts/src/Metamaps.JIT.js b/app/assets/javascripts/src/Metamaps.JIT.js index 4ecda48b..88436a80 100644 --- a/app/assets/javascripts/src/Metamaps.JIT.js +++ b/app/assets/javascripts/src/Metamaps.JIT.js @@ -1,6 +1,5 @@ Metamaps.JIT = { events: { - mouseMove: 'Metamaps:JIT:events:mouseMove', topicDrag: 'Metamaps:JIT:events:topicDrag', newTopic: 'Metamaps:JIT:events:newTopic', deleteTopic: 'Metamaps:JIT:events:deleteTopic', @@ -21,13 +20,26 @@ Metamaps.JIT = { $(".zoomIn").click(self.zoomIn); $(".zoomOut").click(self.zoomOut); - $(".zoomExtents").click(self.zoomExtents); + + var zoomExtents = function (event) { + self.zoomExtents(event, Metamaps.Visualize.mGraph.canvas); + }; + $(".zoomExtents").click(zoomExtents); + + $(".takeScreenshot").click(Metamaps.Map.exportImage); + + self.topicDescImage = new Image(); + self.topicDescImage.src = '/assets/topic_description_signifier.png'; + + self.topicLinkImage = new Image(); + self.topicLinkImage.src = '/assets/topic_link_signifier.png'; }, /** * convert our topic JSON into something JIT can use */ - prepareVizData: function () { - var self = Metamaps.JIT; + convertModelsToJIT: function(topics, synapses) { + var jitReady = []; + var synapsesToRemove = []; var topic; var mapping; @@ -37,18 +49,14 @@ Metamaps.JIT = { var edge; var edges = []; - // reset/empty vizData - self.vizData = []; - Metamaps.Visualize.loadLater = false; - - Metamaps.Topics.each(function (t) { + topics.each(function (t) { node = t.createNode(); nodes[node.id] = node; }); - Metamaps.Synapses.each(function (s) { + synapses.each(function (s) { edge = s.createEdge(); - if(Metamaps.Topics.get(s.get('node1_id')) === undefined || Metamaps.Topics.get(s.get('node2_id')) === undefined) { + if (topics.get(s.get('node1_id')) === undefined || topics.get(s.get('node2_id')) === undefined) { // this means it's an invalid synapse synapsesToRemove.push(s); } @@ -78,17 +86,31 @@ Metamaps.JIT = { } }); + _.each(nodes, function (node) { + jitReady.push(node); + }); + + return [jitReady, synapsesToRemove]; + }, + prepareVizData: function () { + var self = Metamaps.JIT; + var mapping; + + // reset/empty vizData + self.vizData = []; + Metamaps.Visualize.loadLater = false; + + var results = self.convertModelsToJIT(Metamaps.Topics, Metamaps.Synapses); + + self.vizData = results[0]; + // clean up the synapses array in case of any faulty data - _.each(synapsesToRemove, function (synapse) { + _.each(results[1], function (synapse) { mapping = synapse.getMapping(); Metamaps.Synapses.remove(synapse); Metamaps.Mappings.remove(mapping); }); - _.each(nodes, function (node) { - self.vizData.push(node); - }); - if (self.vizData.length == 0) { Metamaps.Visualize.loadLater = true; } @@ -118,14 +140,45 @@ Metamaps.JIT = { var directionCat = synapse.get("category"); //label placement on edges - Metamaps.JIT.renderEdgeArrows($jit.Graph.Plot.edgeHelper, adj, synapse); + if (canvas.denySelected) { + var color = Metamaps.Settings.colors.synapses.normal; + canvas.getCtx().fillStyle = canvas.getCtx().strokeStyle = color; + } + Metamaps.JIT.renderEdgeArrows($jit.Graph.Plot.edgeHelper, adj, synapse, canvas); //check for edge label in data var desc = synapse.get("desc"); var showDesc = adj.getData("showDesc"); - if (desc != "" && showDesc) { + var drawSynapseCount = function (context, x, y, count) { + /* + circle size: 16x16px + positioning: overlay and center on top right corner of synapse label - 8px left and 8px down + color: #dab539 + border color: #424242 + border size: 1.5px + font: DIN medium + font-size: 14pt + font-color: #424242 + */ + context.beginPath(); + context.arc(x, y, 8, 0, 2 * Math.PI, false); + context.fillStyle = '#DAB539'; + context.strokeStyle = '#424242'; + context.lineWidth = 1.5; + context.closePath(); + context.fill(); + context.stroke(); + + // add the synapse count + context.fillStyle = '#424242'; + context.textAlign = 'center'; + context.font = '14px din-medium'; + context.fillText(count, x, y - 6); + }; + + if (!canvas.denySelected && desc != "" && showDesc) { // '&' to '&' desc = Metamaps.Util.decodeEntities(desc); @@ -140,11 +193,12 @@ Metamaps.JIT = { for (index = 0; index < arrayOfLabelLines.length; ++index) { lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) } - var width = Math.max.apply(null, lineWidths) + 8; + var width = Math.max.apply(null, lineWidths) + 16; var height = (16 * arrayOfLabelLines.length) + 8; var x = (pos.x + posChild.x - width) / 2; var y = ((pos.y + posChild.y) / 2) - height / 2; + var radius = 5; //render background @@ -161,70 +215,33 @@ Metamaps.JIT = { ctx.closePath(); ctx.fill(); + // get number of synapses + var synapseNum = adj.getData("synapses").length; + //render text - ctx.fillStyle = '#222222'; + ctx.fillStyle = '#424242'; ctx.textAlign = 'center'; for (index = 0; index < arrayOfLabelLines.length; ++index) { - ctx.fillText(arrayOfLabelLines[index], x + (width / 2), y + 5 + (16 * index)); + ctx.fillText(arrayOfLabelLines[index], x + (width / 2), y + 7 + (16 * index)); + } + + if (synapseNum > 1) { + drawSynapseCount(ctx, x + width, y, synapseNum); } } + else if (!canvas.denySelected && showDesc) { + // get number of synapses + var synapseNum = adj.getData("synapses").length; + + if (synapseNum > 1) { + var ctx = canvas.getCtx(); + var x = (pos.x + posChild.x) / 2; + var y = (pos.y + posChild.y) / 2; + drawSynapseCount(ctx, x, y, synapseNum); + } + } + }, // edgeRender - edgeRenderEmbed: function (adj, canvas) { - //get nodes cartesian coordinates - var pos = adj.nodeFrom.pos.getc(true); - var posChild = adj.nodeTo.pos.getc(true); - - var directionCat = adj.getData("category"); - //label placement on edges - Metamaps.JIT.renderEdgeArrows(this.edgeHelper, adj); - - //check for edge label in data - var desc = adj.getData("desc"); - var showDesc = adj.getData("showDesc"); - if (desc != "" && showDesc) { - // '&' to '&' - desc = Metamaps.Util.decodeEntities(desc); - - //now adjust the label placement - var ctx = canvas.getCtx(); - ctx.font = 'bold 14px arial'; - ctx.fillStyle = '#FFF'; - ctx.textBaseline = 'hanging'; - - var arrayOfLabelLines = Metamaps.Util.splitLine(desc, 30).split('\n'); - var index, lineWidths = []; - for (index = 0; index < arrayOfLabelLines.length; ++index) { - lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) - } - var width = Math.max.apply(null, lineWidths) + 8; - var height = (16 * arrayOfLabelLines.length) + 8; - - var x = (pos.x + posChild.x - width) / 2; - var y = ((pos.y + posChild.y) / 2) - height / 2; - var radius = 5; - - //render background - ctx.beginPath(); - ctx.moveTo(x + radius, y); - ctx.lineTo(x + width - radius, y); - ctx.quadraticCurveTo(x + width, y, x + width, y + radius); - ctx.lineTo(x + width, y + height - radius); - ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); - ctx.lineTo(x + radius, y + height); - ctx.quadraticCurveTo(x, y + height, x, y + height - radius); - ctx.lineTo(x, y + radius); - ctx.quadraticCurveTo(x, y, x + radius, y); - ctx.closePath(); - ctx.fill(); - - //render text - ctx.fillStyle = '#222222'; - ctx.textAlign = 'center'; - for (index = 0; index < arrayOfLabelLines.length; ++index) { - ctx.fillText(arrayOfLabelLines[index], x + (width / 2), y + 5 + (16 * index)); - } - } - }, // edgeRenderEmbed ForceDirected: { animateSavedLayout: { modes: ['linear'], @@ -427,7 +444,7 @@ Metamaps.JIT = { ctx = canvas.getCtx(); // if the topic is selected draw a circle around it - if (node.selected) { + if (!canvas.denySelected && node.selected) { ctx.beginPath(); ctx.arc(pos.x, pos.y, dim + 3, 0, 2 * Math.PI, false); ctx.strokeStyle = Metamaps.Settings.colors.topics.selected; @@ -447,6 +464,26 @@ Metamaps.JIT = { } else { ctx.drawImage(metacode.get('image'), pos.x - dim, pos.y - dim, dim * 2, dim * 2); } + + // if the topic has a link, draw a small image to indicate that + var hasLink = topic && topic.get('link') !== "" && topic.get('link') !== null; + var linkImage = Metamaps.JIT.topicLinkImage; + var linkImageLoaded = linkImage.complete || + (typeof linkImage.naturalWidth !== "undefined" && + linkImage.naturalWidth !== 0) + if (hasLink && linkImageLoaded) { + ctx.drawImage(linkImage, pos.x - dim - 8, pos.y - dim - 8, 16, 16); + } + + // if the topic has a desc, draw a small image to indicate that + var hasDesc = topic && topic.get('desc') !== "" && topic.get('desc') !== null; + var descImage = Metamaps.JIT.topicDescImage; + var descImageLoaded = descImage.complete || + (typeof descImage.naturalWidth !== "undefined" && + descImage.naturalWidth !== 0) + if (hasDesc && descImageLoaded) { + ctx.drawImage(descImage, pos.x + dim - 8, pos.y - dim - 8, 16, 16); + } }, 'contains': function (node, pos) { var npos = node.pos.getc(true), @@ -486,7 +523,7 @@ Metamaps.JIT = { if (-1 < pos.x && pos.x < 1) pos.x = 0; if (-1 < pos.y && pos.y < 1) pos.y = 0; - return $jit.Graph.Plot.edgeHelper.line.contains(from, to, pos, adj.Edge.epsilon); + return $jit.Graph.Plot.edgeHelper.line.contains(from, to, pos, adj.Edge.epsilon + 5); } } } @@ -677,15 +714,18 @@ Metamaps.JIT = { if (!node && !edge) { $('canvas').css('cursor', 'default'); } - - var pos = eventInfo.getPos(); - $(document).trigger(Metamaps.JIT.events.mouseMove, [pos]); }, // onMouseMoveHandler enterKeyHandler: function () { + var creatingMap = Metamaps.GlobalUI.lightbox; + if (creatingMap === "newmap" || creatingMap === "forkmap") { + Metamaps.GlobalUI.CreateMap.submit(); + } // this is to submit new topic creation - if (Metamaps.Create.newTopic.beingCreated) { + else if (Metamaps.Create.newTopic.beingCreated) { Metamaps.Topic.createTopicLocally(); - } else if (Metamaps.Create.newSynapse.beingCreated) { + } + // to submit new synapse creation + else if (Metamaps.Create.newSynapse.beingCreated) { Metamaps.Synapse.createSynapseLocally(); } }, //enterKeyHandler @@ -751,6 +791,8 @@ Metamaps.JIT = { var positionsToSend = {}; var topic; + var authorized = Metamaps.Active.Map && Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); + if (node && !node.nodeFrom) { var pos = eventInfo.getPos(); // if it's a left click, or a touch, move the node @@ -773,7 +815,6 @@ Metamaps.JIT = { // maps positionsToSend[topic.id] = pos; $(document).trigger(Metamaps.JIT.events.topicDrag, [positionsToSend]); - $(document).trigger(Metamaps.JIT.events.mouseMove, [pos]); } } else { var len = Metamaps.Selected.Nodes.length; @@ -805,7 +846,6 @@ Metamaps.JIT = { if (Metamaps.Active.Map) { $(document).trigger(Metamaps.JIT.events.topicDrag, [positionsToSend]); - $(document).trigger(Metamaps.JIT.events.mouseMove, [pos]); } } //if @@ -815,7 +855,7 @@ Metamaps.JIT = { Metamaps.Visualize.mGraph.plot(); } // if it's a right click or holding down alt, start synapse creation ->third option is for firefox - else if ((e.button == 2 || (e.button == 0 && e.altKey) || e.buttons == 2) && Metamaps.Active.Mapper) { + else if ((e.button == 2 || (e.button == 0 && e.altKey) || e.buttons == 2) && authorized) { if (tempInit == false) { tempNode = node; tempInit = true; @@ -877,9 +917,14 @@ Metamaps.JIT = { x: pos.x, y: pos.y }; - $(document).trigger(Metamaps.JIT.events.mouseMove, [pos]); } } + else if ((e.button == 2 || (e.button == 0 && e.altKey) || e.buttons == 2) && Metamaps.Active.Topic) { + Metamaps.GlobalUI.notifyUser("Cannot create in Topic view."); + } + else if ((e.button == 2 || (e.button == 0 && e.altKey) || e.buttons == 2) && !authorized) { + Metamaps.GlobalUI.notifyUser("Cannot edit Public map."); + } } }, // onDragMoveTopicHandler onDragCancelHandler: function (node, eventInfo, e) { @@ -921,6 +966,9 @@ Metamaps.JIT = { // check whether to save mappings var checkWhetherToSave = function() { var map = Metamaps.Active.Map; + + if (!map) return false; + var mapper = Metamaps.Active.Mapper; // this case // covers when it is a public map owned by you @@ -961,7 +1009,17 @@ Metamaps.JIT = { var now = Date.now(); //not compatible with IE8 FYI Metamaps.Mouse.lastCanvasClick = now; + var authorized = Metamaps.Active.Map && Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); + if (now - storedTime < Metamaps.Mouse.DOUBLE_CLICK_TOLERANCE && !Metamaps.Mouse.didPan) { + if (Metamaps.Active.Map && !authorized) { + Metamaps.GlobalUI.notifyUser("Cannot edit Public map."); + return; + } + else if (Metamaps.Active.Topic) { + Metamaps.GlobalUI.notifyUser("Cannot create in Topic view."); + return; + } // DOUBLE CLICK //pop up node creation :) Metamaps.Create.newTopic.addSynapse = false; @@ -1263,12 +1321,18 @@ Metamaps.JIT = { // add the proper options to the menu var menustring = '