From 2b267ea0c66f45d8ed2346bc27f1087beff61dbd Mon Sep 17 00:00:00 2001 From: Tadasu85 Date: Sat, 2 Aug 2014 13:49:51 -0400 Subject: [PATCH] filterbox progress --- app/assets/images/icons/synapsevisualize.png | Bin 0 -> 909 bytes .../javascripts/metamaps/Metamaps.Router.js | 6 +- .../javascripts/metamaps/Metamaps.Router.js~ | 35 + app/assets/javascripts/metamaps/Metamaps.js | 2713 +++++++++++++++++ app/assets/javascripts/metamaps/Metamaps.js~ | 2711 ++++++++++++++++ app/views/maps/show.html.erb | 6 +- app/views/maps/show.html.erb~ | 82 + app/views/shared/_filterBox.html.erb | 44 + app/views/shared/_filterBox.html.erb~ | 42 + db/schema.rb | 258 +- 10 files changed, 5761 insertions(+), 136 deletions(-) create mode 100644 app/assets/images/icons/synapsevisualize.png create mode 100644 app/assets/javascripts/metamaps/Metamaps.Router.js~ create mode 100644 app/assets/javascripts/metamaps/Metamaps.js create mode 100644 app/assets/javascripts/metamaps/Metamaps.js~ create mode 100644 app/views/maps/show.html.erb~ create mode 100644 app/views/shared/_filterBox.html.erb create mode 100644 app/views/shared/_filterBox.html.erb~ diff --git a/app/assets/images/icons/synapsevisualize.png b/app/assets/images/icons/synapsevisualize.png new file mode 100644 index 0000000000000000000000000000000000000000..a3f85f59c5d5d7d3486ef38e256193de8f143a30 GIT binary patch literal 909 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGoY)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmPq2a}+pk-F*4i3|+PQl2i3Ar-gY-Zu2P?I6PTV1KCf zgPTH!J>6yoJaf?z?a_#on6&oH)V+~s-}r1i@@(BUiHW;5mSi50=wwyhd{vVxsEs2n zJ>g|Qvqyo@{M=cG|J`Q;bxhGYhIhHs2-m~Jo{ zup2OcU_Zdypsjeslsh2i(SGJ1YzwqLZWTDNQ{WLh_XOLI@r*kdv@CZ?G8eQ6*hez# zeDa&&4AX9&76CEt4VEA0GyY+^P|^DCH-le`0KeAXbMm6n0<9|cT}l=6w*M(pekZSU z;MHxvcTfD)gl}x?t=c>9OQCY#VI~!ug0o7K-tW>g@jZO;;>A^IX)^t+701rrKA2$8 z@OoFrB96CZzpp-?x{TqY&HhQ|KOF@Wx|Ck*+L!mme$|n}$cBoEQ*RzU%G#t*QFS-R z?7#P&AQ6RoaVygP?tB0H#rNO->&@@{o*w%6NA%as(;xdc`x)2XKb~O{HRq9u)CCh? zvwvT!^$s{rd-A=?_RhU~dVAJq&N}l}z4*^2(S-PAAMf6kwK-os@lVgPid|ij%^5BR+o5T{-eHR<>JgmHT>Irki4lSO;4VklAkLnjQK4aKo zQv7EN&tZq{w|%RvXYK#abmp4wA+^bzY|V_TuD&`{{NUd|yN$JL!f0ADOMXdDk}lYmt1kB7!v}$Bs(YW_7k4t*KC?M@eM^3d6o32SBTu$Rt<^a>y)XV4 zQ;a9?_4L*E+?Qo8yIG^HQumTeV169O<#R35md{+4c`CLG->FVdQ&MBb@0Lf{TSO5S3 literal 0 HcmV?d00001 diff --git a/app/assets/javascripts/metamaps/Metamaps.Router.js b/app/assets/javascripts/metamaps/Metamaps.Router.js index 812b9108..6fd212c4 100644 --- a/app/assets/javascripts/metamaps/Metamaps.Router.js +++ b/app/assets/javascripts/metamaps/Metamaps.Router.js @@ -14,7 +14,7 @@ }); Metamaps.Router = new Router(); Metamaps.Router.init = function () { - Backbone.history.start({ + /*Backbone.history.start({ pushState: true, root: '' }); @@ -30,6 +30,6 @@ evt.preventDefault(); Backbone.history.navigate(href.attr, true); } - }); + });*/ } -})(); \ No newline at end of file +})(); diff --git a/app/assets/javascripts/metamaps/Metamaps.Router.js~ b/app/assets/javascripts/metamaps/Metamaps.Router.js~ new file mode 100644 index 00000000..812b9108 --- /dev/null +++ b/app/assets/javascripts/metamaps/Metamaps.Router.js~ @@ -0,0 +1,35 @@ +(function () { + var Router = Backbone.Router.extend({ + routes: { + "": "home", // #home + "explore/:section": "explore", // #explore/active + "maps/:id": "maps" // #maps/7 + }, + explore: function (section) { + console.log(section); + }, + maps: function (id) { + console.log(id); + } + }); + Metamaps.Router = new Router(); + Metamaps.Router.init = function () { + Backbone.history.start({ + pushState: true, + root: '' + }); + console.log('router started'); + $(document).on("click", "a:not([data-bypass])", function (evt) { + var href = { + prop: $(this).prop("href"), + attr: $(this).attr("href") + }; + var root = location.protocol + "//" + location.host + Backbone.history.options.root; + + if (href.prop && href.prop.slice(0, root.length) === root) { + evt.preventDefault(); + Backbone.history.navigate(href.attr, true); + } + }); + } +})(); \ No newline at end of file diff --git a/app/assets/javascripts/metamaps/Metamaps.js b/app/assets/javascripts/metamaps/Metamaps.js new file mode 100644 index 00000000..d84b47a9 --- /dev/null +++ b/app/assets/javascripts/metamaps/Metamaps.js @@ -0,0 +1,2713 @@ +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); +})(); + +// TODO eliminate these 4 global variables +var panningInt; // this variable is used to store a 'setInterval' for the Metamaps.JIT.SmoothPanning() function, so that it can be cleared with window.clearInterval +var tempNode = null, + tempInit = false, + tempNode2 = null; + +Metamaps.Settings = { + embed: false, // indicates that the app is on a page that is optimized for embedding in iFrames on other web pages + sandbox: false, // puts the app into a mode (when true) where it only creates data locally, and isn't writing it to the database + colors: { + background: '#344A58', + synapses: { + normal: '#222222', + hover: '#222222', + selected: '#FFFFFF' + }, + topics: { + selected: '#FFFFFF' + }, + labels: { + background: '#18202E', + text: '#DDD' + } + } +}; + +Metamaps.Touch = { + touchPos: null, // this stores the x and y values of a current touch event + touchDragNode: null // this stores a reference to a JIT node that is being dragged +}; + +Metamaps.Mouse = { + didPan: false, + changeInX: 0, + changeInY: 0, + edgeHoveringOver: false, + boxStartCoordinates: false, + boxEndCoordinates: false, + synapseStartCoordinates: [], + synapseEndCoordinates: null, + lastNodeClick: 0, + lastCanvasClick: 0, + DOUBLE_CLICK_TOLERANCE: 300 +}; + +Metamaps.Selected = { + Nodes: [], + Edges: [] +}; + +Metamaps.Metacodes = {}; // will be initialized in Metamaps.Backbone.init as a MetacodeCollection +Metamaps.Topics = {}; // will be initialized in Metamaps.Backbone.init as a TopicCollection +Metamaps.Synapses = {}; // will be initialized in Metamaps.Backbone.init as a SynapseCollection +Metamaps.Mappings = {}; // will be initialized in Metamaps.Backbone.init as a MappingCollection + + +/* + * + * BACKBONE + * + */ +Metamaps.Backbone.init = function () { + var self = Metamaps.Backbone; + + self.Metacode = Backbone.Model.extend({ + initialize: function () { + var image = new Image(); + image.src = this.get('icon'); + this.set('image',image); + } + }); + self.MetacodeCollection = Backbone.Collection.extend({ + model: this.Metacode, + url: '/metacodes', + }); + + self.Topic = Backbone.Model.extend({ + urlRoot: '/topics', + blacklist: ['node', 'created_at', 'updated_at'], + toJSON: function (options) { + return _.omit(this.attributes, this.blacklist); + }, + initialize: function () { + if (this.isNew()) { + this.set({ + "user_id": Metamaps.Active.Mapper.id, + "desc": '', + "link": '', + "permission": Metamaps.Active.Map ? Metamaps.Active.Map.get('permission') : 'commons' + }); + } + }, + authorizeToEdit: function (mapper) { + if (mapper && (this.get('permission') === "commons" || this.get('user_id') === mapper.get('id'))) return true; + else return false; + }, + authorizePermissionChange: function (mapper) { + if (mapper && this.get('user_id') === mapper.get('id')) return true; + else return false; + }, + getDate: function () { + + }, + getUser: function () { + return Metamaps.Mapper.get(this.get('user_id')); + }, + getMetacode: function () { + return Metamaps.Metacodes.get(this.get('metacode_id')); + }, + getMapping: function () { + + if (!Metamaps.Active.Map) return false; + + return Metamaps.Mappings.findWhere({ + map_id: Metamaps.Active.Map.id, + topic_id: this.isNew() ? this.cid : this.id + }); + }, + updateMapping: function () { + var mapping = this.getMapping(); + + if (mapping) { + mapping.set('topic_id', this.id); + } + }, + createNode: function () { + var mapping; + var node = { + adjacencies: [], + id: this.isNew() ? this.cid : this.id, + name: this.get('name') + }; + + if (Metamaps.Active.Map) { + mapping = this.getMapping(); + node.data = { + $mapping: null, + $mappingID: mapping.id + }; + } + + return node; + }, + updateNode: function () { + var mapping; + var node = this.get('node'); + node.setData('topic', this); + node.id = this.isNew() ? this.cid : this.id; + + if (Metamaps.Active.Map) { + mapping = this.getMapping(); + node.setData('mapping', mapping); + } + + return node; + }, + }); + + self.TopicCollection = Backbone.Collection.extend({ + model: self.Topic, + url: '/topics', + comparator: function (a, b) { + a = a.get('name').toLowerCase(); + b = b.get('name').toLowerCase(); + return a > b ? 1 : a < b ? -1 : 0; + } + }); + + self.Synapse = Backbone.Model.extend({ + urlRoot: '/synapses', + blacklist: ['edge', 'created_at', 'updated_at'], + toJSON: function (options) { + return _.omit(this.attributes, this.blacklist); + }, + initialize: function () { + if (this.isNew()) { + this.set({ + "user_id": Metamaps.Active.Mapper.id, + "permission": Metamaps.Active.Map ? Metamaps.Active.Map.get('permission') : 'commons', + "category": "from-to" + }); + } + }, + authorizeToEdit: function (mapper) { + if (mapper && (this.get('permission') === "commons" || this.get('user_id') === mapper.get('id'))) return true; + else return false; + }, + authorizePermissionChange: function (mapper) { + if (mapper && this.get('user_id') === mapper.get('id')) return true; + else return false; + }, + getUser: function () { + return Metamaps.Mapper.get(this.get('user_id')); + }, + getTopic1: function () { + return Metamaps.Topic.get(this.get('node1_id')); + }, + getTopic2: function () { + return Metamaps.Topic.get(this.get('node2_id')); + }, + getDirection: function () { + return [ + this.get('node1_id'), + this.get('node2_id') + ]; + }, + getMapping: function () { + + if (!Metamaps.Active.Map) return false; + + return Metamaps.Mappings.findWhere({ + map_id: Metamaps.Active.Map.id, + synapse_id: this.isNew() ? this.cid : this.id + }); + }, + updateMapping: function () { + var mapping = this.getMapping(); + + if (mapping) { + mapping.set('synapse_id', this.id); + } + }, + createEdge: function () { + var mapping, mappingID; + var synapseID = this.isNew() ? this.cid : this.id; + + var edge = { + nodeFrom: this.get('node1_id'), + nodeTo: this.get('node2_id'), + data: { + $synapses: [], + $synapseIDs: [synapseID], + } + }; + + if (Metamaps.Active.Map) { + mapping = this.getMapping(); + mappingID = mapping.isNew() ? mapping.cid : mapping.id; + edge.data.$mappings = []; + edge.data.$mappingIDs = [mappingID]; + } + + return edge; + }, + updateEdge: function () { + var mapping; + var edge = this.get('edge'); + edge.getData('synapses').push(this); + + if (Metamaps.Active.Map) { + mapping = this.getMapping(); + edge.getData('mappings').push(mapping); + } + + return edge; + }, + }); + + self.SynapseCollection = Backbone.Collection.extend({ + model: self.Synapse, + url: '/synapses' + }); + + self.Mapping = Backbone.Model.extend({ + urlRoot: '/mappings', + blacklist: ['created_at', 'updated_at'], + toJSON: function (options) { + return _.omit(this.attributes, this.blacklist); + }, + initialize: function () { + if (this.isNew()) { + this.set({ + "user_id": Metamaps.Active.Mapper.id, + "map_id": Metamaps.Active.Map ? Metamaps.Active.Map.id : null + }); + } + }, + getUser: function () { + return Metamaps.Mapper.get(this.get('user_id')); + }, + getMap: function () { + return Metamaps.Map.get(this.get('map_id')); + }, + getTopic: function () { + if (this.get('category') === 'Topic') return Metamaps.Topic.get(this.get('topic_id')); + else return false; + }, + getSynapse: function () { + if (this.get('category') === 'Synapse') return Metamaps.Synapse.get(this.get('synapse_id')); + else return false; + } + }); + + self.MappingCollection = Backbone.Collection.extend({ + model: self.Mapping, + url: '/mappings' + }); + + Metamaps.Metacodes = new self.MetacodeCollection(Metamaps.Metacodes); + + Metamaps.Topics = new self.TopicCollection(Metamaps.Topics); + + Metamaps.Synapses = new self.SynapseCollection(Metamaps.Synapses); + + Metamaps.Mappings = new self.MappingCollection(Metamaps.Mappings); + + if (Metamaps.Active.Map) { + Metamaps.Active.Map = new self.Map(Metamaps.Active.Map); + Metamaps.Maps.add(Metamaps.Active.Map); + } + + if (Metamaps.Active.Topic) Metamaps.Active.Topic = new self.Topic(Metamaps.Active.Topic); +}; // end Metamaps.Backbone.init + + +/* + * + * CREATE + * + */ +Metamaps.Create = { + isSwitchingSet: false, // indicates whether the metacode set switch lightbox is open + metacodeScrollerInit: false, // indicates whether the scrollbar in the custom metacode set space has been init + selectedMetacodeSet: null, + selectedMetacodeSetIndex: null, + selectedMetacodeNames: [], + newSelectedMetacodeNames: [], + selectedMetacodes: [], + newSelectedMetacodes: [], + init: function () { + var self = Metamaps.Create; + self.newTopic.init(); + self.newSynapse.init(); + + ////// + ////// + //// SWITCHING METACODE SETS + + $('#metacodeSwitchTabs').tabs({ + selected: self.selectedMetacodeSetIndex + }).addClass("ui-tabs-vertical ui-helper-clearfix"); + $("#metacodeSwitchTabs .ui-tabs-nav li").removeClass("ui-corner-top").addClass("ui-corner-left"); + $('.customMetacodeList li').click(self.toggleMetacodeSelected); // within the custom metacode set tab + }, + toggleMetacodeSelected: function () { + var self = Metamaps.Create; + + if ($(this).attr('class') != 'toggledOff') { + $(this).addClass('toggledOff'); + var value_to_remove = $(this).attr('id'); + var name_to_remove = $(this).attr('data-name'); + self.newSelectedMetacodes.splice(self.newSelectedMetacodes.indexOf(value_to_remove), 1); + self.newSelectedMetacodeNames.splice(self.newSelectedMetacodeNames.indexOf(name_to_remove), 1); + } else if ($(this).attr('class') == 'toggledOff') { + $(this).removeClass('toggledOff'); + self.newSelectedMetacodes.push($(this).attr('id')); + self.newSelectedMetacodeNames.push($(this).attr('data-name')); + } + }, + updateMetacodeSet: function (set, index, custom) { + + if (custom && Metamaps.Create.newSelectedMetacodes.length == 0) { + alert('Please select at least one metacode to use!'); + return false; + } + + var codesToSwitchTo; + Metamaps.Create.selectedMetacodeSetIndex = index; + Metamaps.Create.selectedMetacodeSet = "metacodeset-" + set; + + if (!custom) { + codesToSwitchTo = $('#metacodeSwitchTabs' + set).attr('data-metacodes').split(','); + $('.customMetacodeList li').addClass('toggledOff'); + Metamaps.Create.selectedMetacodes = []; + Metamaps.Create.selectedMetacodeNames = []; + Metamaps.Create.newSelectedMetacodes = []; + Metamaps.Create.newSelectedMetacodeNames = []; + } + if (custom) { + // uses .slice to avoid setting the two arrays to the same actual array + Metamaps.Create.selectedMetacodes = Metamaps.Create.newSelectedMetacodes.slice(0); + Metamaps.Create.selectedMetacodeNames = Metamaps.Create.newSelectedMetacodeNames.slice(0); + codesToSwitchTo = Metamaps.Create.selectedMetacodeNames.slice(0); + } + + // sort by name + codesToSwitchTo.sort(); + codesToSwitchTo.reverse(); + + $('#metacodeImg, #metacodeImgTitle').empty(); + $('#metacodeImg').removeData('cloudcarousel'); + var newMetacodes = ""; + var metacode; + for (var i = 0; i < codesToSwitchTo.length; i++) { + metacode = Metamaps.Metacodes.findWhere({ name: codesToSwitchTo[i] }); + newMetacodes += '' + metacode.get('name') + ''; + }; + $('#metacodeImg').empty().append(newMetacodes).CloudCarousel({ + titleBox: $('#metacodeImgTitle'), + yRadius: 40, + xPos: 150, + yPos: 40, + speed: 0.3, + mouseWheel: true, + bringToFront: true + }); + + Metamaps.GlobalUI.closeLightbox(); + $('#topic_name').focus(); + + var mdata = { + "metacodes": { + "value": custom ? Metamaps.Create.selectedMetacodes.toString() : Metamaps.Create.selectedMetacodeSet + } + }; + $.ajax({ + type: "POST", + dataType: 'json', + url: "/user/updatemetacodes", + data: mdata, + success: function (data) { + console.log('selected metacodes saved'); + }, + error: function () { + console.log('failed to save selected metacodes'); + } + }); + }, + + cancelMetacodeSetSwitch: function () { + var self = Metamaps.Create; + self.isSwitchingSet = false; + + if (self.selectedMetacodeSet != "metacodeset-custom") { + $('.customMetacodeList li').addClass('toggledOff'); + self.selectedMetacodes = []; + self.selectedMetacodeNames = []; + self.newSelectedMetacodes = []; + self.newSelectedMetacodeNames = []; + } else { // custom set is selected + // reset it to the current actual selection + $('.customMetacodeList li').addClass('toggledOff'); + for (var i = 0; i < self.selectedMetacodes.length; i++) { + $('#' + self.selectedMetacodes[i]).removeClass('toggledOff'); + }; + // uses .slice to avoid setting the two arrays to the same actual array + self.newSelectedMetacodeNames = self.selectedMetacodeNames.slice(0); + self.newSelectedMetacodes = self.selectedMetacodes.slice(0); + } + $('#metacodeSwitchTabs').tabs("select", self.selectedMetacodeSetIndex); + $('#topic_name').focus(); + }, + newTopic: { + init: function () { + $('#new_topic').bind('contextmenu', function (e) { + return false; + }); + + $('#topic_name').keyup(function () { + Metamaps.Create.newTopic.name = $(this).val(); + }); + + // initialize the autocomplete results for the metacode spinner + $('#topic_name').typeahead([ + { + name: 'topic_autocomplete', + limit: 8, + template: $('#topicAutocompleteTemplate').html(), + remote: { + url: '/topics/autocomplete_topic?term=%QUERY' + }, + engine: Hogan + } + ]); + + // tell the autocomplete to submit the form with the topic you clicked on if you pick from the autocomplete + $('#topic_name').bind('typeahead:selected', function (event, datum, dataset) { + Metamaps.Topic.getTopicFromAutocomplete(datum.id); + }); + + // initialize metacode spinner and then hide it + $("#metacodeImg").CloudCarousel({ + titleBox: $('#metacodeImgTitle'), + yRadius: 40, + xPos: 150, + yPos: 40, + speed: 0.3, + mouseWheel: true, + bringToFront: true + }); + $('.new_topic').hide(); + }, + name: null, + newId: 1, + beingCreated: false, + metacode: null, + x: null, + y: null, + addSynapse: false, + open: function () { + $('#new_topic').fadeIn('fast', function () { + $('#topic_name').focus(); + }); + Metamaps.Create.newTopic.beingCreated = true; + }, + hide: function () { + $('#new_topic').fadeOut('fast'); + $("#topic_name").typeahead('setQuery', ''); + Metamaps.Create.newTopic.beingCreated = false; + } + }, + newSynapse: { + init: function () { + var self = Metamaps.Create.newSynapse; + + // keep the right click menu from opening + $('#new_synapse').bind('contextmenu', function (e) { + return false; + }); + + $('#synapse_desc').keyup(function () { + Metamaps.Create.newSynapse.description = $(this).val(); + }); + + // initialize the autocomplete results for synapse creation + $('#synapse_desc').typeahead([ + { + name: 'synapse_autocomplete', + template: "
{{label}}
", + remote: { + url: '/search/synapses?term=%QUERY' + }, + engine: Hogan + }, + { + name: 'existing_synapses', + limit: 50, + template: $('#synapseAutocompleteTemplate').html(), + remote: { + url: '/search/synapses', + replace: function () { + return self.getSearchQuery(); + } + }, + engine: Hogan, + header: "

