diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 6ed8278d..14f565fa 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -43,5 +43,7 @@ //= require ./src/Metamaps.Mobile //= require ./src/Metamaps.Admin //= require ./src/Metamaps.Import +//= require ./src/Metamaps.AutoLayout +//= require ./src/Metamaps.PasteInput //= require ./src/Metamaps.JIT //= require ./src/Metamaps.Debug diff --git a/app/assets/javascripts/src/Metamaps.AutoLayout.js b/app/assets/javascripts/src/Metamaps.AutoLayout.js new file mode 100644 index 00000000..51e105c2 --- /dev/null +++ b/app/assets/javascripts/src/Metamaps.AutoLayout.js @@ -0,0 +1,75 @@ +/* global Metamaps */ + +/* + * Metmaaps.AutoLayout.js + * + * Dependencies: none! + */ + +Metamaps.AutoLayout = { + nextX: 0, + nextY: 0, + sideLength: 1, + turnCount: 0, + nextXshift: 1, + nextYshift: 0, + timeToTurn: 0, + + getNextCoord: function () { + var self = Metamaps.AutoLayout + var nextX = self.nextX + var nextY = self.nextY + + var DISTANCE_BETWEEN = 120 + + self.nextX = self.nextX + DISTANCE_BETWEEN * self.nextXshift + self.nextY = self.nextY + DISTANCE_BETWEEN * self.nextYshift + + self.timeToTurn += 1 + // if true, it's time to turn + if (self.timeToTurn === self.sideLength) { + self.turnCount += 1 + // if true, it's time to increase side length + if (self.turnCount % 2 === 0) { + self.sideLength += 1 + } + self.timeToTurn = 0 + + // going right? turn down + if (self.nextXshift == 1 && self.nextYshift == 0) { + self.nextXshift = 0 + self.nextYshift = 1 + } + // going down? turn left + else if (self.nextXshift == 0 && self.nextYshift == 1) { + self.nextXshift = -1 + self.nextYshift = 0 + } + // going left? turn up + else if (self.nextXshift == -1 && self.nextYshift == 0) { + self.nextXshift = 0 + self.nextYshift = -1 + } + // going up? turn right + else if (self.nextXshift == 0 && self.nextYshift == -1) { + self.nextXshift = 1 + self.nextYshift = 0 + } + } + + return { + x: nextX, + y: nextY + } + }, + resetSpiral: function () { + var self = Metamaps.AutoLayout + self.nextX = 0 + self.nextY = 0 + self.nextXshift = 1 + self.nextYshift = 0 + self.sideLength = 1 + self.timeToTurn = 0 + self.turnCount = 0 + } +} diff --git a/app/assets/javascripts/src/Metamaps.Backbone.js b/app/assets/javascripts/src/Metamaps.Backbone.js index d05ffe3a..2c1f58af 100644 --- a/app/assets/javascripts/src/Metamaps.Backbone.js +++ b/app/assets/javascripts/src/Metamaps.Backbone.js @@ -63,7 +63,7 @@ Metamaps.Backbone.Map = Backbone.Model.extend({ authorizeToEdit: function (mapper) { if (mapper && ( this.get('permission') === 'commons' || - this.get('collaborator_ids').includes(mapper.get('id')) || + (this.get('collaborator_ids') || []).includes(mapper.get('id')) || this.get('user_id') === mapper.get('id'))) { return true } else { @@ -321,8 +321,8 @@ Metamaps.Backbone.init = function () { if (this.isNew()) { this.set({ 'user_id': Metamaps.Active.Mapper.id, - 'desc': '', - 'link': '', + 'desc': this.get('desc') || '', + 'link': this.get('link') || '', 'permission': Metamaps.Active.Map ? Metamaps.Active.Map.get('permission') : 'commons' }) } @@ -350,9 +350,9 @@ Metamaps.Backbone.init = function () { }, authorizeToEdit: function (mapper) { if (mapper && - (this.get('calculated_permission') === 'commons' || - this.get('collaborator_ids').includes(mapper.get('id')) || - this.get('user_id') === mapper.get('id'))) { + (this.get('user_id') === mapper.get('id') || + this.get('calculated_permission') === 'commons' || + this.get('collaborator_ids').includes(mapper.get('id')))) { return true } else { return false diff --git a/app/assets/javascripts/src/Metamaps.Import.js b/app/assets/javascripts/src/Metamaps.Import.js index d7771988..2dee51d0 100644 --- a/app/assets/javascripts/src/Metamaps.Import.js +++ b/app/assets/javascripts/src/Metamaps.Import.js @@ -6,7 +6,6 @@ * Dependencies: * - Metamaps.Active * - Metamaps.Backbone - * - Metamaps.Famous // TODO remove dependency * - Metamaps.Map * - Metamaps.Mappings * - Metamaps.Metacodes @@ -24,38 +23,30 @@ Metamaps.Import = { ], cidMappings: {}, // to be filled by import_id => cid mappings - init: function () { + handleTSV: function (text) { var self = Metamaps.Import + results = self.parseTabbedString(text) + self.handle(results) + }, - $('body').bind('paste', function (e) { - if (e.target.tagName === 'INPUT') return - if (e.target.tagName === 'TEXTAREA') return + handleJSON: function (text) { + var self = Metamaps.Import + results = JSON.parse(text) + self.handle(results) + }, - var text = e.originalEvent.clipboardData.getData('text/plain') + handle: function(results) { + var self = Metamaps.Import + var topics = results.topics + var synapses = results.synapses - var results - if (text.trimLeft()[0] === '{') { - try { - results = JSON.parse(text) - } catch (e) { - results = false - } - } else { - results = self.parseTabbedString(text) - } - if (results === false) return - - var topics = results.topics - var synapses = results.synapses - - if (topics.length > 0 || synapses.length > 0) { - if (window.confirm('Are you sure you want to create ' + topics.length + - ' new topics and ' + synapses.length + ' new synapses?')) { - self.importTopics(topics) - self.importSynapses(synapses) - } // if + if (topics.length > 0 || synapses.length > 0) { + if (window.confirm('Are you sure you want to create ' + topics.length + + ' new topics and ' + synapses.length + ' new synapses?')) { + self.importTopics(topics) + self.importSynapses(synapses) } // if - }) + } // if }, abort: function (message) { @@ -263,7 +254,7 @@ Metamaps.Import = { }, createTopicWithParameters: function (name, metacode_name, permission, desc, - link, xloc, yloc, import_id) { + link, xloc, yloc, import_id, opts) { var self = Metamaps.Import $(document).trigger(Metamaps.Map.events.editedByActiveMapper) var metacode = Metamaps.Metacodes.where({name: metacode_name})[0] || null @@ -272,15 +263,22 @@ Metamaps.Import = { console.warn("Couldn't find metacode " + metacode_name + ' so used Wildcard instead.') } + var topic_permission = permission || Metamaps.Active.Map.get('permission') + var defer_to_map_id = permission === topic_permission ? Metamaps.Active.Map.get('id') : null var topic = new Metamaps.Backbone.Topic({ name: name, metacode_id: metacode.id, - permission: permission || Metamaps.Active.Map.get('permission'), + permission: topic_permission, + defer_to_map_id: defer_to_map_id, desc: desc || "", - link: link + link: link || "", + calculated_permission: Metamaps.Active.Map.get('permission') }) Metamaps.Topics.add(topic) - self.cidMappings[import_id] = topic.cid + + if (import_id !== null && import_id !== undefined) { + self.cidMappings[import_id] = topic.cid + } var mapping = new Metamaps.Backbone.Mapping({ xloc: xloc, @@ -291,9 +289,11 @@ Metamaps.Import = { Metamaps.Mappings.add(mapping) // this function also includes the creation of the topic in the database - Metamaps.Topic.renderTopic(mapping, topic, true, true) + Metamaps.Topic.renderTopic(mapping, topic, true, true, { + success: opts.success + }) - Metamaps.Famous.viz.hideInstructions() + Metamaps.GlobalUI.hideDiv('#instructions') }, createSynapseWithParameters: function (desc, category, permission, diff --git a/app/assets/javascripts/src/Metamaps.Map.js b/app/assets/javascripts/src/Metamaps.Map.js index 1c3c638d..264e3c48 100644 --- a/app/assets/javascripts/src/Metamaps.Map.js +++ b/app/assets/javascripts/src/Metamaps.Map.js @@ -4,6 +4,7 @@ * Metamaps.Map.js.erb * * Dependencies: + * - Metamaps.AutoLayout * - Metamaps.Create * - Metamaps.Erb * - Metamaps.Filter @@ -34,13 +35,6 @@ Metamaps.Map = { events: { editedByActiveMapper: 'Metamaps:Map:events:editedByActiveMapper' }, - nextX: 0, - nextY: 0, - sideLength: 1, - turnCount: 0, - nextXshift: 1, - nextYshift: 0, - timeToTurn: 0, init: function () { var self = Metamaps.Map @@ -131,7 +125,7 @@ Metamaps.Map = { end: function () { if (Metamaps.Active.Map) { $('.wrapper').removeClass('canEditMap commonsMap') - Metamaps.Map.resetSpiral() + Metamaps.AutoLayout.resetSpiral() $('.rightclickmenu').remove() Metamaps.TopicCard.hideCard() @@ -242,68 +236,6 @@ Metamaps.Map = { Metamaps.Mappers.add(Metamaps.Active.Mapper) } }, - getNextCoord: function () { - var self = Metamaps.Map - var nextX = self.nextX - var nextY = self.nextY - - var DISTANCE_BETWEEN = 120 - - self.nextX = self.nextX + DISTANCE_BETWEEN * self.nextXshift - self.nextY = self.nextY + DISTANCE_BETWEEN * self.nextYshift - - self.timeToTurn += 1 - // if true, it's time to turn - if (self.timeToTurn === self.sideLength) { - self.turnCount += 1 - // if true, it's time to increase side length - if (self.turnCount % 2 === 0) { - self.sideLength += 1 - } - self.timeToTurn = 0 - - // going right? turn down - if (self.nextXshift == 1 && self.nextYshift == 0) { - self.nextXshift = 0 - self.nextYshift = 1 - } - // going down? turn left - else if (self.nextXshift == 0 && self.nextYshift == 1) { - self.nextXshift = -1 - self.nextYshift = 0 - } - // going left? turn up - else if (self.nextXshift == -1 && self.nextYshift == 0) { - self.nextXshift = 0 - self.nextYshift = -1 - } - // going up? turn right - else if (self.nextXshift == 0 && self.nextYshift == -1) { - self.nextXshift = 1 - self.nextYshift = 0 - } - } - - // this is so that if someone has relied on the auto-placement feature on this map, - // it will at least start placing nodes at the first empty spot - // this will only work up to the point in the spiral at which someone manually moved a node - if (Metamaps.Mappings.findWhere({ xloc: nextX, yloc: nextY })) { - return self.getNextCoord() - } - else return { - x: nextX, - y: nextY - } - }, - resetSpiral: function () { - Metamaps.Map.nextX = 0 - Metamaps.Map.nextY = 0 - Metamaps.Map.nextXshift = 1 - Metamaps.Map.nextYshift = 0 - Metamaps.Map.sideLength = 1 - Metamaps.Map.timeToTurn = 0 - Metamaps.Map.turnCount = 0 - }, exportImage: function () { var canvas = {} diff --git a/app/assets/javascripts/src/Metamaps.PasteInput.js b/app/assets/javascripts/src/Metamaps.PasteInput.js new file mode 100644 index 00000000..aaf848d0 --- /dev/null +++ b/app/assets/javascripts/src/Metamaps.PasteInput.js @@ -0,0 +1,110 @@ +/* global Metamaps, $ */ + +/* + * Metamaps.PasteInput.js.erb + * + * Dependencies: + * - Metamaps.Import + * - Metamaps.AutoLayout + */ + +Metamaps.PasteInput = { + // thanks to https://github.com/kevva/url-regex + URL_REGEX: new RegExp('^(?:(?:(?:[a-z]+:)?//)|www\.)(?:\S+(?::\S*)?@)?(?:localhost|(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#][^\s"]*)?$'), + + init: function () { + var self = Metamaps.PasteInput + + // intercept dragged files + // see http://stackoverflow.com/questions/6756583 + window.addEventListener("dragover", function(e) { + e = e || event; + e.preventDefault(); + }, false); + window.addEventListener("drop", function(e) { + e = e || event; + e.preventDefault(); + var coords = Metamaps.Util.pixelsToCoords({ x: e.clientX, y: e.clientY }) + if (e.dataTransfer.files.length > 0) { + var fileReader = new FileReader() + var text = fileReader.readAsText(e.dataTransfer.files[0]) + fileReader.onload = function(e) { + var text = e.currentTarget.result + if (text.substring(0,5) === '(.*)<\/string>[\s\S]*/m, '$1') + } + self.handle(text, coords) + } + } + // OMG import bookmarks 😍 + if (e.dataTransfer.items.length > 0) { + e.dataTransfer.items[0].getAsString(function(text) { + if (text.match(self.URL_REGEX)) { + self.handle(text, coords) + } + }) + } + }, false); + + // allow pasting onto canvas (but don't break existing inputs/textareas) + $('body').bind('paste', function (e) { + if (e.target.tagName === 'INPUT') return + if (e.target.tagName === 'TEXTAREA') return + + var text = e.originalEvent.clipboardData.getData('text/plain').trim() + self.handle(text) + }) + }, + + handle: function(text, coords) { + var self = Metamaps.PasteInput + + if (text.match(self.URL_REGEX)) { + self.handleURL(text, coords) + } else if (text[0] === '{') { + self.handleJSON(text) + } else if (text.match(/\t/)) { + self.handleTSV(text) + } else { + // fail silently + } + }, + + handleURL: function (text, coords) { + var title = 'Link' + if (!coords || !coords.x || !coords.y) { + coords = Metamaps.AutoLayout.getNextCoord() + } + + var import_id = null // don't store a cidMapping + var permission = null // use default + + Metamaps.Import.createTopicWithParameters( + title, + 'Reference', // metacode - todo fix + permission, + text, // desc - todo load from url? + text, // link - todo fix because this isn't being POSTed + coords.x, + coords.y, + import_id, + { + success: function(topic) { + Metamaps.TopicCard.showCard(topic.get('node'), function() { + $('#showcard #titleActivator').click() + .find('textarea, input').focus() + }) + } + } + ) + }, + + handleJSON: function (text) { + Metamaps.Import.handleJSON(text) + }, + + handleTSV: function (text) { + Metamaps.Import.handleTSV(text) + } +} diff --git a/app/assets/javascripts/src/Metamaps.Topic.js b/app/assets/javascripts/src/Metamaps.Topic.js index 40a4cd42..a0ebfa82 100644 --- a/app/assets/javascripts/src/Metamaps.Topic.js +++ b/app/assets/javascripts/src/Metamaps.Topic.js @@ -186,11 +186,10 @@ Metamaps.Topic = { error: function () {} }) }, - /* - * - * - */ - renderTopic: function (mapping, topic, createNewInDB, permitCreateSynapseAfter) { + + // opts is additional options in a hash + // TODO: move createNewInDB and permitCerateSYnapseAfter into opts + renderTopic: function (mapping, topic, createNewInDB, permitCreateSynapseAfter, opts) { var self = Metamaps.Topic var nodeOnViz, tempPos @@ -265,18 +264,24 @@ Metamaps.Topic = { }) } - var mappingSuccessCallback = function (mappingModel, response) { + var mappingSuccessCallback = function (mappingModel, response, topicModel) { var newTopicData = { mappingid: mappingModel.id, mappableid: mappingModel.get('mappable_id') } $(document).trigger(Metamaps.JIT.events.newTopic, [newTopicData]) + // call a success callback if provided + if (opts.success) { + opts.success(topicModel) + } } var topicSuccessCallback = function (topicModel, response) { if (Metamaps.Active.Map) { mapping.save({ mappable_id: topicModel.id }, { - success: mappingSuccessCallback, + success: function (model, response) { + mappingSuccessCallback(model, response, topicModel) + }, error: function (model, response) { console.log('error saving mapping to database') } @@ -326,7 +331,7 @@ Metamaps.Topic = { Metamaps.Topics.add(topic) if (Metamaps.Create.newTopic.pinned) { - var nextCoords = Metamaps.Map.getNextCoord() + var nextCoords = Metamaps.AutoLayout.getNextCoord() } var mapping = new Metamaps.Backbone.Mapping({ xloc: nextCoords ? nextCoords.x : Metamaps.Create.newTopic.x, @@ -351,7 +356,7 @@ Metamaps.Topic = { var topic = self.get(id) if (Metamaps.Create.newTopic.pinned) { - var nextCoords = Metamaps.Map.getNextCoord() + var nextCoords = Metamaps.AutoLayout.getNextCoord() } var mapping = new Metamaps.Backbone.Mapping({ xloc: nextCoords ? nextCoords.x : Metamaps.Create.newTopic.x, @@ -370,7 +375,7 @@ Metamaps.Topic = { var topic = self.get(id) - var nextCoords = Metamaps.Map.getNextCoord() + var nextCoords = Metamaps.AutoLayout.getNextCoord() var mapping = new Metamaps.Backbone.Mapping({ xloc: nextCoords.x, yloc: nextCoords.y, diff --git a/app/assets/javascripts/src/Metamaps.TopicCard.js b/app/assets/javascripts/src/Metamaps.TopicCard.js index f1424ed9..1453104d 100644 --- a/app/assets/javascripts/src/Metamaps.TopicCard.js +++ b/app/assets/javascripts/src/Metamaps.TopicCard.js @@ -37,7 +37,7 @@ Metamaps.TopicCard = { * Will open the Topic Card for the node that it's passed * @param {$jit.Graph.Node} node */ - showCard: function (node) { + showCard: function (node, opts) { var self = Metamaps.TopicCard var topic = node.getData('topic') @@ -46,7 +46,11 @@ Metamaps.TopicCard = { 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') + return $('.showcard').fadeIn('fast', function() { + if (opts.complete) { + opts.complete() + } + }) }, hideCard: function () { var self = Metamaps.TopicCard @@ -413,8 +417,8 @@ Metamaps.TopicCard = { nodeValues.attachments = '' } - var inmapsAr = topic.get('inmaps') - var inmapsLinks = topic.get('inmapsLinks') + var inmapsAr = topic.get('inmaps') || [] + var inmapsLinks = topic.get('inmapsLinks') || [] nodeValues.inmaps = '' if (inmapsAr.length < 6) { for (i = 0; i < inmapsAr.length; i++) {