Existing Synapses

" + } + ]); + + $('#synapse_desc').bind('typeahead:selected', function (event, datum, dataset) { + if (datum.id) { // if they clicked on an existing synapse get it + Metamaps.Synapse.getSynapseFromAutocomplete(datum.id); + } + }); + }, + beingCreated: false, + description: null, + topic1id: null, + topic2id: null, + newSynapseId: null, + open: function () { + $('#new_synapse').fadeIn('fast', function () { + $('#synapse_desc').focus(); + }); + Metamaps.Create.newSynapse.beingCreated = true; + }, + hide: function () { + $('#new_synapse').fadeOut('fast'); + $("#synapse_desc").typeahead('setQuery', ''); + Metamaps.Create.newSynapse.beingCreated = false; + Metamaps.Create.newTopic.addSynapse = false; + Metamaps.Create.newSynapse.topic1id = 0; + Metamaps.Create.newSynapse.topic2id = 0; + }, + getSearchQuery: function () { + var self = Metamaps.Create.newSynapse; + + if (Metamaps.Selected.Nodes.length < 2) { + return '/search/synapses?topic1id=' + self.topic1id + '&topic2id=' + self.topic2id; + } else return ''; + } + } +}; // end Metamaps.Create + + +////////////////// TOPIC AND SYNAPSE CARDS ////////////////////////// + + +/* + * + * TOPICCARD + * + */ +Metamaps.TopicCard = { + openTopicCard: null, //stores the JIT local ID of the topic with the topic card open + init: function () { + + // 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" + }); + $('#showcard').resizable({ + maxHeight: 500, + maxWidth: 500, + minHeight: 320, + minWidth: 226, + resize: function (event, ui) { + var p = $('#showcard').find('.scroll'); + p.height(p.height()).mCustomScrollbar('update'); + } + }).css({ + display: 'none', + top: '300px', + left: '100px' + }); + }, + fadeInShowCard: function (topic) { + $('.showcard').fadeIn('fast'); + Metamaps.TopicCard.openTopicCard = topic.isNew() ? topic.cid : topic.id; + }, + /** + * Will open the Topic Card for the node that it's passed + * @param {$jit.Graph.Node} node + */ + showCard: function (node) { + + var topic = node.getData('topic'); + + //populate the card that's about to show with the right topics data + Metamaps.TopicCard.populateShowCard(topic); + Metamaps.TopicCard.fadeInShowCard(topic); + }, + hideCard: function () { + $('.showcard').fadeOut('fast'); + Metamaps.TopicCard.openTopicCard = null; + }, + bindShowCardListeners: function (topic) { + var self = Metamaps.TopicCard; + var showCard = document.getElementById('showcard'); + + 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'); + } + }); + + $('.showcard .metacodeTitle').click(function () { + if (!selectingMetacode) { + selectingMetacode = true; + $(this).addClass('minimize'); // this line flips the drop down arrow to a pull up arrow + $('.metacodeSelect').show(); + // add the scroll bar to the list of metacode select options if it isn't already there + if (!$('.metacodeSelect ul').hasClass('mCustomScrollbar')) { + $('.metacodeSelect ul').mCustomScrollbar({ + mouseWheelPixels: 200, + advanced: { + updateOnContentResize: true + } + }); + + $('.metacodeSelect li').click(function () { + selectingMetacode = false; + var metacodeName = $(this).find('.mSelectName').text(); + var metacode = Metamaps.Metacodes.findWhere({ + name: metacodeName + }); + $('.CardOnGraph').find('.metacodeTitle').text(metacodeName) + .attr('class', 'metacodeTitle mbg' + metacodeName.replace(/\s/g, '')); + $('.CardOnGraph').find('.metacodeImage').css('background-image', 'url(' + metacode.get('icon') + ')'); + topic.save({ + metacode_id: metacode.id + }); + Metamaps.Visualize.mGraph.plot(); + $('.metacodeTitle').removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow + $('.metacodeSelect').hide(); + setTimeout(function () { + $('.metacodeTitle').hide(); + $('.showcard .icon').css('z-index', '1'); + }, 500); + }); + } + } else { + selectingMetacode = false; + $(this).removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow + $('.metacodeSelect').hide(); + } + }); + + + // ability to change permission + var selectingPermission = false; + if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) { + $('.showcard .yourTopic .mapPerm').click(function () { + 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('
'); + } + $('.permissionSelect li').click(function (event) { + selectingPermission = false; + var permission = $(this).attr('class'); + topic.save({ + permission: permission + }); + $('.showcard .mapPerm').removeClass('co pu pr minimize').addClass(permission.substring(0, 2)); + $('.permissionSelect').remove(); + event.stopPropagation(); + }); + } else { + selectingPermission = false; + $(this).removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow + $('.permissionSelect').remove(); + } + }); + } + + // when you're typing a description, resize the scroll box to have space + $('.best_in_place_desc textarea').bind('keyup', function () { + var s = $('.showcard').find('.scroll'); + s.height(s.height()).mCustomScrollbar('update'); + }); + + //bind best_in_place ajax callbacks + $(showCard).find('.best_in_place_name').bind("ajax:success", function () { + + var s = $('.showcard').find('.scroll'); + s.height(s.height()).mCustomScrollbar('update'); + + var name = $(this).html(); + topic.set("name", Metamaps.Util.decodeEntities(name)); + Metamaps.Visualize.mGraph.plot(); + }); + + $(showCard).find('.best_in_place_desc').bind("ajax:success", function () { + this.innerHTML = this.innerHTML.replace(/\r/g, '') + + var s = $('.showcard').find('.scroll'); + s.height(s.height()).mCustomScrollbar('update'); + + var desc = $(this).html(); + topic.set("desc", desc); + }); + + $(showCard).find('.best_in_place_link').bind("ajax:success", function () { + var link = $(this).html(); + $(showCard).find('.go-link').attr('href', link); + topic.set("link", link); + }); + }, + populateShowCard: function (topic) { + var self = Metamaps.TopicCard; + + var showCard = document.getElementById('showcard'); + + $(showCard).find('.permission').remove(); + + var html = self.generateShowcardHTML.render(self.buildObject(topic)); + + 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 nodeValues = {}; + var authorized = topic.authorizeToEdit(Metamaps.Active.Mapper); + + //link is rendered differently if user is logged out or in + var go_link, a_tag, close_a_tag; + if (!authorized) { + go_link = ''; + if (topic.get("link") != "") { + a_tag = ''; + close_a_tag = ''; + } else { + a_tag = ''; + close_a_tag = ''; + } + } else { + go_link = ''; + a_tag = ''; + close_a_tag = ''; + } + + var desc_nil = "Click to add description..."; + var link_nil = "Click to add link..."; + + nodeValues.permission = topic.get("permission"); + nodeValues.mk_permission = topic.get("permission").substring(0, 2); + //nodeValues.map_count = topic.get("inmaps").length; + //nodeValues.synapse_count = topic.get("synapseCount"); + nodeValues.id = topic.isNew() ? topic.cid : topic.id; + nodeValues.metacode = topic.getMetacode().get("name"); + nodeValues.metacode_class = 'mbg' + topic.getMetacode().get("name").replace(/\s/g, ''); + nodeValues.imgsrc = topic.getMetacode().get("icon"); + nodeValues.name = topic.get("name"); + nodeValues.userid = topic.get("user_id"); + nodeValues.username = topic.getUser().get("name"); + nodeValues.date = topic.getDate(); + + // the code for this is stored in /views/main/_metacodeOptions.html.erb + nodeValues.metacode_select = $('#metacodeOptions').html(); + nodeValues.go_link = go_link; + nodeValues.a_tag = a_tag; + nodeValues.close_a_tag = close_a_tag; + nodeValues.link_nil = link_nil; + nodeValues.link = (topic.get("link") == "" && authorized) ? link_nil : topic.get("link"); + 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 synapse = edge.getData('synapses')[0]; // 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.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'; + } + $('.main .wrapper').append(edit_div); + + self.populateShowCard(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 = synapse.isNew() ? synapse.cid : synapse.id; + }, + + hideCard: function () { + $('#edit_synapse').remove(); + Metamaps.SynapseCard.openSynapseCard = null; + }, + + populateShowCard: function (synapse) { + var self = Metamaps.SynapseCard; + + self.add_name_form(synapse); + self.add_user_info(synapse); + self.add_perms_form(synapse); + if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) { + self.add_direction_form(synapse); + } + }, + + add_name_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 + + //name editing form + $('#edit_synapse').append('
'); + $('#edit_synapse_name').attr('class', 'best_in_place best_in_place_desc'); + $('#edit_synapse_name').attr('data-object', 'synapse'); + $('#edit_synapse_name').attr('data-attribute', 'desc'); + $('#edit_synapse_name').attr('data-type', 'textarea'); + $('#edit_synapse_name').attr('data-nil', data_nil); + $('#edit_synapse_name').attr('data-url', '/synapses/' + synapse.id); + $('#edit_synapse_name').html(synapse.get("desc")); + + //if edge data is blank or just whitespace, populate it with data_nil + if ($('#edit_synapse_name').html().trim() == '') { + $('#edit_synapse_name').html(data_nil); + } + + $('#edit_synapse_name').bind("ajax:success", function () { + var desc = $(this).html(); + if (desc == data_nil) { + synapse.set("desc", ''); + } else { + synapse.set("desc", desc); + } + Metamaps.Control.selectEdge(synapse.get('edge')); + Metamaps.Visualize.mGraph.plot(); + }); + }, + + add_user_info: function (synapse) { + var u = '
'; + u += '
Created by ' + synapse.getUser().get("name") + '
'; + $('#edit_synapse').append(u); + }, + + add_perms_form: function (synapse) { + //permissions - if owner, also allow permission editing + $('#edit_synapse').append('
'); + + // ability to change permission + var selectingPermission = false; + if (synapse.authorizePermissionChange(Metamaps.Active.Mapper)) { + $('#edit_synapse.yourEdge .mapPerm').click(function () { + 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(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(); + }); + } else { + selectingPermission = false; + $(this).removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow + $('#edit_synapse .permissionSelect').remove(); + } + }); + } + }, //add_perms_form + + add_direction_form: function (synapse) { + //directionality checkboxes + $('#edit_synapse').append(''); + $('#edit_synapse').append(''); + $('#edit_synapse').append(''); + $('#edit_synapse').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; + var right = edge.nodeFrom; + } else { + var left = edge.nodeFrom; + var right = edge.nodeTo; + } + + /* + * 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.getDirection(); + if (from_to[0] == left.id) { + //check left checkbox + $('#edit_synapse_left').prop('checked', true); + } else { + //check right checkbox + $('#edit_synapse_right').prop('checked', true); + } + } else if (directionCat == 'both') { + //check both checkboxes + $('#edit_synapse_left').prop('checked', true); + $('#edit_synapse_right').prop('checked', true); + } + $('#edit_synapse_left, #edit_synapse_right').click(function () { + 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(); + }); + } //add_direction_form +}; // end Metamaps.SynapseCard + + +////////////////////// END TOPIC AND SYNAPSE CARDS ////////////////////////////////// + + + + +/* + * + * VISUALIZE + * + */ +Metamaps.Visualize = { + mGraph: {}, // 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" + savedLayout: true, // indicates whether the map has a saved layout or not + 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 + target: null, // the selector representing the location to render the graph + 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; + }); + }, + render: function (targetID, vizData) { + var self = Metamaps.Visualize; + self.mGraph = {}; + self.target = targetID; + self.__buildGraph(vizData); + }, + computePositions: function () { + var self = Metamaps.Visualize, + mapping; + + if (self.type == "RGraph") { + self.mGraph.graph.eachNode(function (n) { + topic = Metamaps.Topics.get(n.id); + topic.set('node', n); + topic.updateNode(); + + n.eachAdjacency(function (edge) { + l = edge.getData('synapseIDs').length; + for (i = 0; i < l; i++) { + synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]); + synapse.set('edge', edge); + synapse.updateEdge(); + } + }); + + var pos = n.getPos(); + pos.setc(-200, -200); + }); + self.mGraph.compute('end'); + } else if (self.type == "ForceDirected" && self.savedLayout) { + var i, l, startPos, endPos, topic, synapse; + + self.mGraph.graph.eachNode(function (n) { + topic = Metamaps.Topics.get(n.id); + topic.set('node', n); + topic.updateNode(); + mapping = topic.getMapping(); + + n.eachAdjacency(function (edge) { + l = edge.getData('synapseIDs').length; + for (i = 0; i < l; i++) { + synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]); + synapse.set('edge', edge); + 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.savedLayout) { + self.mGraph.compute(); + } + }, + /** + * __buildGraph does the heavy lifting of creating the engine that renders the graph with the properties we desire + * + * @param vizData a json structure containing the data to be rendered. + */ + __buildGraph: function (vizData) { + var self = Metamaps.Visualize + RGraphSettings = $.extend(true, {}, Metamaps.JIT.ForceDirected.graphSettings); + + if (self.type == "RGraph") { + $jit.RGraph.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings); + $jit.RGraph.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings); + + RGraphSettings.background = Metamaps.JIT.RGraph.background; + RGraphSettings.levelDistance = Metamaps.JIT.RGraph.levelDistance; + + self.mGraph = new $jit.RGraph(RGraphSettings); + } else if (self.type == "ForceDirected") { + $jit.ForceDirected.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings); + $jit.ForceDirected.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings); + self.mGraph = new $jit.ForceDirected(Metamaps.JIT.ForceDirected.graphSettings); + } else if (self.type == "ForceDirected3D") { + // init ForceDirected3D + self.mGraph = new $jit.ForceDirected3D(Metamaps.JIT.ForceDirected3D.graphSettings); + self.cameraPosition = self.mGraph.canvas.canvases[0].camera.position; + } + + // load JSON data, if it's not empty + if (!self.loadLater) { + //load JSON data. + self.mGraph.loadJSON(vizData); + //compute positions and plot. + self.computePositions(); + if (self.type == "RGraph") { + self.mGraph.fx.animate(Metamaps.JIT.RGraph.animate); + } else if (self.type == "ForceDirected" && self.savedLayout) { + Metamaps.Organize.loadSavedLayout(); + } else if (self.type == "ForceDirected3D" || !self.savedLayout) { + self.mGraph.animate(Metamaps.JIT.ForceDirected.animateFDLayout); + } + } + } +}; // 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; + 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; + }, + 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)); + }, + 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(/^http:\/\/(?:www\.)?youtube.com\/watch\?(?=[^?]*v=\w+)(?:[^\s?]+)?$/) != null); + } +}; // end Metamaps.Util + +/* + * + * REALTIME + * + */ +Metamaps.Realtime = { + // this is for the heroku staging environment + //Metamaps.Realtime.socket = io.connect('http://gentle-savannah-1303.herokuapp.com'); + // this is for metamaps.cc + //Metamaps.Realtime.socket = io.connect('http://metamaps.cc:5001'); + // this is for localhost development + //Metamaps.Realtime.socket = io.connect('http://localhost:5001'); + socket: null, + isOpen: false, + timeOut: null, + changing: false, + mappersOnMap: {}, + status: true, // stores whether realtime is True/On or False/Off + init: function () { + var self = Metamaps.Realtime; + + $(".realtimeOnOff").click(self.toggle); + + $(".sidebarCollaborate").hover(self.open, self.close); + + var mapperm = Metamaps.Active.Map && Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); + + if (mapperm) { + self.socket = io.connect('http://localhost:5001'); + self.socket.on('connect', function () { + console.log('socket connected'); + self.setupSocket(); + }); + } + }, + toggle: function () { + var self = Metamaps.Realtime; + + if (!self.status) { + self.sendRealtimeOn(); + $(this).html('ON').removeClass('rtOff').addClass('rtOn'); + $(".rtMapperSelf").removeClass('littleRtOff').addClass('littleRtOn'); + } else { + self.sendRealtimeOff(); + $(this).html('OFF').removeClass('rtOn').addClass('rtOff'); + $(".rtMapperSelf").removeClass('littleRtOn').addClass('littleRtOff'); + } + self.status = !self.status; + $(".sidebarCollaborateIcon").toggleClass("blue"); + }, + open: function () { + var self = Metamaps.Realtime; + + clearTimeout(self.timeOut); + if (!self.isOpen && !self.changing) { + self.changing = true; + $('.sidebarCollaborateBox').fadeIn(200, function () { + self.changing = false; + self.isOpen = true; + }); + } + }, + close: function () { + var self = Metamaps.Realtime; + + self.timeOut = setTimeout(function () { + if (!self.changing) { + self.changing = true; + $('.sidebarCollaborateBox').fadeOut(200, function () { + self.changing = false; + self.isOpen = false; + }); + } + }, 500); + }, + 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"), + mapid: Metamaps.Active.Map.id + }); + + // 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, self.contentUpdate); + }, + 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.userrealtime + + self.mappersOnMap[data.userid] = { + name: data.username, + realtime: data.userrealtime + }; + + var onOff = data.userrealtime ? "On" : "Off"; + var mapperListItem = '
  • ' + data.username + '
  • '; + + $('#mapper' + data.userid).remove(); + $('.realtimeMapperList ul').append(mapperListItem); + }, + newPeerOnMap: function (data) { + var self = Metamaps.Realtime; + var socket = Metamaps.Realtime.socket; + + // data.userid + // data.username + + self.mappersOnMap[data.userid] = { + name: data.username, + realtime: true + }; + + var mapperListItem = '
  • ' + data.username + '
  • '; + $('#mapper' + data.userid).remove(); + $('.realtimeMapperList ul').append(mapperListItem); + + Metamaps.GlobalUI.notifyUser(data.username + ' just joined the map'); + + // 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"), + userid: Metamaps.Active.Mapper.id, + userrealtime: self.status, + mapid: Metamaps.Active.Map.id + }; + socket.emit('updateNewMapperList', update); + }, + lostPeerOnMap: function (data) { + var self = Metamaps.Realtime; + var socket = Metamaps.Realtime.socket; + + // data.userid + // data.username + + delete self.mappersOnMap[data.userid]; + + $('#mapper' + data.userid).remove(); + + Metamaps.GlobalUI.notifyUser(data.username + ' just left the map'); + }, + 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'); + + 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'); + + Metamaps.GlobalUI.notifyUser(data.username + ' just turned off realtime'); + }, + contentUpdate: function (data) { + var self = Metamaps.Realtime; + var socket = Metamaps.Realtime.socket; + var graph = Metamaps.Visualize.mGraph.graph; + + //as long as you weren't the origin of the changes, update your map + if (data.origin != Metamaps.Active.Mapper.id && self.status) { + if (data.resource == 'Topic') { + topic = $.parseJSON(data.obj); + + if (data.action == 'create') { + self.addTopicToMap(topic); + } else if (data.action == 'update' && graph.getNode(topic.id) != 'undefined') { + self.updateTopicOnMap(topic); + } else if (data.action == 'destroy' && graph.getNode(topic.id) != 'undefined') { + Metamaps.Control.hideNode(topic.id) + } + + return; + } else if (data.resource == 'Synapse') { + synapse = $.parseJSON(data.obj); + + if (data.action == 'create') { + self.addSynapseToMap(synapse); + } else if (data.action == 'update' && + graph.getAdjacence(synapse.data.$direction['0'], synapse.data.$direction['1']) != 'undefined') { + self.updateSynapseOnMap(synapse); + } else if (data.action == 'destroy' && + graph.getAdjacence(synapse.data.$direction['0'], synapse.data.$direction['1']) != 'undefined') { + var edge = graph.getAdjacence(synapse.data.$direction['0'], synapse.data.$direction['1']); + Metamaps.Control.hideEdge(edge); + } + + return; + } + } + }, + addTopicToMap: function (topic) { + + // TODO + var newPos, tempForT; + Metamaps.Visualize.mGraph.graph.addNode(topic); + tempForT = Metamaps.Visualize.mGraph.graph.getNode(topic.id); + tempForT.setData('dim', 1, 'start'); + tempForT.setData('dim', 25, 'end'); + newPos = new $jit.Complex(); + newPos.x = tempForT.data.$xloc; + newPos.y = tempForT.data.$yloc; + tempForT.setPos(newPos, 'start'); + tempForT.setPos(newPos, 'current'); + tempForT.setPos(newPos, 'end'); + Metamaps.Visualize.mGraph.fx.plotNode(tempForT, Metamaps.Visualize.mGraph.canvas); + }, + updateTopicOnMap: function (topic) { + + // TODO + var newPos, tempForT; + tempForT = Metamaps.Visualize.mGraph.graph.getNode(topic.id); + tempForT.data = topic.data; + tempForT.name = topic.name; + if (MetamapsModel.showcardInUse === topic.id) { + populateShowCard(tempForT); + } + newPos = new $jit.Complex(); + newPos.x = tempForT.data.$xloc; + newPos.y = tempForT.data.$yloc; + tempForT.setPos(newPos, 'start'); + tempForT.setPos(newPos, 'current'); + tempForT.setPos(newPos, 'end'); + return Metamaps.Visualize.mGraph.fx.animate({ + modes: ['linear', 'node-property:dim', 'edge-property:lineWidth'], + transition: $jit.Trans.Quad.easeInOut, + duration: 500 + }); + }, + addSynapseToMap: function (synapse) { + + // TODO + var Node1, Node2, tempForS; + Node1 = Metamaps.Visualize.mGraph.graph.getNode(synapse.data.$direction[0]); + Node2 = Metamaps.Visualize.mGraph.graph.getNode(synapse.data.$direction[1]); + Metamaps.Visualize.mGraph.graph.addAdjacence(Node1, Node2, {}); + tempForS = Metamaps.Visualize.mGraph.graph.getAdjacence(Node1.id, Node2.id); + tempForS.setDataset('start', { + lineWidth: 0.4 + }); + tempForS.setDataset('end', { + lineWidth: 2 + }); + tempForS.data = synapse.data; + Metamaps.Visualize.mGraph.fx.plotLine(tempForS, Metamaps.Visualize.mGraph.canvas); + return Metamaps.Visualize.mGraph.fx.animate({ + modes: ['linear', 'node-property:dim', 'edge-property:lineWidth'], + transition: $jit.Trans.Quad.easeInOut, + duration: 500 + }); + }, + updateSynapseOnMap: function (synapse) { + + // TODO + var k, tempForS, v, wasShowDesc, _ref; + tempForS = Metamaps.Visualize.mGraph.graph.getAdjacence(synapse.data.$direction[0], synapse.data.$direction[1]); + wasShowDesc = tempForS.data.$showDesc; + _ref = synapse.data; + for (k in _ref) { + v = _ref[k]; + tempForS.data[k] = v; + } + tempForS.data.$showDesc = wasShowDesc; + if (MetamapsModel.edgecardInUse === synapse.data.$id) { // TODO + editEdge(tempForS, false); + } + return Metamaps.Visualize.mGraph.plot(); + } +}; // end Metamaps.Realtime + + +/* + * + * CONTROL + * + */ +Metamaps.Control = { + init: function () { + + }, + selectNode: function (node) { + if (Metamaps.Selected.Nodes.indexOf(node) != -1) return; + node.selected = true; + node.setData('dim', 30, 'current'); + node.eachAdjacency(function (adj) { + Metamaps.Control.selectEdge(adj); + }); + Metamaps.Selected.Nodes.push(node); + }, + deselectAllNodes: function () { + var l = Metamaps.Selected.Nodes.length; + for (var i = l - 1; i >= 0; i -= 1) { + var node = Metamaps.Selected.Nodes[i]; + Metamaps.Control.deselectNode(node); + } + Metamaps.Visualize.mGraph.plot(); + }, + deselectNode: function (node) { + delete node.selected; + node.eachAdjacency(function (adj) { + Metamaps.Control.deselectEdge(adj); + }); + node.setData('dim', 25, 'current'); + + //remove the node + Metamaps.Selected.Nodes.splice( + Metamaps.Selected.Nodes.indexOf(node), 1); + }, + deleteSelectedNodes: function () { // refers to deleting topics permanently + var l = Metamaps.Selected.Nodes.length; + for (var i = l - 1; i >= 0; i -= 1) { + var node = Metamaps.Selected.Nodes[i]; + Metamaps.Control.deleteNode(node.id); + } + }, + deleteNode: function (nodeid) { // refers to deleting topics permanently + var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid); + var id = node.getData('id'); + Metamaps.Control.deselectNode(node); + Metamaps.Topics.get(id).destroy(); + Metamaps.Control.hideNode(nodeid); + }, + removeSelectedNodes: function () { // refers to removing topics permanently from a map + var l = Metamaps.Selected.Nodes.length, + i, + node, + mapperm = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); + + if (mapperm) { + for (i = l - 1; i >= 0; i -= 1) { + node = Metamaps.Selected.Nodes[i]; + Metamaps.Control.removeNode(node.id); + } + } + }, + removeNode: function (nodeid) { // refers to removing topics permanently from a map + var mapperm = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); + var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid); + var mappingid = node.getData("mapping").id; + + if (mapperm) { + Metamaps.Control.deselectNode(node); + Metamaps.Mappings.get(mappingid).destroy(); + Metamaps.Control.hideNode(nodeid); + } + }, + hideSelectedNodes: function () { + var l = Metamaps.Selected.Nodes.length, + i, + node; + + for (i = l - 1; i >= 0; i -= 1) { + node = Metamaps.Selected.Nodes[i]; + Metamaps.Control.hideNode(node.id); + } + }, + hideNode: function (nodeid) { + var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid); + 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; + } + + Metamaps.Control.deselectNode(node); + + node.setData('alpha', 0, 'end'); + node.eachAdjacency(function (adj) { + adj.setData('alpha', 0, 'end'); + }); + Metamaps.Visualize.mGraph.fx.animate({ + modes: ['node-property:alpha', + 'edge-property:alpha' + ], + duration: 500 + }); + setTimeout(function () { + Metamaps.Visualize.mGraph.graph.removeNode(nodeid); + }, 500); + }, + selectEdge: function (edge) { + if (Metamaps.Selected.Edges.indexOf(edge) != -1) return; + edge.setData('showDesc', true, 'current'); + if (!Metamaps.Settings.embed) { + edge.setDataset('end', { + lineWidth: 4, + color: Metamaps.Settings.colors.synapses.selected, + alpha: 1 + }); + } else if (Metamaps.Settings.embed) { + edge.setDataset('end', { + lineWidth: 4, + color: Metamaps.Settings.colors.synapses.selected, + alpha: 1 + }); + } + Metamaps.Visualize.mGraph.fx.animate({ + modes: ['edge-property:lineWidth:color:alpha'], + duration: 100 + }); + Metamaps.Selected.Edges.push(edge); + }, + deselectAllEdges: function () { + var l = Metamaps.Selected.Edges.length; + for (var i = l - 1; i >= 0; i -= 1) { + var edge = Metamaps.Selected.Edges[i]; + Metamaps.Control.deselectEdge(edge); + } + Metamaps.Visualize.mGraph.plot(); + }, + deselectEdge: function (edge) { + edge.setData('showDesc', false, 'current'); + edge.setDataset('end', { + lineWidth: 2, + color: Metamaps.Settings.colors.synapses.normal, + alpha: 0.4 + }); + + if (Metamaps.Mouse.edgeHoveringOver == edge) { + edge.setData('showDesc', true, 'current'); + edge.setDataset('end', { + lineWidth: 4, + color: Metamaps.Settings.colors.synapses.hover, + alpha: 1 + }); + } + + Metamaps.Visualize.mGraph.fx.animate({ + modes: ['edge-property:lineWidth:color:alpha'], + duration: 100 + }); + + //remove the edge + Metamaps.Selected.Edges.splice( + Metamaps.Selected.Edges.indexOf(edge), 1); + }, + deleteSelectedEdges: function () { // refers to deleting topics permanently + var edge, + l = Metamaps.Selected.Edges.length; + for (var i = l - 1; i >= 0; i -= 1) { + edge = Metamaps.Selected.Edges[i]; + Metamaps.Control.deleteEdge(edge); + } + }, + deleteEdge: function (edge) { + + // TODO make it so that you select which one, of multiple possible synapses you want to delete + + //var id = edge.getData("id"); + //Metamaps.Synapses.get(id).destroy(); + //Metamaps.Control.hideEdge(edge); + }, + removeSelectedEdges: function () { + var l = Metamaps.Selected.Edges.length, + i, + edge; + + if (Metamaps.Active.Map) { + for (i = l - 1; i >= 0; i -= 1) { + edge = Metamaps.Selected.Edges[i]; + Metamaps.Control.removeEdge(edge); + } + Metamaps.Selected.Edges = new Array(); + } + }, + removeEdge: function (edge) { + + // TODO make it so that you select which one, of multiple possible synapses you want + + //var mappingid = edge.getData("mappingid"); + //Metamaps.Mappings.get(mappingid).destroy(); + //Metamaps.Control.hideEdge(edge); + }, + hideSelectedEdges: function () { + var edge, + l = Metamaps.Selected.Edges.length, + i; + for (i = l - 1; i >= 0; i -= 1) { + edge = Metamaps.Selected.Edges[i]; + Metamaps.Control.hideEdge(edge); + } + Metamaps.Selected.Edges = new Array(); + }, + hideEdge: function (edge) { + var from = edge.nodeFrom.id; + var to = edge.nodeTo.id; + edge.setData('alpha', 0, 'end'); + Metamaps.Visualize.mGraph.fx.animate({ + modes: ['edge-property:alpha'], + duration: 500 + }); + setTimeout(function () { + Metamaps.Visualize.mGraph.graph.removeAdjacence(from, to); + }, 500); + }, + updateSelectedPermissions: function (permission) { + + var edge, synapse, node, topic; + + Metamaps.GlobalUI.notifyUser('Working...'); + + // variables to keep track of how many nodes and synapses you had the ability to change the permission of + var nCount = 0, + sCount = 0; + + // change the permission of the selected synapses, if logged in user is the original creator + var l = Metamaps.Selected.Edges.length; + for (var i = l - 1; i >= 0; i -= 1) { + edge = Metamaps.Selected.Edges[i]; + synapse = edge.getData('synapses')[0]; + + if (synapse.authorizePermissionChange(Metamaps.Active.Mapper)) { + synapse.save({ + permission: permission + }); + sCount++; + } + } + + // change the permission of the selected topics, if logged in user is the original creator + var l = Metamaps.Selected.Nodes.length; + for (var i = l - 1; i >= 0; i -= 1) { + node = Metamaps.Selected.Nodes[i]; + topic = node.getData('topic'); + + if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) { + topic.save({ + permission: permission + }); + nCount++; + } + } + + var nString = nCount == 1 ? (nCount.toString() + ' topic and ') : (nCount.toString() + ' topics and '); + var sString = sCount == 1 ? (sCount.toString() + ' synapse') : (sCount.toString() + ' synapses'); + + var message = nString + sString + ' you created updated to ' + permission; + Metamaps.GlobalUI.notifyUser(message); + }, +}; // end Metamaps.Control + + +/* + * + * FILTER + * + */ +Metamaps.Filter = { + filters: { + name: "", + metacode: [], + mappers: [], + synapseTypes: [] + }, + isOpen: false, + timeOut: null, + changing: false, + init: function () { + var self = Metamaps.Filter; + + $(".sidebarFilter").hover(self.open, self.close); + + // initialize scroll bar for filter by metacode, then hide it and position it correctly again + $("#filter_by_metacode").mCustomScrollbar({ + mouseWheelPixels: 200, + advanced: { + updateOnContentResize: true + } + }); + $('.sidebarFilterBox').hide().css({ + position: 'absolute', + top: '45px', + right: '-36px' + }); + + $('.sidebarFilterBox .showAll').click(self.filterNoMetacodes); + $('.sidebarFilterBox .hideAll').click(self.filterAllMetacodes); + + // toggle visibility of topics with metacodes based on status in the filters list + $('#filter_by_metacode ul li').click(self.toggleMetacode); + }, + open: function () { + var self = Metamaps.Filter; + + clearTimeout(self.timeOut); + if (!self.isOpen && !self.changing) { + self.changing = true; + + $('.sidebarFilterBox').fadeIn(200, function () { + self.changing = false; + self.isOpen = true; + }); + } + }, + close: function () { + var self = Metamaps.Filter; + + self.timeOut = setTimeout(function () { + if (!self.changing) { + self.changing = true; + + $('.sidebarFilterBox').fadeOut(200, function () { + self.changing = false; + self.isOpen = false; + }); + } + }, 500); + }, + filterNoMetacodes: function (e) { + + $('#filter_by_metacode ul li').removeClass('toggledOff'); + + // TODO + /* + showAll(); + + for (var catVis in categoryVisible) { + categoryVisible[catVis] = true; + } + */ + }, + filterAllMetacodes: function (e) { + + $('#filter_by_metacode ul li').addClass('toggledOff'); + + // TODO + /* + hideAll(); + for (var catVis in categoryVisible) { + categoryVisible[catVis] = false; + } + */ + }, + toggleMetacode: function () { + + var category = $(this).children('img').attr('alt'); + + // TODO + /*switchVisible(category); + + // toggle the image and the boolean array value + if (categoryVisible[category] == true) { + $(this).addClass('toggledOff'); + categoryVisible[category] = false; + } else if (categoryVisible[category] == false) { + $(this).removeClass('toggledOff'); + categoryVisible[category] = true; + }*/ + }, + passFilters: function (topic) { + var self = Metamaps.Find; + var filters = self.filters; + + var passesName = filters.name == "" ? true : false, + passesType = filters.type == [] ? true : false; + + //filter by name + if (topic.get('1')[1][0].toLowerCase().indexOf(filters.name) !== -1) { + passesName = true; + } + // filter by type + if (!filters.type == []) { + // get the array of types that your topic 'is' + var metacodes = topic.get('2') ? topic.get('2')[1] : []; + if (_.intersection(filters.type, metacodes).length == 0) passesType = true; + } + + if (passesName && passesType) { + return true; + } else { + return false; + } + + + } +}; // end Metamaps.Filter + + +/* + * + * LISTENERS + * + */ +Metamaps.Listeners = { + + init: function () { + + $(document).on('keydown', function (e) { + switch (e.which) { + case 13: + Metamaps.JIT.enterKeyHandler(); + e.preventDefault(); + break; + case 27: + Metamaps.JIT.escKeyHandler(); + break; + default: + break; //alert(e.which); + } + }); + + //$(window).resize(function () { + // Metamaps.Visualize.mGraph.canvas.resize($(window).width(), $(window).height()); + //}); + } +}; // end Metamaps.Listeners + + +/* + * + * ORGANIZE + * + */ +Metamaps.Organize = { + init: function () { + + }, + arrange: function (layout, centerNode) { + + + // first option for layout to implement is 'grid', will do an evenly spaced grid with its center at the 0,0 origin + if (layout == 'grid') { + var numNodes = _.size(Metamaps.Visualize.mGraph.graph.nodes); // this will always be an integer, the # of nodes on your graph visualization + var numColumns = Math.floor(Math.sqrt(numNodes)); // the number of columns to make an even grid + var GRIDSPACE = 400; + var row = 0; + var column = 0; + Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + if (column == numColumns) { + column = 0; + row += 1; + } + var newPos = new $jit.Complex(); + newPos.x = column * GRIDSPACE; + newPos.y = row * GRIDSPACE; + n.setPos(newPos, 'end'); + column += 1; + }); + Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout); + } else if (layout == 'grid_full') { + + // this will always be an integer, the # of nodes on your graph visualization + var numNodes = _.size(Metamaps.Visualize.mGraph.graph.nodes); + //var numColumns = Math.floor(Math.sqrt(numNodes)); // the number of columns to make an even grid + //var GRIDSPACE = 400; + var height = Metamaps.Visualize.mGraph.canvas.getSize(0).height; + var width = Metamaps.Visualize.mGraph.canvas.getSize(0).width; + var totalArea = height * width; + var cellArea = totalArea / numNodes; + var ratio = height / width; + var cellWidth = sqrt(cellArea / ratio); + var cellHeight = cellArea / cellWidth; + var row = floor(height / cellHeight); + var column = floor(width / cellWidth); + var totalCells = row * column; + + if (totalCells) + Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + if (column == numColumns) { + column = 0; + row += 1; + } + var newPos = new $jit.Complex(); + newPos.x = column * GRIDSPACE; + newPos.y = row * GRIDSPACE; + n.setPos(newPos, 'end'); + column += 1; + }); + Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout); + } else if (layout == 'radial') { + + var centerX = centerNode.getPos().x; + var centerY = centerNode.getPos().y; + centerNode.setPos(centerNode.getPos(), 'end'); + + console.log(centerNode.adjacencies); + var lineLength = 200; + var usedNodes = {}; + usedNodes[centerNode.id] = centerNode; + var radial = function (node, level, degree) { + if (level == 1) { + var numLinksTemp = _.size(node.adjacencies); + var angleTemp = 2 * Math.PI / numLinksTemp; + } else { + angleTemp = 2 * Math.PI / 20 + }; + node.eachAdjacency(function (a) { + var isSecondLevelNode = (centerNode.adjacencies[a.nodeTo.id] != undefined && level > 1); + if (usedNodes[a.nodeTo.id] == undefined && !isSecondLevelNode) { + var newPos = new $jit.Complex(); + newPos.x = level * lineLength * Math.sin(degree) + centerX; + newPos.y = level * lineLength * Math.cos(degree) + centerY; + a.nodeTo.setPos(newPos, 'end'); + usedNodes[a.nodeTo.id] = a.nodeTo; + + radial(a.nodeTo, level + 1, degree); + degree += angleTemp; + }; + }); + }; + radial(centerNode, 1, 0); + Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout); + + } else if (layout == 'center_viewport') { + + var lowX = 0, + lowY = 0, + highX = 0, + highY = 0; + var oldOriginX = Metamaps.Visualize.mGraph.canvas.translateOffsetX; + var oldOriginY = Metamaps.Visualize.mGraph.canvas.translateOffsetY; + + Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + if (n.id === 1) { + lowX = n.getPos().x; + lowY = n.getPos().y; + highX = n.getPos().x; + highY = n.getPos().y; + }; + if (n.getPos().x < lowX) lowX = n.getPos().x; + if (n.getPos().y < lowY) lowY = n.getPos().y; + if (n.getPos().x > highX) highX = n.getPos().x; + if (n.getPos().y > highY) highY = n.getPos().y; + }); + console.log(lowX, lowY, highX, highY); + var newOriginX = (lowX + highX) / 2; + var newOriginY = (lowY + highY) / 2; + + } else alert('please call function with a valid layout dammit!'); + }, + loadSavedLayout: function (id) { + Metamaps.Visualize.computePositions(); + Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout); + }, +}; // end Metamaps.Organize + + +/* + * + * TOPIC + * + */ +Metamaps.Topic = { + // this function is to retrieve a topic JSON object from the database + // @param id = the id of the topic to retrieve + get: function (id, callback) { + // if the desired topic is not yet in the local topic repository, fetch it + if (Metamaps.Topics.get(id) == undefined) { + //console.log("Ajax call!"); + if (!callback) { + var e = $.ajax({ + url: "/topics/" + id + ".json", + async: false + }); + Metamaps.Topics.add($.parseJSON(e.responseText)); + return Metamaps.Topics.get(id); + } else { + return $.ajax({ + url: "/topics/" + id + ".json", + success: function (data) { + Metamaps.Topics.add(data); + callback(Metamaps.Topics.get(id)); + } + }); + } + } else { + if (!callback) { + return Metamaps.Topics.get(id); + } else { + return callback(Metamaps.Topics.get(id)); + } + } + }, + + /* + * + * + */ + renderTopic: function (mapping, topic, createNewInDB) { + var self = Metamaps.Topic; + + var nodeOnViz, tempPos; + + var newnode = topic.createNode(); + + if (!$.isEmptyObject(Metamaps.Visualize.mGraph.graph.nodes)) { + Metamaps.Visualize.mGraph.graph.addNode(newnode); + Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + n.setData("dim", 25, "start"); + n.setData("dim", 25, "end"); + }); + nodeOnViz = Metamaps.Visualize.mGraph.graph.getNode(newnode.id); + topic.set('node', nodeOnViz); + topic.updateNode(); // links the topic and the mapping to the node + + + nodeOnViz.setData("dim", 1, "start"); + nodeOnViz.setData("dim", 25, "end"); + if (Metamaps.Visualize.type === "RGraph") { + tempPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')); + tempPos = tempPos.toPolar(); + nodeOnViz.setPos(tempPos, "current"); + nodeOnViz.setPos(tempPos, "start"); + nodeOnViz.setPos(tempPos, "end"); + } else if (Metamaps.Visualize.type === "ForceDirected") { + nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "current"); + nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "start"); + nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "end"); + } + if (Metamaps.Create.newTopic.addSynapse) { + Metamaps.Create.newSynapse.topic1id = tempNode.id; + Metamaps.Create.newSynapse.topic2id = nodeOnViz.id; + Metamaps.Create.newSynapse.open(); + Metamaps.Visualize.mGraph.fx.animate({ + modes: ["node-property:dim"], + duration: 500, + onComplete: function () { + tempNode = null; + tempNode2 = null; + tempInit = false; + } + }); + } else { + Metamaps.Visualize.mGraph.fx.plotNode(nodeOnViz, Metamaps.Visualize.mGraph.canvas); + Metamaps.Visualize.mGraph.fx.animate({ + modes: ["node-property:dim"], + duration: 500, + onComplete: function () { + + } + }); + } + } else { + Metamaps.Visualize.mGraph.loadJSON(newnode); + nodeOnViz = Metamaps.Visualize.mGraph.graph.getNode(newnode.id); + topic.set('node', nodeOnViz); + topic.updateNode(); // links the topic and the mapping to the node + + nodeOnViz.setData("dim", 1, "start"); + nodeOnViz.setData("dim", 25, "end"); + nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "current"); + nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "start"); + nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "end"); + Metamaps.Visualize.mGraph.fx.plotNode(nodeOnViz, Metamaps.Visualize.mGraph.canvas); + Metamaps.Visualize.mGraph.fx.animate({ + modes: ["node-property:dim"], + duration: 500, + onComplete: function () { + + } + }); + } + + if (!Metamaps.Settings.sandbox && createNewInDB) { + if (topic.isNew()) { + topic.save(null, { + success: function (topicModel, response) { + topicModel.updateMapping(); + if (Metamaps.Active.Map) { + mapping.save(); + } + }, + error: function (model, response) { + console.log('error saving topic to database'); + } + }); + } else if (!topic.isNew() && Metamaps.Active.Map) { + mapping.save(); + } + } + }, + createTopicLocally: function () { + var self = Metamaps.Topic; + + var metacode = Metamaps.Metacodes.findWhere({ + name: Metamaps.Create.newTopic.metacode + }); + + var topic = new Metamaps.Backbone.Topic({ + name: Metamaps.Create.newTopic.name, + metacode_id: metacode.id + }); + Metamaps.Topics.add(topic); + + var mapping = new Metamaps.Backbone.Mapping({ + category: "Topic", + xloc: Metamaps.Create.newTopic.x, + yloc: Metamaps.Create.newTopic.y, + topic_id: topic.cid + }); + Metamaps.Mappings.add(mapping); + + //these can't happen until the value is retrieved, which happens in the line above + Metamaps.Create.newTopic.hide(); + + self.renderTopic(mapping, topic, true); // this function also includes the creation of the topic in the database + }, + getTopicFromAutocomplete: function (id) { + var self = Metamaps.Topic; + + Metamaps.Create.newTopic.hide(); + + var topic = self.get(id); + + var mapping = new Metamaps.Backbone.Mapping({ + category: "Topic", + xloc: Metamaps.Create.newTopic.x, + yloc: Metamaps.Create.newTopic.y, + topic_id: topic.id + }); + Metamaps.Mappings.add(mapping); + + self.renderTopic(mapping, topic, false); + } +}; // end Metamaps.Topic + + +/* + * + * SYNAPSE + * + */ +Metamaps.Synapse = { + // this function is to retrieve a synapse JSON object from the database + // @param id = the id of the synapse to retrieve + get: function (id, callback) { + // if the desired topic is not yet in the local topic repository, fetch it + if (Metamaps.Synapses.get(id) == undefined) { + if (!callback) { + var e = $.ajax({ + url: "/synapses/" + id + ".json", + async: false + }); + Metamaps.Synapses.add($.parseJSON(e.responseText)); + return Metamaps.Synapses.get(id); + } else { + return $.ajax({ + url: "/synapses/" + id + ".json", + success: function (data) { + Metamaps.Synapses.add(data); + callback(Metamaps.Synapses.get(id)); + } + }); + } + } else { + if (!callback) { + return Metamaps.Synapses.get(id); + } else { + return callback(Metamaps.Synapses.get(id)); + } + } + }, + /* + * + * + */ + renderSynapse: function (mapping, synapse, node1, node2, createNewInDB) { + var self = Metamaps.Synapse; + + var edgeOnViz; + + var newedge = synapse.createEdge(); + + Metamaps.Visualize.mGraph.graph.addAdjacence(node1, node2, newedge.data); + edgeOnViz = Metamaps.Visualize.mGraph.graph.getAdjacence(node1.id, node2.id); + synapse.set('edge', edgeOnViz); + synapse.updateEdge(); // links the topic and the mapping to the node + + Metamaps.Visualize.mGraph.fx.plotLine(edgeOnViz, Metamaps.Visualize.mGraph.canvas); + Metamaps.Control.selectEdge(edgeOnViz); + + if (!Metamaps.Settings.sandbox && createNewInDB) { + if (synapse.isNew()) { + synapse.save(null, { + success: function (synapseModel, response) { + synapseModel.updateMapping(); + if (Metamaps.Active.Map) { + mapping.save(); + } + }, + error: function (model, response) { + console.log('error saving synapse to database'); + } + }); + } else if (!synapse.isNew() && Metamaps.Active.Map) { + mapping.save(); + } + } + }, + createSynapseLocally: function () { + var self = Metamaps.Synapse, + topic1, + topic2, + node1, + node2, + synapse, + mapping; + + //for each node in this array we will create a synapse going to the position2 node. + var synapsesToCreate = []; + + node2 = Metamaps.Visualize.mGraph.graph.getNode(Metamaps.Create.newSynapse.topic2id); + topic2 = node2.getData('topic'); + + var len = Metamaps.Selected.Nodes.length; + if (len == 0) { + synapsesToCreate[0] = Metamaps.Visualize.mGraph.graph.getNode(Metamaps.Create.newSynapse.topic1id); + } else if (len > 0) { + synapsesToCreate = Metamaps.Selected.Nodes; + } + + for (var i = 0; i < synapsesToCreate.length; i++) { + node1 = synapsesToCreate[i]; + topic1 = node1.getData('topic'); + synapse = new Metamaps.Backbone.Synapse({ + desc: Metamaps.Create.newSynapse.description, + node1_id: topic1.isNew() ? topic1.cid : topic1.id, + node2_id: topic2.isNew() ? topic2.cid : topic2.id, + }); + Metamaps.Synapses.add(synapse); + + mapping = new Metamaps.Backbone.Mapping({ + category: "Synapse", + synapse_id: synapse.cid + }); + Metamaps.Mappings.add(mapping); + + // this function also includes the creation of the synapse in the database + self.renderSynapse(mapping, synapse, node1, node2, true); + } // for each in synapsesToCreate + + Metamaps.Create.newSynapse.hide(); + }, + getSynapseFromAutocomplete: function (id) { + var self = Metamaps.Synapse, + node1, + node2; + + Metamaps.Create.newSynapse.hide(); + + var synapse = self.get(id); + + var mapping = new Metamaps.Backbone.Mapping({ + category: "Synapse", + synapse_id: synapse.id + }); + Metamaps.Mappings.add(mapping); + + node1 = Metamaps.Visualize.mGraph.graph.getNode(Metamaps.Create.newSynapse.topic1id); + node2 = Metamaps.Visualize.mGraph.graph.getNode(Metamaps.Create.newSynapse.topic2id); + + self.renderSynapse(mapping, synapse, node1, node2, false); + } +}; // end Metamaps.Synapse + + +/* + * + * MAP + * + */ +Metamaps.Map = { + init: function () { + var self = Metamaps.Map; + + // prevent right clicks on the main canvas, so as to not get in the way of our right clicks + $('#center-container').bind('contextmenu', function (e) { + return false; + }); + + $('.sidebarFork').click(function () { + self.fork(); + }); + + Metamaps.GlobalUI.CreateMap.emptyForkMapForm = $('#fork_map').html(); + + self.InfoBox.init(); + self.CheatSheet.init(); + }, + // this function is to retrieve a map JSON object from the database + // @param id = the id of the map to retrieve + get: function (id, callback) { + // if the desired topic is not yet in the local topic repository, fetch it + if (Metamaps.Maps.get(id) == undefined) { + if (!callback) { + var e = $.ajax({ + url: "/maps/" + id + ".json", + async: false + }); + Metamaps.Maps.add($.parseJSON(e.responseText)); + return Metamaps.Maps.get(id); + } else { + return $.ajax({ + url: "/users/" + id + ".json", + success: function (data) { + Metamaps.Maps.add(data); + callback(Metamaps.Maps.get(id)); + } + }); + } + } else { + if (!callback) { + return Metamaps.Maps.get(id); + } else { + return callback(Metamaps.Maps.get(id)); + } + } + }, + fork: function () { + Metamaps.GlobalUI.openLightbox('forkmap'); + + var nodes_data = "", + synapses_data = ""; + var synapses_array = new Array(); + Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + //don't add to the map if it was filtered out + // TODO + //if (categoryVisible[n.getData('metacode')] == false) { + // return; + //} + + var x, y; + if (n.pos.x && n.pos.y) { + x = n.pos.x; + y = n.pos.y; + } else { + var x = Math.cos(n.pos.theta) * n.pos.rho; + var y = Math.sin(n.pos.theta) * n.pos.rho; + } + nodes_data += n.id + '/' + x + '/' + y + ','; + n.eachAdjacency(function (adj) { + synapses_array.push(adj.getData("synapses")[0].id); // TODO + }); + }); + + //get unique values only + synapses_array = $.grep(synapses_array, function (value, key) { + return $.inArray(value, synapses_array) === key; + }); + + synapses_data = synapses_array.join(); + nodes_data = nodes_data.slice(0, -1); + + Metamaps.GlobalUI.CreateMap.topicsToMap = nodes_data; + Metamaps.GlobalUI.CreateMap.synapsesToMap = synapses_data; + } +}; + + +/* + * + * CHEATSHEET + * + */ +Metamaps.Map.CheatSheet = { + init: function () { + // tab the cheatsheet + $('#cheatSheet').tabs().addClass("ui-tabs-vertical ui-helper-clearfix"); + $("#cheatSheet .ui-tabs-nav li").removeClass("ui-corner-top").addClass("ui-corner-left"); + } +}; // end Metamaps.Map.CheatSheet + + +/* + * + * INFOBOX + * + */ +Metamaps.Map.InfoBox = { + isOpen: false, + timeOut: null, + changing: false, + selectingPermission: false, + init: function () { + var self = Metamaps.Map.InfoBox; + + // because anyone who can edit the map can change the map title + $('.mapInfoName .best_in_place_name').bind("ajax:success", function () { + var name = $(this).html(); + $('.mapName').html(name); + Metamaps.Active.Map.set('name', name); + }); + + $('.yourMap .mapPermission').click(self.onPermissionClick); + + $("div.index").hover(self.open, self.close); + }, + open: function (event) { + var self = Metamaps.GlobalUI.Account; + + clearTimeout(self.timeOut); + if (!self.isOpen && !self.changing && event.target.className != "openCheatsheet openLightbox") { + self.changing = true; + $('.mapInfoBox').fadeIn(200, function () { + self.changing = false; + self.isOpen = true; + }); + } + }, + close: function () { + var self = Metamaps.GlobalUI.Account; + + self.timeOut = setTimeout(function () { + if (!self.changing) { + self.changing = true; + $('.mapInfoBox').fadeOut(200, function () { + self.changing = false; + self.isOpen = false; + }); + } + }, 500); + }, + onPermissionClick: function () { + var self = Metamaps.Map.InfoBox; + + if (!self.selectingPermission) { + self.selectingPermission = true; + $(this).addClass('minimize'); // this line flips the drop down arrow to a pull up arrow + if ($(this).hasClass('commons')) { + $(this).append('
    '); + } else if ($(this).hasClass('public')) { + $(this).append('
    '); + } else if ($(this).hasClass('private')) { + $(this).append('
    '); + } + $('.mapPermission .permissionSelect li').click(self.selectPermission); + } else { + self.selectingPermission = false; + $(this).removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow + $('.mapPermission .permissionSelect').remove(); + } + }, + selectPermission: function () { + var self = Metamaps.Map.InfoBox; + + self.selectingPermission = false; + var permission = $(this).attr('class'); + Metamaps.Active.Map.save({ + permission: permission + }); + $('.mapPermission').removeClass('commons public private minimize').addClass(permission); + $('.mapPermission .permissionSelect').remove(); + event.stopPropagation(); + } +}; // end Metamaps.Map.InfoBox + + +/* + * + * MAPPER + * + */ +Metamaps.Mapper = { + // this function is to retrieve a mapper JSON object from the database + // @param id = the id of the mapper to retrieve + get: function (id, callback) { + // if the desired topic is not yet in the local topic repository, fetch it + if (Metamaps.Mappers.get(id) == undefined) { + if (!callback) { + var e = $.ajax({ + url: "/users/" + id + ".json", + async: false + }); + Metamaps.Mappers.add($.parseJSON(e.responseText)); + return Metamaps.Mappers.get(id); + } else { + return $.ajax({ + url: "/users/" + id + ".json", + success: function (data) { + Metamaps.Mappers.add(data); + callback(Metamaps.Mappers.get(id)); + } + }); + } + } else { + if (!callback) { + return Metamaps.Mappers.get(id); + } else { + return callback(Metamaps.Mappers.get(id)); + } + } + }, +}; // end Metamaps.Mapper diff --git a/app/assets/javascripts/metamaps/Metamaps.js~ b/app/assets/javascripts/metamaps/Metamaps.js~ new file mode 100644 index 00000000..cc796c14 --- /dev/null +++ b/app/assets/javascripts/metamaps/Metamaps.js~ @@ -0,0 +1,2711 @@ +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); +})(); + +// TODO eliminate these 4 global variables +var panningInt; // this variable is used to store a 'setInterval' for the Metamaps.JIT.SmoothPanning() function, so that it can be cleared with window.clearInterval +var tempNode = null, + tempInit = false, + tempNode2 = null; + +Metamaps.Settings = { + embed: false, // indicates that the app is on a page that is optimized for embedding in iFrames on other web pages + sandbox: false, // puts the app into a mode (when true) where it only creates data locally, and isn't writing it to the database + colors: { + background: '#344A58', + synapses: { + normal: '#222222', + hover: '#222222', + selected: '#FFFFFF' + }, + topics: { + selected: '#FFFFFF' + }, + labels: { + background: '#18202E', + text: '#DDD' + } + } +}; + +Metamaps.Touch = { + touchPos: null, // this stores the x and y values of a current touch event + touchDragNode: null // this stores a reference to a JIT node that is being dragged +}; + +Metamaps.Mouse = { + didPan: false, + changeInX: 0, + changeInY: 0, + edgeHoveringOver: false, + boxStartCoordinates: false, + boxEndCoordinates: false, + synapseStartCoordinates: [], + synapseEndCoordinates: null, + lastNodeClick: 0, + lastCanvasClick: 0, + DOUBLE_CLICK_TOLERANCE: 300 +}; + +Metamaps.Selected = { + Nodes: [], + Edges: [] +}; + +Metamaps.Metacodes = {}; // will be initialized in Metamaps.Backbone.init as a MetacodeCollection +Metamaps.Topics = {}; // will be initialized in Metamaps.Backbone.init as a TopicCollection +Metamaps.Synapses = {}; // will be initialized in Metamaps.Backbone.init as a SynapseCollection +Metamaps.Mappings = {}; // will be initialized in Metamaps.Backbone.init as a MappingCollection + + +/* + * + * BACKBONE + * + */ +Metamaps.Backbone.init = function () { + var self = Metamaps.Backbone; + + self.Metacode = Backbone.Model.extend({ + initialize: function () { + var image = new Image(); + image.src = this.get('icon'); + this.set('image',image); + } + }); + self.MetacodeCollection = Backbone.Collection.extend({ + model: this.Metacode, + url: '/metacodes', + }); + + self.Topic = Backbone.Model.extend({ + urlRoot: '/topics', + blacklist: ['node', 'created_at', 'updated_at'], + toJSON: function (options) { + return _.omit(this.attributes, this.blacklist); + }, + initialize: function () { + if (this.isNew()) { + this.set({ + "user_id": Metamaps.Active.Mapper.id, + "desc": '', + "link": '', + "permission": Metamaps.Active.Map ? Metamaps.Active.Map.get('permission') : 'commons' + }); + } + }, + authorizeToEdit: function (mapper) { + if (mapper && (this.get('permission') === "commons" || this.get('user_id') === mapper.get('id'))) return true; + else return false; + }, + authorizePermissionChange: function (mapper) { + if (mapper && this.get('user_id') === mapper.get('id')) return true; + else return false; + }, + getDate: function () { + + }, + getUser: function () { + return Metamaps.Mapper.get(this.get('user_id')); + }, + getMetacode: function () { + return Metamaps.Metacodes.get(this.get('metacode_id')); + }, + getMapping: function () { + + if (!Metamaps.Active.Map) return false; + + return Metamaps.Mappings.findWhere({ + map_id: Metamaps.Active.Map.id, + topic_id: this.isNew() ? this.cid : this.id + }); + }, + updateMapping: function () { + var mapping = this.getMapping(); + + if (mapping) { + mapping.set('topic_id', this.id); + } + }, + createNode: function () { + var mapping; + var node = { + adjacencies: [], + id: this.isNew() ? this.cid : this.id, + name: this.get('name') + }; + + if (Metamaps.Active.Map) { + mapping = this.getMapping(); + node.data = { + $mapping: null, + $mappingID: mapping.id + }; + } + + return node; + }, + updateNode: function () { + var mapping; + var node = this.get('node'); + node.setData('topic', this); + node.id = this.isNew() ? this.cid : this.id; + + if (Metamaps.Active.Map) { + mapping = this.getMapping(); + node.setData('mapping', mapping); + } + + return node; + }, + }); + + self.TopicCollection = Backbone.Collection.extend({ + model: self.Topic, + url: '/topics', + comparator: function (a, b) { + a = a.get('name').toLowerCase(); + b = b.get('name').toLowerCase(); + return a > b ? 1 : a < b ? -1 : 0; + } + }); + + self.Synapse = Backbone.Model.extend({ + urlRoot: '/synapses', + blacklist: ['edge', 'created_at', 'updated_at'], + toJSON: function (options) { + return _.omit(this.attributes, this.blacklist); + }, + initialize: function () { + if (this.isNew()) { + this.set({ + "user_id": Metamaps.Active.Mapper.id, + "permission": Metamaps.Active.Map ? Metamaps.Active.Map.get('permission') : 'commons', + "category": "from-to" + }); + } + }, + authorizeToEdit: function (mapper) { + if (mapper && (this.get('permission') === "commons" || this.get('user_id') === mapper.get('id'))) return true; + else return false; + }, + authorizePermissionChange: function (mapper) { + if (mapper && this.get('user_id') === mapper.get('id')) return true; + else return false; + }, + getUser: function () { + return Metamaps.Mapper.get(this.get('user_id')); + }, + getTopic1: function () { + return Metamaps.Topic.get(this.get('node1_id')); + }, + getTopic2: function () { + return Metamaps.Topic.get(this.get('node2_id')); + }, + getDirection: function () { + return [ + this.get('node1_id'), + this.get('node2_id') + ]; + }, + getMapping: function () { + + if (!Metamaps.Active.Map) return false; + + return Metamaps.Mappings.findWhere({ + map_id: Metamaps.Active.Map.id, + synapse_id: this.isNew() ? this.cid : this.id + }); + }, + updateMapping: function () { + var mapping = this.getMapping(); + + if (mapping) { + mapping.set('synapse_id', this.id); + } + }, + createEdge: function () { + var mapping, mappingID; + var synapseID = this.isNew() ? this.cid : this.id; + + var edge = { + nodeFrom: this.get('node1_id'), + nodeTo: this.get('node2_id'), + data: { + $synapses: [], + $synapseIDs: [synapseID], + } + }; + + if (Metamaps.Active.Map) { + mapping = this.getMapping(); + mappingID = mapping.isNew() ? mapping.cid : mapping.id; + edge.data.$mappings = []; + edge.data.$mappingIDs = [mappingID]; + } + + return edge; + }, + updateEdge: function () { + var mapping; + var edge = this.get('edge'); + edge.getData('synapses').push(this); + + if (Metamaps.Active.Map) { + mapping = this.getMapping(); + edge.getData('mappings').push(mapping); + } + + return edge; + }, + }); + + self.SynapseCollection = Backbone.Collection.extend({ + model: self.Synapse, + url: '/synapses' + }); + + self.Mapping = Backbone.Model.extend({ + urlRoot: '/mappings', + blacklist: ['created_at', 'updated_at'], + toJSON: function (options) { + return _.omit(this.attributes, this.blacklist); + }, + initialize: function () { + if (this.isNew()) { + this.set({ + "user_id": Metamaps.Active.Mapper.id, + "map_id": Metamaps.Active.Map ? Metamaps.Active.Map.id : null + }); + } + }, + getUser: function () { + return Metamaps.Mapper.get(this.get('user_id')); + }, + getMap: function () { + return Metamaps.Map.get(this.get('map_id')); + }, + getTopic: function () { + if (this.get('category') === 'Topic') return Metamaps.Topic.get(this.get('topic_id')); + else return false; + }, + getSynapse: function () { + if (this.get('category') === 'Synapse') return Metamaps.Synapse.get(this.get('synapse_id')); + else return false; + } + }); + + self.MappingCollection = Backbone.Collection.extend({ + model: self.Mapping, + url: '/mappings' + }); + + Metamaps.Metacodes = new self.MetacodeCollection(Metamaps.Metacodes); + + Metamaps.Topics = new self.TopicCollection(Metamaps.Topics); + + Metamaps.Synapses = new self.SynapseCollection(Metamaps.Synapses); + + Metamaps.Mappings = new self.MappingCollection(Metamaps.Mappings); + + if (Metamaps.Active.Map) { + Metamaps.Active.Map = new self.Map(Metamaps.Active.Map); + Metamaps.Maps.add(Metamaps.Active.Map); + } + + if (Metamaps.Active.Topic) Metamaps.Active.Topic = new self.Topic(Metamaps.Active.Topic); +}; // end Metamaps.Backbone.init + + +/* + * + * CREATE + * + */ +Metamaps.Create = { + isSwitchingSet: false, // indicates whether the metacode set switch lightbox is open + metacodeScrollerInit: false, // indicates whether the scrollbar in the custom metacode set space has been init + selectedMetacodeSet: null, + selectedMetacodeSetIndex: null, + selectedMetacodeNames: [], + newSelectedMetacodeNames: [], + selectedMetacodes: [], + newSelectedMetacodes: [], + init: function () { + var self = Metamaps.Create; + self.newTopic.init(); + self.newSynapse.init(); + + ////// + ////// + //// SWITCHING METACODE SETS + + $('#metacodeSwitchTabs').tabs({ + selected: self.selectedMetacodeSetIndex + }).addClass("ui-tabs-vertical ui-helper-clearfix"); + $("#metacodeSwitchTabs .ui-tabs-nav li").removeClass("ui-corner-top").addClass("ui-corner-left"); + $('.customMetacodeList li').click(self.toggleMetacodeSelected); // within the custom metacode set tab + }, + toggleMetacodeSelected: function () { + var self = Metamaps.Create; + + if ($(this).attr('class') != 'toggledOff') { + $(this).addClass('toggledOff'); + var value_to_remove = $(this).attr('id'); + var name_to_remove = $(this).attr('data-name'); + self.newSelectedMetacodes.splice(self.newSelectedMetacodes.indexOf(value_to_remove), 1); + self.newSelectedMetacodeNames.splice(self.newSelectedMetacodeNames.indexOf(name_to_remove), 1); + } else if ($(this).attr('class') == 'toggledOff') { + $(this).removeClass('toggledOff'); + self.newSelectedMetacodes.push($(this).attr('id')); + self.newSelectedMetacodeNames.push($(this).attr('data-name')); + } + }, + updateMetacodeSet: function (set, index, custom) { + + if (custom && Metamaps.Create.newSelectedMetacodes.length == 0) { + alert('Please select at least one metacode to use!'); + return false; + } + + var codesToSwitchTo; + Metamaps.Create.selectedMetacodeSetIndex = index; + Metamaps.Create.selectedMetacodeSet = "metacodeset-" + set; + + if (!custom) { + codesToSwitchTo = $('#metacodeSwitchTabs' + set).attr('data-metacodes').split(','); + $('.customMetacodeList li').addClass('toggledOff'); + Metamaps.Create.selectedMetacodes = []; + Metamaps.Create.selectedMetacodeNames = []; + Metamaps.Create.newSelectedMetacodes = []; + Metamaps.Create.newSelectedMetacodeNames = []; + } + if (custom) { + // uses .slice to avoid setting the two arrays to the same actual array + Metamaps.Create.selectedMetacodes = Metamaps.Create.newSelectedMetacodes.slice(0); + Metamaps.Create.selectedMetacodeNames = Metamaps.Create.newSelectedMetacodeNames.slice(0); + codesToSwitchTo = Metamaps.Create.selectedMetacodeNames.slice(0); + } + + // sort by name + codesToSwitchTo.sort(); + codesToSwitchTo.reverse(); + + $('#metacodeImg, #metacodeImgTitle').empty(); + $('#metacodeImg').removeData('cloudcarousel'); + var newMetacodes = ""; + var metacode; + for (var i = 0; i < codesToSwitchTo.length; i++) { + metacode = Metamaps.Metacodes.findWhere({ name: codesToSwitchTo[i] }); + newMetacodes += '' + metacode.get('name') + ''; + }; + $('#metacodeImg').empty().append(newMetacodes).CloudCarousel({ + titleBox: $('#metacodeImgTitle'), + yRadius: 40, + xPos: 150, + yPos: 40, + speed: 0.3, + mouseWheel: true, + bringToFront: true + }); + + Metamaps.GlobalUI.closeLightbox(); + $('#topic_name').focus(); + + var mdata = { + "metacodes": { + "value": custom ? Metamaps.Create.selectedMetacodes.toString() : Metamaps.Create.selectedMetacodeSet + } + }; + $.ajax({ + type: "POST", + dataType: 'json', + url: "/user/updatemetacodes", + data: mdata, + success: function (data) { + console.log('selected metacodes saved'); + }, + error: function () { + console.log('failed to save selected metacodes'); + } + }); + }, + + cancelMetacodeSetSwitch: function () { + var self = Metamaps.Create; + self.isSwitchingSet = false; + + if (self.selectedMetacodeSet != "metacodeset-custom") { + $('.customMetacodeList li').addClass('toggledOff'); + self.selectedMetacodes = []; + self.selectedMetacodeNames = []; + self.newSelectedMetacodes = []; + self.newSelectedMetacodeNames = []; + } else { // custom set is selected + // reset it to the current actual selection + $('.customMetacodeList li').addClass('toggledOff'); + for (var i = 0; i < self.selectedMetacodes.length; i++) { + $('#' + self.selectedMetacodes[i]).removeClass('toggledOff'); + }; + // uses .slice to avoid setting the two arrays to the same actual array + self.newSelectedMetacodeNames = self.selectedMetacodeNames.slice(0); + self.newSelectedMetacodes = self.selectedMetacodes.slice(0); + } + $('#metacodeSwitchTabs').tabs("select", self.selectedMetacodeSetIndex); + $('#topic_name').focus(); + }, + newTopic: { + init: function () { + $('#new_topic').bind('contextmenu', function (e) { + return false; + }); + + $('#topic_name').keyup(function () { + Metamaps.Create.newTopic.name = $(this).val(); + }); + + // initialize the autocomplete results for the metacode spinner + $('#topic_name').typeahead([ + { + name: 'topic_autocomplete', + limit: 8, + template: $('#topicAutocompleteTemplate').html(), + remote: { + url: '/topics/autocomplete_topic?term=%QUERY' + }, + engine: Hogan + } + ]); + + // tell the autocomplete to submit the form with the topic you clicked on if you pick from the autocomplete + $('#topic_name').bind('typeahead:selected', function (event, datum, dataset) { + Metamaps.Topic.getTopicFromAutocomplete(datum.id); + }); + + // initialize metacode spinner and then hide it + $("#metacodeImg").CloudCarousel({ + titleBox: $('#metacodeImgTitle'), + yRadius: 40, + xPos: 150, + yPos: 40, + speed: 0.3, + mouseWheel: true, + bringToFront: true + }); + $('.new_topic').hide(); + }, + name: null, + newId: 1, + beingCreated: false, + metacode: null, + x: null, + y: null, + addSynapse: false, + open: function () { + $('#new_topic').fadeIn('fast', function () { + $('#topic_name').focus(); + }); + Metamaps.Create.newTopic.beingCreated = true; + }, + hide: function () { + $('#new_topic').fadeOut('fast'); + $("#topic_name").typeahead('setQuery', ''); + Metamaps.Create.newTopic.beingCreated = false; + } + }, + newSynapse: { + init: function () { + var self = Metamaps.Create.newSynapse; + + // keep the right click menu from opening + $('#new_synapse').bind('contextmenu', function (e) { + return false; + }); + + $('#synapse_desc').keyup(function () { + Metamaps.Create.newSynapse.description = $(this).val(); + }); + + // initialize the autocomplete results for synapse creation + $('#synapse_desc').typeahead([ + { + name: 'synapse_autocomplete', + template: "
    {{label}}
    ", + remote: { + url: '/search/synapses?term=%QUERY' + }, + engine: Hogan + }, + { + name: 'existing_synapses', + limit: 50, + template: $('#synapseAutocompleteTemplate').html(), + remote: { + url: '/search/synapses', + replace: function () { + return self.getSearchQuery(); + } + }, + engine: Hogan, + header: "

    Existing Synapses

    " + } + ]); + + $('#synapse_desc').bind('typeahead:selected', function (event, datum, dataset) { + if (datum.id) { // if they clicked on an existing synapse get it + Metamaps.Synapse.getSynapseFromAutocomplete(datum.id); + } + }); + }, + beingCreated: false, + description: null, + topic1id: null, + topic2id: null, + newSynapseId: null, + open: function () { + $('#new_synapse').fadeIn('fast', function () { + $('#synapse_desc').focus(); + }); + Metamaps.Create.newSynapse.beingCreated = true; + }, + hide: function () { + $('#new_synapse').fadeOut('fast'); + $("#synapse_desc").typeahead('setQuery', ''); + Metamaps.Create.newSynapse.beingCreated = false; + Metamaps.Create.newTopic.addSynapse = false; + Metamaps.Create.newSynapse.topic1id = 0; + Metamaps.Create.newSynapse.topic2id = 0; + }, + getSearchQuery: function () { + var self = Metamaps.Create.newSynapse; + + if (Metamaps.Selected.Nodes.length < 2) { + return '/search/synapses?topic1id=' + self.topic1id + '&topic2id=' + self.topic2id; + } else return ''; + } + } +}; // end Metamaps.Create + + +////////////////// TOPIC AND SYNAPSE CARDS ////////////////////////// + + +/* + * + * TOPICCARD + * + */ +Metamaps.TopicCard = { + openTopicCard: null, //stores the JIT local ID of the topic with the topic card open + init: function () { + + // 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" + }); + $('#showcard').resizable({ + maxHeight: 500, + maxWidth: 500, + minHeight: 320, + minWidth: 226, + resize: function (event, ui) { + var p = $('#showcard').find('.scroll'); + p.height(p.height()).mCustomScrollbar('update'); + } + }).css({ + display: 'none', + top: '300px', + left: '100px' + }); + }, + fadeInShowCard: function (topic) { + $('.showcard').fadeIn('fast'); + Metamaps.TopicCard.openTopicCard = topic.isNew() ? topic.cid : topic.id; + }, + /** + * Will open the Topic Card for the node that it's passed + * @param {$jit.Graph.Node} node + */ + showCard: function (node) { + + var topic = node.getData('topic'); + + //populate the card that's about to show with the right topics data + Metamaps.TopicCard.populateShowCard(topic); + Metamaps.TopicCard.fadeInShowCard(topic); + }, + hideCard: function () { + $('.showcard').fadeOut('fast'); + Metamaps.TopicCard.openTopicCard = null; + }, + bindShowCardListeners: function (topic) { + var self = Metamaps.TopicCard; + var showCard = document.getElementById('showcard'); + + 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'); + } + }); + + $('.showcard .metacodeTitle').click(function () { + if (!selectingMetacode) { + selectingMetacode = true; + $(this).addClass('minimize'); // this line flips the drop down arrow to a pull up arrow + $('.metacodeSelect').show(); + // add the scroll bar to the list of metacode select options if it isn't already there + if (!$('.metacodeSelect ul').hasClass('mCustomScrollbar')) { + $('.metacodeSelect ul').mCustomScrollbar({ + mouseWheelPixels: 200, + advanced: { + updateOnContentResize: true + } + }); + + $('.metacodeSelect li').click(function () { + selectingMetacode = false; + var metacodeName = $(this).find('.mSelectName').text(); + var metacode = Metamaps.Metacodes.findWhere({ + name: metacodeName + }); + $('.CardOnGraph').find('.metacodeTitle').text(metacodeName) + .attr('class', 'metacodeTitle mbg' + metacodeName.replace(/\s/g, '')); + $('.CardOnGraph').find('.metacodeImage').css('background-image', 'url(' + metacode.get('icon') + ')'); + topic.save({ + metacode_id: metacode.id + }); + Metamaps.Visualize.mGraph.plot(); + $('.metacodeTitle').removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow + $('.metacodeSelect').hide(); + setTimeout(function () { + $('.metacodeTitle').hide(); + $('.showcard .icon').css('z-index', '1'); + }, 500); + }); + } + } else { + selectingMetacode = false; + $(this).removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow + $('.metacodeSelect').hide(); + } + }); + + + // ability to change permission + var selectingPermission = false; + if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) { + $('.showcard .yourTopic .mapPerm').click(function () { + 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('
    '); + } + $('.permissionSelect li').click(function (event) { + selectingPermission = false; + var permission = $(this).attr('class'); + topic.save({ + permission: permission + }); + $('.showcard .mapPerm').removeClass('co pu pr minimize').addClass(permission.substring(0, 2)); + $('.permissionSelect').remove(); + event.stopPropagation(); + }); + } else { + selectingPermission = false; + $(this).removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow + $('.permissionSelect').remove(); + } + }); + } + + // when you're typing a description, resize the scroll box to have space + $('.best_in_place_desc textarea').bind('keyup', function () { + var s = $('.showcard').find('.scroll'); + s.height(s.height()).mCustomScrollbar('update'); + }); + + //bind best_in_place ajax callbacks + $(showCard).find('.best_in_place_name').bind("ajax:success", function () { + + var s = $('.showcard').find('.scroll'); + s.height(s.height()).mCustomScrollbar('update'); + + var name = $(this).html(); + topic.set("name", Metamaps.Util.decodeEntities(name)); + Metamaps.Visualize.mGraph.plot(); + }); + + $(showCard).find('.best_in_place_desc').bind("ajax:success", function () { + this.innerHTML = this.innerHTML.replace(/\r/g, '') + + var s = $('.showcard').find('.scroll'); + s.height(s.height()).mCustomScrollbar('update'); + + var desc = $(this).html(); + topic.set("desc", desc); + }); + + $(showCard).find('.best_in_place_link').bind("ajax:success", function () { + var link = $(this).html(); + $(showCard).find('.go-link').attr('href', link); + topic.set("link", link); + }); + }, + populateShowCard: function (topic) { + var self = Metamaps.TopicCard; + + var showCard = document.getElementById('showcard'); + + $(showCard).find('.permission').remove(); + + var html = self.generateShowcardHTML.render(self.buildObject(topic)); + + 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 nodeValues = {}; + var authorized = topic.authorizeToEdit(Metamaps.Active.Mapper); + + //link is rendered differently if user is logged out or in + var go_link, a_tag, close_a_tag; + if (!authorized) { + go_link = ''; + if (topic.get("link") != "") { + a_tag = ''; + close_a_tag = ''; + } else { + a_tag = ''; + close_a_tag = ''; + } + } else { + go_link = ''; + a_tag = ''; + close_a_tag = ''; + } + + var desc_nil = "Click to add description..."; + var link_nil = "Click to add link..."; + + nodeValues.permission = topic.get("permission"); + nodeValues.mk_permission = topic.get("permission").substring(0, 2); + //nodeValues.map_count = topic.get("inmaps").length; + //nodeValues.synapse_count = topic.get("synapseCount"); + nodeValues.id = topic.isNew() ? topic.cid : topic.id; + nodeValues.metacode = topic.getMetacode().get("name"); + nodeValues.metacode_class = 'mbg' + topic.getMetacode().get("name").replace(/\s/g, ''); + nodeValues.imgsrc = topic.getMetacode().get("icon"); + nodeValues.name = topic.get("name"); + nodeValues.userid = topic.get("user_id"); + nodeValues.username = topic.getUser().get("name"); + nodeValues.date = topic.getDate(); + + // the code for this is stored in /views/main/_metacodeOptions.html.erb + nodeValues.metacode_select = $('#metacodeOptions').html(); + nodeValues.go_link = go_link; + nodeValues.a_tag = a_tag; + nodeValues.close_a_tag = close_a_tag; + nodeValues.link_nil = link_nil; + nodeValues.link = (topic.get("link") == "" && authorized) ? link_nil : topic.get("link"); + 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 synapse = edge.getData('synapses')[0]; // 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.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'; + } + $('.main .wrapper').append(edit_div); + + self.populateShowCard(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 = synapse.isNew() ? synapse.cid : synapse.id; + }, + + hideCard: function () { + $('#edit_synapse').remove(); + Metamaps.SynapseCard.openSynapseCard = null; + }, + + populateShowCard: function (synapse) { + var self = Metamaps.SynapseCard; + + self.add_name_form(synapse); + self.add_user_info(synapse); + self.add_perms_form(synapse); + if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) { + self.add_direction_form(synapse); + } + }, + + add_name_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 + + //name editing form + $('#edit_synapse').append('
    '); + $('#edit_synapse_name').attr('class', 'best_in_place best_in_place_desc'); + $('#edit_synapse_name').attr('data-object', 'synapse'); + $('#edit_synapse_name').attr('data-attribute', 'desc'); + $('#edit_synapse_name').attr('data-type', 'textarea'); + $('#edit_synapse_name').attr('data-nil', data_nil); + $('#edit_synapse_name').attr('data-url', '/synapses/' + synapse.id); + $('#edit_synapse_name').html(synapse.get("desc")); + + //if edge data is blank or just whitespace, populate it with data_nil + if ($('#edit_synapse_name').html().trim() == '') { + $('#edit_synapse_name').html(data_nil); + } + + $('#edit_synapse_name').bind("ajax:success", function () { + var desc = $(this).html(); + if (desc == data_nil) { + synapse.set("desc", ''); + } else { + synapse.set("desc", desc); + } + Metamaps.Control.selectEdge(synapse.get('edge')); + Metamaps.Visualize.mGraph.plot(); + }); + }, + + add_user_info: function (synapse) { + var u = '
    '; + u += '
    Created by ' + synapse.getUser().get("name") + '
    '; + $('#edit_synapse').append(u); + }, + + add_perms_form: function (synapse) { + //permissions - if owner, also allow permission editing + $('#edit_synapse').append('
    '); + + // ability to change permission + var selectingPermission = false; + if (synapse.authorizePermissionChange(Metamaps.Active.Mapper)) { + $('#edit_synapse.yourEdge .mapPerm').click(function () { + 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(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(); + }); + } else { + selectingPermission = false; + $(this).removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow + $('#edit_synapse .permissionSelect').remove(); + } + }); + } + }, //add_perms_form + + add_direction_form: function (synapse) { + //directionality checkboxes + $('#edit_synapse').append(''); + $('#edit_synapse').append(''); + $('#edit_synapse').append(''); + $('#edit_synapse').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; + var right = edge.nodeFrom; + } else { + var left = edge.nodeFrom; + var right = edge.nodeTo; + } + + /* + * 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.getDirection(); + if (from_to[0] == left.id) { + //check left checkbox + $('#edit_synapse_left').prop('checked', true); + } else { + //check right checkbox + $('#edit_synapse_right').prop('checked', true); + } + } else if (directionCat == 'both') { + //check both checkboxes + $('#edit_synapse_left').prop('checked', true); + $('#edit_synapse_right').prop('checked', true); + } + $('#edit_synapse_left, #edit_synapse_right').click(function () { + 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(); + }); + } //add_direction_form +}; // end Metamaps.SynapseCard + + +////////////////////// END TOPIC AND SYNAPSE CARDS ////////////////////////////////// + + + + +/* + * + * VISUALIZE + * + */ +Metamaps.Visualize = { + mGraph: {}, // 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" + savedLayout: true, // indicates whether the map has a saved layout or not + 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 + target: null, // the selector representing the location to render the graph + 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; + }); + }, + render: function (targetID, vizData) { + var self = Metamaps.Visualize; + self.mGraph = {}; + self.target = targetID; + self.__buildGraph(vizData); + }, + computePositions: function () { + var self = Metamaps.Visualize, + mapping; + + if (self.type == "RGraph") { + self.mGraph.graph.eachNode(function (n) { + topic = Metamaps.Topics.get(n.id); + topic.set('node', n); + topic.updateNode(); + + n.eachAdjacency(function (edge) { + l = edge.getData('synapseIDs').length; + for (i = 0; i < l; i++) { + synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]); + synapse.set('edge', edge); + synapse.updateEdge(); + } + }); + + var pos = n.getPos(); + pos.setc(-200, -200); + }); + self.mGraph.compute('end'); + } else if (self.type == "ForceDirected" && self.savedLayout) { + var i, l, startPos, endPos, topic, synapse; + + self.mGraph.graph.eachNode(function (n) { + topic = Metamaps.Topics.get(n.id); + topic.set('node', n); + topic.updateNode(); + mapping = topic.getMapping(); + + n.eachAdjacency(function (edge) { + l = edge.getData('synapseIDs').length; + for (i = 0; i < l; i++) { + synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]); + synapse.set('edge', edge); + 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.savedLayout) { + self.mGraph.compute(); + } + }, + /** + * __buildGraph does the heavy lifting of creating the engine that renders the graph with the properties we desire + * + * @param vizData a json structure containing the data to be rendered. + */ + __buildGraph: function (vizData) { + var self = Metamaps.Visualize + RGraphSettings = $.extend(true, {}, Metamaps.JIT.ForceDirected.graphSettings); + + if (self.type == "RGraph") { + $jit.RGraph.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings); + $jit.RGraph.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings); + + RGraphSettings.background = Metamaps.JIT.RGraph.background; + RGraphSettings.levelDistance = Metamaps.JIT.RGraph.levelDistance; + + self.mGraph = new $jit.RGraph(RGraphSettings); + } else if (self.type == "ForceDirected") { + $jit.ForceDirected.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings); + $jit.ForceDirected.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings); + self.mGraph = new $jit.ForceDirected(Metamaps.JIT.ForceDirected.graphSettings); + } else if (self.type == "ForceDirected3D") { + // init ForceDirected3D + self.mGraph = new $jit.ForceDirected3D(Metamaps.JIT.ForceDirected3D.graphSettings); + self.cameraPosition = self.mGraph.canvas.canvases[0].camera.position; + } + + // load JSON data, if it's not empty + if (!self.loadLater) { + //load JSON data. + self.mGraph.loadJSON(vizData); + //compute positions and plot. + self.computePositions(); + if (self.type == "RGraph") { + self.mGraph.fx.animate(Metamaps.JIT.RGraph.animate); + } else if (self.type == "ForceDirected" && self.savedLayout) { + Metamaps.Organize.loadSavedLayout(); + } else if (self.type == "ForceDirected3D" || !self.savedLayout) { + self.mGraph.animate(Metamaps.JIT.ForceDirected.animateFDLayout); + } + } + } +}; // 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; + 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; + }, + 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)); + }, + 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(/^http:\/\/(?:www\.)?youtube.com\/watch\?(?=[^?]*v=\w+)(?:[^\s?]+)?$/) != null); + } +}; // end Metamaps.Util + +/* + * + * REALTIME + * + */ +Metamaps.Realtime = { + // this is for the heroku staging environment + //Metamaps.Realtime.socket = io.connect('http://gentle-savannah-1303.herokuapp.com'); + // this is for metamaps.cc + //Metamaps.Realtime.socket = io.connect('http://metamaps.cc:5001'); + // this is for localhost development + //Metamaps.Realtime.socket = io.connect('http://localhost:5001'); + socket: null, + isOpen: false, + timeOut: null, + changing: false, + mappersOnMap: {}, + status: true, // stores whether realtime is True/On or False/Off + init: function () { + var self = Metamaps.Realtime; + + $(".realtimeOnOff").click(self.toggle); + + $(".sidebarCollaborate").hover(self.open, self.close); + + var mapperm = Metamaps.Active.Map && Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); + + if (mapperm) { + self.socket = io.connect('http://localhost:5001'); + self.socket.on('connect', function () { + console.log('socket connected'); + self.setupSocket(); + }); + } + }, + toggle: function () { + var self = Metamaps.Realtime; + + if (!self.status) { + self.sendRealtimeOn(); + $(this).html('ON').removeClass('rtOff').addClass('rtOn'); + $(".rtMapperSelf").removeClass('littleRtOff').addClass('littleRtOn'); + } else { + self.sendRealtimeOff(); + $(this).html('OFF').removeClass('rtOn').addClass('rtOff'); + $(".rtMapperSelf").removeClass('littleRtOn').addClass('littleRtOff'); + } + self.status = !self.status; + $(".sidebarCollaborateIcon").toggleClass("blue"); + }, + open: function () { + var self = Metamaps.Realtime; + + clearTimeout(self.timeOut); + if (!self.isOpen && !self.changing) { + self.changing = true; + $('.sidebarCollaborateBox').fadeIn(200, function () { + self.changing = false; + self.isOpen = true; + }); + } + }, + close: function () { + var self = Metamaps.Realtime; + + self.timeOut = setTimeout(function () { + if (!self.changing) { + self.changing = true; + $('.sidebarCollaborateBox').fadeOut(200, function () { + self.changing = false; + self.isOpen = false; + }); + } + }, 500); + }, + 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"), + mapid: Metamaps.Active.Map.id + }); + + // 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, self.contentUpdate); + }, + 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.userrealtime + + self.mappersOnMap[data.userid] = { + name: data.username, + realtime: data.userrealtime + }; + + var onOff = data.userrealtime ? "On" : "Off"; + var mapperListItem = '
  • ' + data.username + '
  • '; + + $('#mapper' + data.userid).remove(); + $('.realtimeMapperList ul').append(mapperListItem); + }, + newPeerOnMap: function (data) { + var self = Metamaps.Realtime; + var socket = Metamaps.Realtime.socket; + + // data.userid + // data.username + + self.mappersOnMap[data.userid] = { + name: data.username, + realtime: true + }; + + var mapperListItem = '
  • ' + data.username + '
  • '; + $('#mapper' + data.userid).remove(); + $('.realtimeMapperList ul').append(mapperListItem); + + Metamaps.GlobalUI.notifyUser(data.username + ' just joined the map'); + + // 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"), + userid: Metamaps.Active.Mapper.id, + userrealtime: self.status, + mapid: Metamaps.Active.Map.id + }; + socket.emit('updateNewMapperList', update); + }, + lostPeerOnMap: function (data) { + var self = Metamaps.Realtime; + var socket = Metamaps.Realtime.socket; + + // data.userid + // data.username + + delete self.mappersOnMap[data.userid]; + + $('#mapper' + data.userid).remove(); + + Metamaps.GlobalUI.notifyUser(data.username + ' just left the map'); + }, + 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'); + + 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'); + + Metamaps.GlobalUI.notifyUser(data.username + ' just turned off realtime'); + }, + contentUpdate: function (data) { + var self = Metamaps.Realtime; + var socket = Metamaps.Realtime.socket; + var graph = Metamaps.Visualize.mGraph.graph; + + //as long as you weren't the origin of the changes, update your map + if (data.origin != Metamaps.Active.Mapper.id && self.status) { + if (data.resource == 'Topic') { + topic = $.parseJSON(data.obj); + + if (data.action == 'create') { + self.addTopicToMap(topic); + } else if (data.action == 'update' && graph.getNode(topic.id) != 'undefined') { + self.updateTopicOnMap(topic); + } else if (data.action == 'destroy' && graph.getNode(topic.id) != 'undefined') { + Metamaps.Control.hideNode(topic.id) + } + + return; + } else if (data.resource == 'Synapse') { + synapse = $.parseJSON(data.obj); + + if (data.action == 'create') { + self.addSynapseToMap(synapse); + } else if (data.action == 'update' && + graph.getAdjacence(synapse.data.$direction['0'], synapse.data.$direction['1']) != 'undefined') { + self.updateSynapseOnMap(synapse); + } else if (data.action == 'destroy' && + graph.getAdjacence(synapse.data.$direction['0'], synapse.data.$direction['1']) != 'undefined') { + var edge = graph.getAdjacence(synapse.data.$direction['0'], synapse.data.$direction['1']); + Metamaps.Control.hideEdge(edge); + } + + return; + } + } + }, + addTopicToMap: function (topic) { + + // TODO + var newPos, tempForT; + Metamaps.Visualize.mGraph.graph.addNode(topic); + tempForT = Metamaps.Visualize.mGraph.graph.getNode(topic.id); + tempForT.setData('dim', 1, 'start'); + tempForT.setData('dim', 25, 'end'); + newPos = new $jit.Complex(); + newPos.x = tempForT.data.$xloc; + newPos.y = tempForT.data.$yloc; + tempForT.setPos(newPos, 'start'); + tempForT.setPos(newPos, 'current'); + tempForT.setPos(newPos, 'end'); + Metamaps.Visualize.mGraph.fx.plotNode(tempForT, Metamaps.Visualize.mGraph.canvas); + }, + updateTopicOnMap: function (topic) { + + // TODO + var newPos, tempForT; + tempForT = Metamaps.Visualize.mGraph.graph.getNode(topic.id); + tempForT.data = topic.data; + tempForT.name = topic.name; + if (MetamapsModel.showcardInUse === topic.id) { + populateShowCard(tempForT); + } + newPos = new $jit.Complex(); + newPos.x = tempForT.data.$xloc; + newPos.y = tempForT.data.$yloc; + tempForT.setPos(newPos, 'start'); + tempForT.setPos(newPos, 'current'); + tempForT.setPos(newPos, 'end'); + return Metamaps.Visualize.mGraph.fx.animate({ + modes: ['linear', 'node-property:dim', 'edge-property:lineWidth'], + transition: $jit.Trans.Quad.easeInOut, + duration: 500 + }); + }, + addSynapseToMap: function (synapse) { + + // TODO + var Node1, Node2, tempForS; + Node1 = Metamaps.Visualize.mGraph.graph.getNode(synapse.data.$direction[0]); + Node2 = Metamaps.Visualize.mGraph.graph.getNode(synapse.data.$direction[1]); + Metamaps.Visualize.mGraph.graph.addAdjacence(Node1, Node2, {}); + tempForS = Metamaps.Visualize.mGraph.graph.getAdjacence(Node1.id, Node2.id); + tempForS.setDataset('start', { + lineWidth: 0.4 + }); + tempForS.setDataset('end', { + lineWidth: 2 + }); + tempForS.data = synapse.data; + Metamaps.Visualize.mGraph.fx.plotLine(tempForS, Metamaps.Visualize.mGraph.canvas); + return Metamaps.Visualize.mGraph.fx.animate({ + modes: ['linear', 'node-property:dim', 'edge-property:lineWidth'], + transition: $jit.Trans.Quad.easeInOut, + duration: 500 + }); + }, + updateSynapseOnMap: function (synapse) { + + // TODO + var k, tempForS, v, wasShowDesc, _ref; + tempForS = Metamaps.Visualize.mGraph.graph.getAdjacence(synapse.data.$direction[0], synapse.data.$direction[1]); + wasShowDesc = tempForS.data.$showDesc; + _ref = synapse.data; + for (k in _ref) { + v = _ref[k]; + tempForS.data[k] = v; + } + tempForS.data.$showDesc = wasShowDesc; + if (MetamapsModel.edgecardInUse === synapse.data.$id) { // TODO + editEdge(tempForS, false); + } + return Metamaps.Visualize.mGraph.plot(); + } +}; // end Metamaps.Realtime + + +/* + * + * CONTROL + * + */ +Metamaps.Control = { + init: function () { + + }, + selectNode: function (node) { + if (Metamaps.Selected.Nodes.indexOf(node) != -1) return; + node.selected = true; + node.setData('dim', 30, 'current'); + node.eachAdjacency(function (adj) { + Metamaps.Control.selectEdge(adj); + }); + Metamaps.Selected.Nodes.push(node); + }, + deselectAllNodes: function () { + var l = Metamaps.Selected.Nodes.length; + for (var i = l - 1; i >= 0; i -= 1) { + var node = Metamaps.Selected.Nodes[i]; + Metamaps.Control.deselectNode(node); + } + Metamaps.Visualize.mGraph.plot(); + }, + deselectNode: function (node) { + delete node.selected; + node.eachAdjacency(function (adj) { + Metamaps.Control.deselectEdge(adj); + }); + node.setData('dim', 25, 'current'); + + //remove the node + Metamaps.Selected.Nodes.splice( + Metamaps.Selected.Nodes.indexOf(node), 1); + }, + deleteSelectedNodes: function () { // refers to deleting topics permanently + var l = Metamaps.Selected.Nodes.length; + for (var i = l - 1; i >= 0; i -= 1) { + var node = Metamaps.Selected.Nodes[i]; + Metamaps.Control.deleteNode(node.id); + } + }, + deleteNode: function (nodeid) { // refers to deleting topics permanently + var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid); + var id = node.getData('id'); + Metamaps.Control.deselectNode(node); + Metamaps.Topics.get(id).destroy(); + Metamaps.Control.hideNode(nodeid); + }, + removeSelectedNodes: function () { // refers to removing topics permanently from a map + var l = Metamaps.Selected.Nodes.length, + i, + node, + mapperm = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); + + if (mapperm) { + for (i = l - 1; i >= 0; i -= 1) { + node = Metamaps.Selected.Nodes[i]; + Metamaps.Control.removeNode(node.id); + } + } + }, + removeNode: function (nodeid) { // refers to removing topics permanently from a map + var mapperm = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper); + var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid); + var mappingid = node.getData("mapping").id; + + if (mapperm) { + Metamaps.Control.deselectNode(node); + Metamaps.Mappings.get(mappingid).destroy(); + Metamaps.Control.hideNode(nodeid); + } + }, + hideSelectedNodes: function () { + var l = Metamaps.Selected.Nodes.length, + i, + node; + + for (i = l - 1; i >= 0; i -= 1) { + node = Metamaps.Selected.Nodes[i]; + Metamaps.Control.hideNode(node.id); + } + }, + hideNode: function (nodeid) { + var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid); + 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; + } + + Metamaps.Control.deselectNode(node); + + node.setData('alpha', 0, 'end'); + node.eachAdjacency(function (adj) { + adj.setData('alpha', 0, 'end'); + }); + Metamaps.Visualize.mGraph.fx.animate({ + modes: ['node-property:alpha', + 'edge-property:alpha' + ], + duration: 500 + }); + setTimeout(function () { + Metamaps.Visualize.mGraph.graph.removeNode(nodeid); + }, 500); + }, + selectEdge: function (edge) { + if (Metamaps.Selected.Edges.indexOf(edge) != -1) return; + edge.setData('showDesc', true, 'current'); + if (!Metamaps.Settings.embed) { + edge.setDataset('end', { + lineWidth: 4, + color: Metamaps.Settings.colors.synapses.selected, + alpha: 1 + }); + } else if (Metamaps.Settings.embed) { + edge.setDataset('end', { + lineWidth: 4, + color: Metamaps.Settings.colors.synapses.selected, + alpha: 1 + }); + } + Metamaps.Visualize.mGraph.fx.animate({ + modes: ['edge-property:lineWidth:color:alpha'], + duration: 100 + }); + Metamaps.Selected.Edges.push(edge); + }, + deselectAllEdges: function () { + var l = Metamaps.Selected.Edges.length; + for (var i = l - 1; i >= 0; i -= 1) { + var edge = Metamaps.Selected.Edges[i]; + Metamaps.Control.deselectEdge(edge); + } + Metamaps.Visualize.mGraph.plot(); + }, + deselectEdge: function (edge) { + edge.setData('showDesc', false, 'current'); + edge.setDataset('end', { + lineWidth: 2, + color: Metamaps.Settings.colors.synapses.normal, + alpha: 0.4 + }); + + if (Metamaps.Mouse.edgeHoveringOver == edge) { + edge.setData('showDesc', true, 'current'); + edge.setDataset('end', { + lineWidth: 4, + color: Metamaps.Settings.colors.synapses.hover, + alpha: 1 + }); + } + + Metamaps.Visualize.mGraph.fx.animate({ + modes: ['edge-property:lineWidth:color:alpha'], + duration: 100 + }); + + //remove the edge + Metamaps.Selected.Edges.splice( + Metamaps.Selected.Edges.indexOf(edge), 1); + }, + deleteSelectedEdges: function () { // refers to deleting topics permanently + var edge, + l = Metamaps.Selected.Edges.length; + for (var i = l - 1; i >= 0; i -= 1) { + edge = Metamaps.Selected.Edges[i]; + Metamaps.Control.deleteEdge(edge); + } + }, + deleteEdge: function (edge) { + + // TODO make it so that you select which one, of multiple possible synapses you want to delete + + //var id = edge.getData("id"); + //Metamaps.Synapses.get(id).destroy(); + //Metamaps.Control.hideEdge(edge); + }, + removeSelectedEdges: function () { + var l = Metamaps.Selected.Edges.length, + i, + edge; + + if (Metamaps.Active.Map) { + for (i = l - 1; i >= 0; i -= 1) { + edge = Metamaps.Selected.Edges[i]; + Metamaps.Control.removeEdge(edge); + } + Metamaps.Selected.Edges = new Array(); + } + }, + removeEdge: function (edge) { + + // TODO make it so that you select which one, of multiple possible synapses you want + + //var mappingid = edge.getData("mappingid"); + //Metamaps.Mappings.get(mappingid).destroy(); + //Metamaps.Control.hideEdge(edge); + }, + hideSelectedEdges: function () { + var edge, + l = Metamaps.Selected.Edges.length, + i; + for (i = l - 1; i >= 0; i -= 1) { + edge = Metamaps.Selected.Edges[i]; + Metamaps.Control.hideEdge(edge); + } + Metamaps.Selected.Edges = new Array(); + }, + hideEdge: function (edge) { + var from = edge.nodeFrom.id; + var to = edge.nodeTo.id; + edge.setData('alpha', 0, 'end'); + Metamaps.Visualize.mGraph.fx.animate({ + modes: ['edge-property:alpha'], + duration: 500 + }); + setTimeout(function () { + Metamaps.Visualize.mGraph.graph.removeAdjacence(from, to); + }, 500); + }, + updateSelectedPermissions: function (permission) { + + var edge, synapse, node, topic; + + Metamaps.GlobalUI.notifyUser('Working...'); + + // variables to keep track of how many nodes and synapses you had the ability to change the permission of + var nCount = 0, + sCount = 0; + + // change the permission of the selected synapses, if logged in user is the original creator + var l = Metamaps.Selected.Edges.length; + for (var i = l - 1; i >= 0; i -= 1) { + edge = Metamaps.Selected.Edges[i]; + synapse = edge.getData('synapses')[0]; + + if (synapse.authorizePermissionChange(Metamaps.Active.Mapper)) { + synapse.save({ + permission: permission + }); + sCount++; + } + } + + // change the permission of the selected topics, if logged in user is the original creator + var l = Metamaps.Selected.Nodes.length; + for (var i = l - 1; i >= 0; i -= 1) { + node = Metamaps.Selected.Nodes[i]; + topic = node.getData('topic'); + + if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) { + topic.save({ + permission: permission + }); + nCount++; + } + } + + var nString = nCount == 1 ? (nCount.toString() + ' topic and ') : (nCount.toString() + ' topics and '); + var sString = sCount == 1 ? (sCount.toString() + ' synapse') : (sCount.toString() + ' synapses'); + + var message = nString + sString + ' you created updated to ' + permission; + Metamaps.GlobalUI.notifyUser(message); + }, +}; // end Metamaps.Control + + +/* + * + * FILTER + * + */ +Metamaps.Filter = { + filters: { + name: "", + metacode: [], + mappers: [], + synapseTypes: [] + }, + isOpen: false, + timeOut: null, + changing: false, + init: function () { + var self = Metamaps.Filter; + + $(".sidebarFilter").hover(self.open, self.close); + + // initialize scroll bar for filter by metacode, then hide it and position it correctly again + $("#filter_by_metacode").mCustomScrollbar({ + mouseWheelPixels: 200, + advanced: { + updateOnContentResize: true + } + }); + $('.sidebarFilterBox').hide().css({ + position: 'absolute', + top: '45px', + right: '-36px' + }); + + $('.sidebarFilterBox .showAll').click(self.filterNoMetacodes); + $('.sidebarFilterBox .hideAll').click(self.filterAllMetacodes); + + // toggle visibility of topics with metacodes based on status in the filters list + $('#filter_by_metacode ul li').click(self.toggleMetacode); + }, + open: function () { + var self = Metamaps.Filter; + + clearTimeout(self.timeOut); + if (!self.isOpen && !self.changing) { + self.changing = true; + + $('.sidebarFilterBox').fadeIn(200, function () { + self.changing = false; + self.isOpen = true; + }); + } + }, + close: function () { + var self = Metamaps.Filter; + + self.timeOut = setTimeout(function () { + if (!self.changing) { + self.changing = true; + + $('.sidebarFilterBox').fadeOut(200, function () { + self.changing = false; + self.isOpen = false; + }); + } + }, 500); + }, + filterNoMetacodes: function (e) { + + $('#filter_by_metacode ul li').removeClass('toggledOff'); + + // TODO + /* + showAll(); + + for (var catVis in categoryVisible) { + categoryVisible[catVis] = true; + } + */ + }, + filterAllMetacodes: function (e) { + + $('#filter_by_metacode ul li').addClass('toggledOff'); + + // TODO + /* + hideAll(); + for (var catVis in categoryVisible) { + categoryVisible[catVis] = false; + } + */ + }, + toggleMetacode: function () { + + var category = $(this).children('img').attr('alt'); + + // TODO + /*switchVisible(category); + + // toggle the image and the boolean array value + if (categoryVisible[category] == true) { + $(this).addClass('toggledOff'); + categoryVisible[category] = false; + } else if (categoryVisible[category] == false) { + $(this).removeClass('toggledOff'); + categoryVisible[category] = true; + }*/ + }, + passFilters: function (topic) { + var self = Metamaps.Find; + var filters = self.filters; + + var passesName = filters.name == "" ? true : false, + passesType = filters.type == [] ? true : false; + + //filter by name + if (topic.get('1')[1][0].toLowerCase().indexOf(filters.name) !== -1) { + passesName = true; + } + // filter by type + if (!filters.type == []) { + // get the array of types that your topic 'is' + var metacodes = topic.get('2') ? topic.get('2')[1] : []; + if (_.intersection(filters.type, metacodes).length == 0) passesType = true; + } + + if (passesName && passesType) { + return true; + } else { + return false; + } + } +}; // end Metamaps.Filter + + +/* + * + * LISTENERS + * + */ +Metamaps.Listeners = { + + init: function () { + + $(document).on('keydown', function (e) { + switch (e.which) { + case 13: + Metamaps.JIT.enterKeyHandler(); + e.preventDefault(); + break; + case 27: + Metamaps.JIT.escKeyHandler(); + break; + default: + break; //alert(e.which); + } + }); + + //$(window).resize(function () { + // Metamaps.Visualize.mGraph.canvas.resize($(window).width(), $(window).height()); + //}); + } +}; // end Metamaps.Listeners + + +/* + * + * ORGANIZE + * + */ +Metamaps.Organize = { + init: function () { + + }, + arrange: function (layout, centerNode) { + + + // first option for layout to implement is 'grid', will do an evenly spaced grid with its center at the 0,0 origin + if (layout == 'grid') { + var numNodes = _.size(Metamaps.Visualize.mGraph.graph.nodes); // this will always be an integer, the # of nodes on your graph visualization + var numColumns = Math.floor(Math.sqrt(numNodes)); // the number of columns to make an even grid + var GRIDSPACE = 400; + var row = 0; + var column = 0; + Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + if (column == numColumns) { + column = 0; + row += 1; + } + var newPos = new $jit.Complex(); + newPos.x = column * GRIDSPACE; + newPos.y = row * GRIDSPACE; + n.setPos(newPos, 'end'); + column += 1; + }); + Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout); + } else if (layout == 'grid_full') { + + // this will always be an integer, the # of nodes on your graph visualization + var numNodes = _.size(Metamaps.Visualize.mGraph.graph.nodes); + //var numColumns = Math.floor(Math.sqrt(numNodes)); // the number of columns to make an even grid + //var GRIDSPACE = 400; + var height = Metamaps.Visualize.mGraph.canvas.getSize(0).height; + var width = Metamaps.Visualize.mGraph.canvas.getSize(0).width; + var totalArea = height * width; + var cellArea = totalArea / numNodes; + var ratio = height / width; + var cellWidth = sqrt(cellArea / ratio); + var cellHeight = cellArea / cellWidth; + var row = floor(height / cellHeight); + var column = floor(width / cellWidth); + var totalCells = row * column; + + if (totalCells) + Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + if (column == numColumns) { + column = 0; + row += 1; + } + var newPos = new $jit.Complex(); + newPos.x = column * GRIDSPACE; + newPos.y = row * GRIDSPACE; + n.setPos(newPos, 'end'); + column += 1; + }); + Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout); + } else if (layout == 'radial') { + + var centerX = centerNode.getPos().x; + var centerY = centerNode.getPos().y; + centerNode.setPos(centerNode.getPos(), 'end'); + + console.log(centerNode.adjacencies); + var lineLength = 200; + var usedNodes = {}; + usedNodes[centerNode.id] = centerNode; + var radial = function (node, level, degree) { + if (level == 1) { + var numLinksTemp = _.size(node.adjacencies); + var angleTemp = 2 * Math.PI / numLinksTemp; + } else { + angleTemp = 2 * Math.PI / 20 + }; + node.eachAdjacency(function (a) { + var isSecondLevelNode = (centerNode.adjacencies[a.nodeTo.id] != undefined && level > 1); + if (usedNodes[a.nodeTo.id] == undefined && !isSecondLevelNode) { + var newPos = new $jit.Complex(); + newPos.x = level * lineLength * Math.sin(degree) + centerX; + newPos.y = level * lineLength * Math.cos(degree) + centerY; + a.nodeTo.setPos(newPos, 'end'); + usedNodes[a.nodeTo.id] = a.nodeTo; + + radial(a.nodeTo, level + 1, degree); + degree += angleTemp; + }; + }); + }; + radial(centerNode, 1, 0); + Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout); + + } else if (layout == 'center_viewport') { + + var lowX = 0, + lowY = 0, + highX = 0, + highY = 0; + var oldOriginX = Metamaps.Visualize.mGraph.canvas.translateOffsetX; + var oldOriginY = Metamaps.Visualize.mGraph.canvas.translateOffsetY; + + Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + if (n.id === 1) { + lowX = n.getPos().x; + lowY = n.getPos().y; + highX = n.getPos().x; + highY = n.getPos().y; + }; + if (n.getPos().x < lowX) lowX = n.getPos().x; + if (n.getPos().y < lowY) lowY = n.getPos().y; + if (n.getPos().x > highX) highX = n.getPos().x; + if (n.getPos().y > highY) highY = n.getPos().y; + }); + console.log(lowX, lowY, highX, highY); + var newOriginX = (lowX + highX) / 2; + var newOriginY = (lowY + highY) / 2; + + } else alert('please call function with a valid layout dammit!'); + }, + loadSavedLayout: function (id) { + Metamaps.Visualize.computePositions(); + Metamaps.Visualize.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout); + }, +}; // end Metamaps.Organize + + +/* + * + * TOPIC + * + */ +Metamaps.Topic = { + // this function is to retrieve a topic JSON object from the database + // @param id = the id of the topic to retrieve + get: function (id, callback) { + // if the desired topic is not yet in the local topic repository, fetch it + if (Metamaps.Topics.get(id) == undefined) { + //console.log("Ajax call!"); + if (!callback) { + var e = $.ajax({ + url: "/topics/" + id + ".json", + async: false + }); + Metamaps.Topics.add($.parseJSON(e.responseText)); + return Metamaps.Topics.get(id); + } else { + return $.ajax({ + url: "/topics/" + id + ".json", + success: function (data) { + Metamaps.Topics.add(data); + callback(Metamaps.Topics.get(id)); + } + }); + } + } else { + if (!callback) { + return Metamaps.Topics.get(id); + } else { + return callback(Metamaps.Topics.get(id)); + } + } + }, + + /* + * + * + */ + renderTopic: function (mapping, topic, createNewInDB) { + var self = Metamaps.Topic; + + var nodeOnViz, tempPos; + + var newnode = topic.createNode(); + + if (!$.isEmptyObject(Metamaps.Visualize.mGraph.graph.nodes)) { + Metamaps.Visualize.mGraph.graph.addNode(newnode); + Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + n.setData("dim", 25, "start"); + n.setData("dim", 25, "end"); + }); + nodeOnViz = Metamaps.Visualize.mGraph.graph.getNode(newnode.id); + topic.set('node', nodeOnViz); + topic.updateNode(); // links the topic and the mapping to the node + + + nodeOnViz.setData("dim", 1, "start"); + nodeOnViz.setData("dim", 25, "end"); + if (Metamaps.Visualize.type === "RGraph") { + tempPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')); + tempPos = tempPos.toPolar(); + nodeOnViz.setPos(tempPos, "current"); + nodeOnViz.setPos(tempPos, "start"); + nodeOnViz.setPos(tempPos, "end"); + } else if (Metamaps.Visualize.type === "ForceDirected") { + nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "current"); + nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "start"); + nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "end"); + } + if (Metamaps.Create.newTopic.addSynapse) { + Metamaps.Create.newSynapse.topic1id = tempNode.id; + Metamaps.Create.newSynapse.topic2id = nodeOnViz.id; + Metamaps.Create.newSynapse.open(); + Metamaps.Visualize.mGraph.fx.animate({ + modes: ["node-property:dim"], + duration: 500, + onComplete: function () { + tempNode = null; + tempNode2 = null; + tempInit = false; + } + }); + } else { + Metamaps.Visualize.mGraph.fx.plotNode(nodeOnViz, Metamaps.Visualize.mGraph.canvas); + Metamaps.Visualize.mGraph.fx.animate({ + modes: ["node-property:dim"], + duration: 500, + onComplete: function () { + + } + }); + } + } else { + Metamaps.Visualize.mGraph.loadJSON(newnode); + nodeOnViz = Metamaps.Visualize.mGraph.graph.getNode(newnode.id); + mapping.set('node', nodeOnViz); + mapping.updateNode(); // links the topic and the mapping to the node + + nodeOnViz.setData("dim", 1, "start"); + nodeOnViz.setData("dim", 25, "end"); + nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "current"); + nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "start"); + nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), "end"); + Metamaps.Visualize.mGraph.fx.plotNode(nodeOnViz, Metamaps.Visualize.mGraph.canvas); + Metamaps.Visualize.mGraph.fx.animate({ + modes: ["node-property:dim"], + duration: 500, + onComplete: function () { + + } + }); + } + + if (!Metamaps.Settings.sandbox && createNewInDB) { + if (topic.isNew()) { + topic.save(null, { + success: function (topicModel, response) { + topicModel.updateMapping(); + if (Metamaps.Active.Map) { + mapping.save(); + } + }, + error: function (model, response) { + console.log('error saving topic to database'); + } + }); + } else if (!topic.isNew() && Metamaps.Active.Map) { + mapping.save(); + } + } + }, + createTopicLocally: function () { + var self = Metamaps.Topic; + + var metacode = Metamaps.Metacodes.findWhere({ + name: Metamaps.Create.newTopic.metacode + }); + + var topic = new Metamaps.Backbone.Topic({ + name: Metamaps.Create.newTopic.name, + metacode_id: metacode.id + }); + Metamaps.Topics.add(topic); + + var mapping = new Metamaps.Backbone.Mapping({ + category: "Topic", + xloc: Metamaps.Create.newTopic.x, + yloc: Metamaps.Create.newTopic.y, + topic_id: topic.cid + }); + Metamaps.Mappings.add(mapping); + + //these can't happen until the value is retrieved, which happens in the line above + Metamaps.Create.newTopic.hide(); + + self.renderTopic(mapping, topic, true); // this function also includes the creation of the topic in the database + }, + getTopicFromAutocomplete: function (id) { + var self = Metamaps.Topic; + + Metamaps.Create.newTopic.hide(); + + var topic = self.get(id); + + var mapping = new Metamaps.Backbone.Mapping({ + category: "Topic", + xloc: Metamaps.Create.newTopic.x, + yloc: Metamaps.Create.newTopic.y, + topic_id: topic.id + }); + Metamaps.Mappings.add(mapping); + + self.renderTopic(mapping, topic, false); + } +}; // end Metamaps.Topic + + +/* + * + * SYNAPSE + * + */ +Metamaps.Synapse = { + // this function is to retrieve a synapse JSON object from the database + // @param id = the id of the synapse to retrieve + get: function (id, callback) { + // if the desired topic is not yet in the local topic repository, fetch it + if (Metamaps.Synapses.get(id) == undefined) { + if (!callback) { + var e = $.ajax({ + url: "/synapses/" + id + ".json", + async: false + }); + Metamaps.Synapses.add($.parseJSON(e.responseText)); + return Metamaps.Synapses.get(id); + } else { + return $.ajax({ + url: "/synapses/" + id + ".json", + success: function (data) { + Metamaps.Synapses.add(data); + callback(Metamaps.Synapses.get(id)); + } + }); + } + } else { + if (!callback) { + return Metamaps.Synapses.get(id); + } else { + return callback(Metamaps.Synapses.get(id)); + } + } + }, + /* + * + * + */ + renderSynapse: function (mapping, synapse, node1, node2, createNewInDB) { + var self = Metamaps.Synapse; + + var edgeOnViz; + + var newedge = synapse.createEdge(); + + Metamaps.Visualize.mGraph.graph.addAdjacence(node1, node2, newedge.data); + edgeOnViz = Metamaps.Visualize.mGraph.graph.getAdjacence(node1.id, node2.id); + synapse.set('edge', edgeOnViz); + synapse.updateEdge(); // links the topic and the mapping to the node + + Metamaps.Visualize.mGraph.fx.plotLine(edgeOnViz, Metamaps.Visualize.mGraph.canvas); + Metamaps.Control.selectEdge(edgeOnViz); + + if (!Metamaps.Settings.sandbox && createNewInDB) { + if (synapse.isNew()) { + synapse.save(null, { + success: function (synapseModel, response) { + synapseModel.updateMapping(); + if (Metamaps.Active.Map) { + mapping.save(); + } + }, + error: function (model, response) { + console.log('error saving synapse to database'); + } + }); + } else if (!synapse.isNew() && Metamaps.Active.Map) { + mapping.save(); + } + } + }, + createSynapseLocally: function () { + var self = Metamaps.Synapse, + topic1, + topic2, + node1, + node2, + synapse, + mapping; + + //for each node in this array we will create a synapse going to the position2 node. + var synapsesToCreate = []; + + node2 = Metamaps.Visualize.mGraph.graph.getNode(Metamaps.Create.newSynapse.topic2id); + topic2 = node2.getData('topic'); + + var len = Metamaps.Selected.Nodes.length; + if (len == 0) { + synapsesToCreate[0] = Metamaps.Visualize.mGraph.graph.getNode(Metamaps.Create.newSynapse.topic1id); + } else if (len > 0) { + synapsesToCreate = Metamaps.Selected.Nodes; + } + + for (var i = 0; i < synapsesToCreate.length; i++) { + node1 = synapsesToCreate[i]; + topic1 = node1.getData('topic'); + synapse = new Metamaps.Backbone.Synapse({ + desc: Metamaps.Create.newSynapse.description, + node1_id: topic1.isNew() ? topic1.cid : topic1.id, + node2_id: topic2.isNew() ? topic2.cid : topic2.id, + }); + Metamaps.Synapses.add(synapse); + + mapping = new Metamaps.Backbone.Mapping({ + category: "Synapse", + synapse_id: synapse.cid + }); + Metamaps.Mappings.add(mapping); + + // this function also includes the creation of the synapse in the database + self.renderSynapse(mapping, synapse, node1, node2, true); + } // for each in synapsesToCreate + + Metamaps.Create.newSynapse.hide(); + }, + getSynapseFromAutocomplete: function (id) { + var self = Metamaps.Synapse, + node1, + node2; + + Metamaps.Create.newSynapse.hide(); + + var synapse = self.get(id); + + var mapping = new Metamaps.Backbone.Mapping({ + category: "Synapse", + synapse_id: synapse.id + }); + Metamaps.Mappings.add(mapping); + + node1 = Metamaps.Visualize.mGraph.graph.getNode(Metamaps.Create.newSynapse.topic1id); + node2 = Metamaps.Visualize.mGraph.graph.getNode(Metamaps.Create.newSynapse.topic2id); + + self.renderSynapse(mapping, synapse, node1, node2, false); + } +}; // end Metamaps.Synapse + + +/* + * + * MAP + * + */ +Metamaps.Map = { + init: function () { + var self = Metamaps.Map; + + // prevent right clicks on the main canvas, so as to not get in the way of our right clicks + $('#center-container').bind('contextmenu', function (e) { + return false; + }); + + $('.sidebarFork').click(function () { + self.fork(); + }); + + Metamaps.GlobalUI.CreateMap.emptyForkMapForm = $('#fork_map').html(); + + self.InfoBox.init(); + self.CheatSheet.init(); + }, + // this function is to retrieve a map JSON object from the database + // @param id = the id of the map to retrieve + get: function (id, callback) { + // if the desired topic is not yet in the local topic repository, fetch it + if (Metamaps.Maps.get(id) == undefined) { + if (!callback) { + var e = $.ajax({ + url: "/maps/" + id + ".json", + async: false + }); + Metamaps.Maps.add($.parseJSON(e.responseText)); + return Metamaps.Maps.get(id); + } else { + return $.ajax({ + url: "/users/" + id + ".json", + success: function (data) { + Metamaps.Maps.add(data); + callback(Metamaps.Maps.get(id)); + } + }); + } + } else { + if (!callback) { + return Metamaps.Maps.get(id); + } else { + return callback(Metamaps.Maps.get(id)); + } + } + }, + fork: function () { + Metamaps.GlobalUI.openLightbox('forkmap'); + + var nodes_data = "", + synapses_data = ""; + var synapses_array = new Array(); + Metamaps.Visualize.mGraph.graph.eachNode(function (n) { + //don't add to the map if it was filtered out + // TODO + //if (categoryVisible[n.getData('metacode')] == false) { + // return; + //} + + var x, y; + if (n.pos.x && n.pos.y) { + x = n.pos.x; + y = n.pos.y; + } else { + var x = Math.cos(n.pos.theta) * n.pos.rho; + var y = Math.sin(n.pos.theta) * n.pos.rho; + } + nodes_data += n.id + '/' + x + '/' + y + ','; + n.eachAdjacency(function (adj) { + synapses_array.push(adj.getData("synapses")[0].id); // TODO + }); + }); + + //get unique values only + synapses_array = $.grep(synapses_array, function (value, key) { + return $.inArray(value, synapses_array) === key; + }); + + synapses_data = synapses_array.join(); + nodes_data = nodes_data.slice(0, -1); + + Metamaps.GlobalUI.CreateMap.topicsToMap = nodes_data; + Metamaps.GlobalUI.CreateMap.synapsesToMap = synapses_data; + } +}; + + +/* + * + * CHEATSHEET + * + */ +Metamaps.Map.CheatSheet = { + init: function () { + // tab the cheatsheet + $('#cheatSheet').tabs().addClass("ui-tabs-vertical ui-helper-clearfix"); + $("#cheatSheet .ui-tabs-nav li").removeClass("ui-corner-top").addClass("ui-corner-left"); + } +}; // end Metamaps.Map.CheatSheet + + +/* + * + * INFOBOX + * + */ +Metamaps.Map.InfoBox = { + isOpen: false, + timeOut: null, + changing: false, + selectingPermission: false, + init: function () { + var self = Metamaps.Map.InfoBox; + + // because anyone who can edit the map can change the map title + $('.mapInfoName .best_in_place_name').bind("ajax:success", function () { + var name = $(this).html(); + $('.mapName').html(name); + Metamaps.Active.Map.set('name', name); + }); + + $('.yourMap .mapPermission').click(self.onPermissionClick); + + $("div.index").hover(self.open, self.close); + }, + open: function (event) { + var self = Metamaps.GlobalUI.Account; + + clearTimeout(self.timeOut); + if (!self.isOpen && !self.changing && event.target.className != "openCheatsheet openLightbox") { + self.changing = true; + $('.mapInfoBox').fadeIn(200, function () { + self.changing = false; + self.isOpen = true; + }); + } + }, + close: function () { + var self = Metamaps.GlobalUI.Account; + + self.timeOut = setTimeout(function () { + if (!self.changing) { + self.changing = true; + $('.mapInfoBox').fadeOut(200, function () { + self.changing = false; + self.isOpen = false; + }); + } + }, 500); + }, + onPermissionClick: function () { + var self = Metamaps.Map.InfoBox; + + if (!self.selectingPermission) { + self.selectingPermission = true; + $(this).addClass('minimize'); // this line flips the drop down arrow to a pull up arrow + if ($(this).hasClass('commons')) { + $(this).append('
    '); + } else if ($(this).hasClass('public')) { + $(this).append('
    '); + } else if ($(this).hasClass('private')) { + $(this).append('
    '); + } + $('.mapPermission .permissionSelect li').click(self.selectPermission); + } else { + self.selectingPermission = false; + $(this).removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow + $('.mapPermission .permissionSelect').remove(); + } + }, + selectPermission: function () { + var self = Metamaps.Map.InfoBox; + + self.selectingPermission = false; + var permission = $(this).attr('class'); + Metamaps.Active.Map.save({ + permission: permission + }); + $('.mapPermission').removeClass('commons public private minimize').addClass(permission); + $('.mapPermission .permissionSelect').remove(); + event.stopPropagation(); + } +}; // end Metamaps.Map.InfoBox + + +/* + * + * MAPPER + * + */ +Metamaps.Mapper = { + // this function is to retrieve a mapper JSON object from the database + // @param id = the id of the mapper to retrieve + get: function (id, callback) { + // if the desired topic is not yet in the local topic repository, fetch it + if (Metamaps.Mappers.get(id) == undefined) { + if (!callback) { + var e = $.ajax({ + url: "/users/" + id + ".json", + async: false + }); + Metamaps.Mappers.add($.parseJSON(e.responseText)); + return Metamaps.Mappers.get(id); + } else { + return $.ajax({ + url: "/users/" + id + ".json", + success: function (data) { + Metamaps.Mappers.add(data); + callback(Metamaps.Mappers.get(id)); + } + }); + } + } else { + if (!callback) { + return Metamaps.Mappers.get(id); + } else { + return callback(Metamaps.Mappers.get(id)); + } + } + }, +}; // end Metamaps.Mapper \ No newline at end of file diff --git a/app/views/maps/show.html.erb b/app/views/maps/show.html.erb index 8b1d85d8..82d5cf52 100644 --- a/app/views/maps/show.html.erb +++ b/app/views/maps/show.html.erb @@ -38,9 +38,7 @@
    -

    Filter By Metacode

    allnone -
    - <%= render :partial => 'shared/filterbymetacode' %> + <%= render :partial => 'shared/filterBox' %>
    @@ -79,4 +77,4 @@ Metamaps.Topics = <%= @alltopics.to_json.html_safe %>; Metamaps.Synapses = <%= @allsynapses.to_json.html_safe %>; Metamaps.Mappings = <%= @allmappings.to_json.html_safe %>; - \ No newline at end of file + diff --git a/app/views/maps/show.html.erb~ b/app/views/maps/show.html.erb~ new file mode 100644 index 00000000..1a7a2081 --- /dev/null +++ b/app/views/maps/show.html.erb~ @@ -0,0 +1,82 @@ +<%# +# @file +# Code to display a map +# /maps/:id +#%> + +<% content_for :title, @map.name + " | Metamaps" %> + +
    + +
    + + +<% if authenticated? %> +
    +
    +
    +
    +
    +<% if @map.permission == "commons" || @map.user == user %> +
    +
    +
    +

    Realtime:

    + ON +
    +
    +
      +
    • + <%= user.name %> (me) +
    • +
    +
    +
    +
    +<% end %> +<% end %> +
    +
    +
    +

    Filter By Metacode

    allnone +
    + <%= render :partial => 'shared/filterBox' %> +
    +
    + +
    +
    + +
    + <%= render :partial => 'maps/mapinfobox' %> +
    + +
    +
    +
    +
    +
    +
    +
    + +<% if authenticated? %> + +<% # add these if you have edit permissions on the map %> +<% if @map.permission == "commons" || @map.user == user %> +<% # for creating and pulling in topics and synapses %> +<%= render :partial => 'newtopic' %> +<%= render :partial => 'newsynapse' %> +<% end %> + +<% # for populating the change metacode list on the topic card %> +<%= render :partial => 'shared/metacodeoptions' %> +<% end %> + + + diff --git a/app/views/shared/_filterBox.html.erb b/app/views/shared/_filterBox.html.erb new file mode 100644 index 00000000..f74d3c4a --- /dev/null +++ b/app/views/shared/_filterBox.html.erb @@ -0,0 +1,44 @@ +<%# + # @file + # this code generates the list of icons in the filter by metacode box in the upper right menu area + #%> + +<% + @mappers = [] + @synapses = [] + @metacodes = [] + @metacodelist = '' + @mapperlist = '' + @synapselist = '' + @map.topics.each_with_index do |topic, index| + if @metacodes.index(topic.metacode_id) == nil + @metacodes.push(topic.metacode_id) + @metacodelist += '
  • ' + topic.metacode.id.to_s + '

    ' + topic.metacode.name.downcase + '

  • ' + end + end + + @map.synapses.each_with_index do |synapses, index| + if @synapses.index(synapses.synapse_id) == nil + @synapses.push(synapses.synapse_id) + @synapselist += '
  • ' + synapse.id.to_s + '

    ' + synapse.name.downcase + '

  • ' + end + + end +%> + +

    Filter By Metacode

    allnone +
    +
    +
      + <%= @metacodelist.html_safe %> +
    +
    + +
    +
      + <%= @synapselist.html_safe %> +
    +
    + +
    +
    diff --git a/app/views/shared/_filterBox.html.erb~ b/app/views/shared/_filterBox.html.erb~ new file mode 100644 index 00000000..e909651a --- /dev/null +++ b/app/views/shared/_filterBox.html.erb~ @@ -0,0 +1,42 @@ +<%# + # @file + # this code generates the list of icons in the filter by metacode box in the upper right menu area + #%> + +<% + @mappers = [] + @synapses = [] + @metacodes = [] + @metacodelist = '' + @mapperlist = '' + @synapselist = '' + @map.topics.each_with_index do |topic, index| + if @metacodes.index(topic.metacode_id) == nil + @metacodes.push(topic.metacode_id) + @metacodelist += '
  • ' + topic.metacode.id.to_s + '

    ' + topic.metacode.name.downcase + '

  • ' + end + end + + @map.synapses.each_with_index do |synapses, index| + if @synapses.index(synapses.synapse_id) == nil + @synapses.push(synapses.synapse_id) + @synapselist += '
  • ' + synapse.id.to_s + '

    ' + synapse.name.downcase + '

  • ' + end + + end +%> + +

    Filter By Metacode

    allnone +
    +
    +
      + <%= @metacodelist.html_safe %> +
    +
    + +
    +
      + <%= @synapselist.html_safe %> +
    +
    +
    diff --git a/db/schema.rb b/db/schema.rb index c3fce2b2..ec86b421 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1,129 +1,129 @@ -# encoding: UTF-8 -# This file is auto-generated from the current state of the database. Instead -# of editing this file, please use the migrations feature of Active Record to -# incrementally modify your database, and then regenerate this schema definition. -# -# Note that this schema.rb definition is the authoritative source for your -# database schema. If you need to create the application database on another -# system, you should be using db:schema:load, not running all the migrations -# from scratch. The latter is a flawed and unsustainable approach (the more migrations -# you'll amass, the slower it'll run and the greater likelihood for issues). -# -# It's strongly recommended to check this file into your version control system. - -ActiveRecord::Schema.define(:version => 20140707161810) do - - create_table "in_metacode_sets", :force => true do |t| - t.integer "metacode_id" - t.integer "metacode_set_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - add_index "in_metacode_sets", ["metacode_id"], :name => "index_in_metacode_sets_on_metacode_id" - add_index "in_metacode_sets", ["metacode_set_id"], :name => "index_in_metacode_sets_on_metacode_set_id" - - create_table "mappings", :force => true do |t| - t.text "category" - t.integer "xloc" - t.integer "yloc" - t.integer "topic_id" - t.integer "synapse_id" - t.integer "map_id" - t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "maps", :force => true do |t| - t.text "name" - t.boolean "arranged" - t.text "desc" - t.text "permission" - t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.boolean "featured" - end - - create_table "metacode_sets", :force => true do |t| - t.string "name" - t.text "desc" - t.integer "user_id" - t.boolean "mapperContributed" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - add_index "metacode_sets", ["user_id"], :name => "index_metacode_sets_on_user_id" - - create_table "metacodes", :force => true do |t| - t.text "name" - t.string "icon" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "synapses", :force => true do |t| - t.text "desc" - t.text "category" - t.text "weight" - t.text "permission" - t.integer "node1_id" - t.integer "node2_id" - t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "topics", :force => true do |t| - t.text "name" - t.text "desc" - t.text "link" - t.text "permission" - t.integer "user_id" - t.integer "metacode_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "image_file_name" - t.string "image_content_type" - t.integer "image_file_size" - t.datetime "image_updated_at" - t.string "audio_file_name" - t.string "audio_content_type" - t.integer "audio_file_size" - t.datetime "audio_updated_at" - end - - create_table "users", :force => true do |t| - t.string "name" - t.string "email" - t.text "settings" - t.string "code", :limit => 8 - t.string "joinedwithcode", :limit => 8 - t.string "crypted_password" - t.string "password_salt" - t.string "persistence_token" - t.string "perishable_token" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "encrypted_password", :limit => 128, :default => "" - t.string "remember_token" - t.datetime "remember_created_at" - t.string "reset_password_token" - t.datetime "last_sign_in_at" - t.string "last_sign_in_ip" - t.integer "sign_in_count", :default => 0 - t.datetime "current_sign_in_at" - t.string "current_sign_in_ip" - t.datetime "reset_password_sent_at" - t.boolean "admin" - t.string "image_file_name" - t.string "image_content_type" - t.integer "image_file_size" - t.datetime "image_updated_at" - end - - add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true - -end +# encoding: UTF-8 +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended to check this file into your version control system. + +ActiveRecord::Schema.define(:version => 20140707161810) do + + create_table "in_metacode_sets", :force => true do |t| + t.integer "metacode_id" + t.integer "metacode_set_id" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "in_metacode_sets", ["metacode_id"], :name => "index_in_metacode_sets_on_metacode_id" + add_index "in_metacode_sets", ["metacode_set_id"], :name => "index_in_metacode_sets_on_metacode_set_id" + + create_table "mappings", :force => true do |t| + t.text "category" + t.integer "xloc" + t.integer "yloc" + t.integer "topic_id" + t.integer "synapse_id" + t.integer "map_id" + t.integer "user_id" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + create_table "maps", :force => true do |t| + t.text "name" + t.boolean "arranged" + t.text "desc" + t.text "permission" + t.integer "user_id" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.boolean "featured" + end + + create_table "metacode_sets", :force => true do |t| + t.string "name" + t.text "desc" + t.integer "user_id" + t.boolean "mapperContributed" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "metacode_sets", ["user_id"], :name => "index_metacode_sets_on_user_id" + + create_table "metacodes", :force => true do |t| + t.text "name" + t.string "icon" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + create_table "synapses", :force => true do |t| + t.text "desc" + t.text "category" + t.text "weight" + t.text "permission" + t.integer "node1_id" + t.integer "node2_id" + t.integer "user_id" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + create_table "topics", :force => true do |t| + t.text "name" + t.text "desc" + t.text "link" + t.text "permission" + t.integer "user_id" + t.integer "metacode_id" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "image_file_name" + t.string "image_content_type" + t.integer "image_file_size" + t.datetime "image_updated_at" + t.string "audio_file_name" + t.string "audio_content_type" + t.integer "audio_file_size" + t.datetime "audio_updated_at" + end + + create_table "users", :force => true do |t| + t.string "name" + t.string "email" + t.text "settings" + t.string "code", :limit => 8 + t.string "joinedwithcode", :limit => 8 + t.string "crypted_password" + t.string "password_salt" + t.string "persistence_token" + t.string "perishable_token" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "encrypted_password", :limit => 128, :default => "" + t.string "remember_token" + t.datetime "remember_created_at" + t.string "reset_password_token" + t.datetime "last_sign_in_at" + t.string "last_sign_in_ip" + t.integer "sign_in_count", :default => 0 + t.datetime "current_sign_in_at" + t.string "current_sign_in_ip" + t.datetime "reset_password_sent_at" + t.boolean "admin" + t.string "image_file_name" + t.string "image_content_type" + t.integer "image_file_size" + t.datetime "image_updated_at" + end + + add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true + +end