// TODO document this user agent function
var labelType, useGradients, nativeTextSupport, animate;
(function () {
var ua = navigator.userAgent,
iStuff = ua.match(/iPhone/i) || ua.match(/iPad/i),
typeOfCanvas = typeof HTMLCanvasElement,
nativeCanvasSupport = (typeOfCanvas == 'object' || typeOfCanvas == 'function'),
textSupport = nativeCanvasSupport && (typeof document.createElement('canvas').getContext('2d').fillText == 'function');
//I'm setting this based on the fact that ExCanvas provides text support for IE
//and that as of today iPhone/iPad current text support is lame
labelType = (!nativeCanvasSupport || (textSupport && !iStuff)) ? 'Native' : 'HTML';
nativeTextSupport = labelType == 'Native';
useGradients = nativeCanvasSupport;
animate = !(iStuff || !nativeCanvasSupport);
})();
// TODO eliminate these 5 top-level variables
Metamaps.panningInt = null;
Metamaps.tempNode = null;
Metamaps.tempInit = false;
Metamaps.tempNode2 = null;
Metamaps.VERSION = '<%= METAMAPS_VERSION %>'
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: '#888888',
hover: '#888888',
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,
didBoxZoom: false,
changeInX: 0,
changeInY: 0,
edgeHoveringOver: false,
boxStartCoordinates: false,
boxEndCoordinates: false,
synapseStartCoordinates: [],
synapseEndCoordinates: null,
lastNodeClick: 0,
lastCanvasClick: 0,
DOUBLE_CLICK_TOLERANCE: 300
};
Metamaps.Selected = {
reset: function () {
var self = Metamaps.Selected;
self.Nodes = [];
self.Edges = [];
},
Nodes: [],
Edges: []
};
/*
*
* BACKBONE
*
*/
Metamaps.Backbone.init = function () {
var self = Metamaps.Backbone;
self.Metacode = Backbone.Model.extend({
initialize: function () {
var image = new Image();
image.crossOrigin = "Anonymous";
image.src = this.get('icon');
this.set('image',image);
},
prepareLiForFilter: function () {
var li = '';
li += '
';
li += '';
li += '
' + this.get('name').toLowerCase() + '
';
return li;
}
});
self.MetacodeCollection = Backbone.Collection.extend({
model: this.Metacode,
url: '/metacodes',
comparator: function (a, b) {
a = a.get('name').toLowerCase();
b = b.get('name').toLowerCase();
return a > b ? 1 : a < b ? -1 : 0;
}
});
self.Topic = Backbone.Model.extend({
urlRoot: '/topics',
blacklist: ['node', 'created_at', 'updated_at', 'user_name', 'user_image', 'map_count', 'synapse_count'],
toJSON: function (options) {
return _.omit(this.attributes, this.blacklist);
},
save: function (key, val, options) {
var attrs;
// Handle both `"key", value` and `{key: value}` -style arguments.
if (key == null || typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
var newOptions = options || {};
var s = newOptions.success;
var permBefore = this.get('permission');
newOptions.success = function (model, response, opt) {
if (s) s(model, response, opt);
model.trigger('saved');
if (permBefore === 'private' && model.get('permission') !== 'private') {
model.trigger('noLongerPrivate');
}
else if (permBefore !== 'private' && model.get('permission') === 'private') {
model.trigger('nowPrivate');
}
};
return Backbone.Model.prototype.save.call(this, attrs, newOptions);
},
initialize: function () {
if (this.isNew()) {
this.set({
"user_id": Metamaps.Active.Mapper.id,
"desc": '',
"link": '',
"permission": Metamaps.Active.Map ? Metamaps.Active.Map.get('permission') : 'commons'
});
}
this.on('changeByOther', this.updateCardView);
this.on('change', this.updateNodeView);
this.on('saved', this.savedEvent);
this.on('nowPrivate', function(){
var removeTopicData = {
mappableid: this.id
};
$(document).trigger(Metamaps.JIT.events.removeTopic, [removeTopicData]);
});
this.on('noLongerPrivate', function(){
var newTopicData = {
mappingid: this.getMapping().id,
mappableid: this.id
};
$(document).trigger(Metamaps.JIT.events.newTopic, [newTopicData]);
});
this.on('change:metacode_id', Metamaps.Filter.checkMetacodes, this);
},
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 () {
},
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,
mappable_type: "Topic",
mappable_id: this.isNew() ? this.cid : 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);
if (Metamaps.Active.Map) {
mapping = this.getMapping();
node.setData('mapping', mapping);
}
return node;
},
savedEvent: function() {
Metamaps.Realtime.sendTopicChange(this);
},
updateViews: function() {
var onPageWithTopicCard = Metamaps.Active.Map || Metamaps.Active.Topic;
var node = this.get('node');
// update topic card, if this topic is the one open there
if (onPageWithTopicCard && this == Metamaps.TopicCard.openTopicCard) {
Metamaps.TopicCard.showCard(node);
}
// update the node on the map
if (onPageWithTopicCard && node) {
node.name = this.get('name');
Metamaps.Visualize.mGraph.plot();
}
},
updateCardView: function() {
var onPageWithTopicCard = Metamaps.Active.Map || Metamaps.Active.Topic;
var node = this.get('node');
// update topic card, if this topic is the one open there
if (onPageWithTopicCard && this == Metamaps.TopicCard.openTopicCard) {
Metamaps.TopicCard.showCard(node);
}
},
updateNodeView: function() {
var onPageWithTopicCard = Metamaps.Active.Map || Metamaps.Active.Topic;
var node = this.get('node');
// update the node on the map
if (onPageWithTopicCard && node) {
node.name = this.get('name');
Metamaps.Visualize.mGraph.plot();
}
}
});
self.TopicCollection = Backbone.Collection.extend({
model: self.Topic,
url: '/topics'
});
self.Synapse = Backbone.Model.extend({
urlRoot: '/synapses',
blacklist: ['edge', 'created_at', 'updated_at'],
toJSON: function (options) {
return _.omit(this.attributes, this.blacklist);
},
save: function (key, val, options) {
var attrs;
// Handle both `"key", value` and `{key: value}` -style arguments.
if (key == null || typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
var newOptions = options || {};
var s = newOptions.success;
var permBefore = this.get('permission');
newOptions.success = function (model, response, opt) {
if (s) s(model, response, opt);
model.trigger('saved');
if (permBefore === 'private' && model.get('permission') !== 'private') {
model.trigger('noLongerPrivate');
}
else if (permBefore !== 'private' && model.get('permission') === 'private') {
model.trigger('nowPrivate');
}
};
return Backbone.Model.prototype.save.call(this, attrs, newOptions);
},
initialize: function () {
if (this.isNew()) {
this.set({
"user_id": Metamaps.Active.Mapper.id,
"permission": Metamaps.Active.Map ? Metamaps.Active.Map.get('permission') : 'commons',
"category": "from-to"
});
}
this.on('changeByOther', this.updateCardView);
this.on('change', this.updateEdgeView);
this.on('saved', this.savedEvent);
this.on('noLongerPrivate', function(){
var newSynapseData = {
mappingid: this.getMapping().id,
mappableid: this.id
};
$(document).trigger(Metamaps.JIT.events.newSynapse, [newSynapseData]);
});
this.on('nowPrivate', function(){
$(document).trigger(Metamaps.JIT.events.removeSynapse, [{
mappableid: this.id
}]);
});
this.on('change:desc', Metamaps.Filter.checkSynapses, this);
},
prepareLiForFilter: function () {
var li = '';
li += '
';
li += '';
li += '
' + this.get('desc') + '
';
return li;
},
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;
},
getTopic1: function () {
return Metamaps.Topics.get(this.get('node1_id'));
},
getTopic2: function () {
return Metamaps.Topics.get(this.get('node2_id'));
},
getDirection: function () {
var t1 = this.getTopic1(),
t2 = this.getTopic2();
return t1 && t2 ? [
t1.get('node').id,
t2.get('node').id
] : false;
},
getMapping: function () {
if (!Metamaps.Active.Map) return false;
return Metamaps.Mappings.findWhere({
map_id: Metamaps.Active.Map.id,
mappable_type: "Synapse",
mappable_id: this.isNew() ? this.cid : this.id
});
},
createEdge: function (providedMapping) {
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 = providedMapping || 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;
},
savedEvent: function() {
Metamaps.Realtime.sendSynapseChange(this);
},
updateViews: function() {
this.updateCardView();
this.updateEdgeView();
},
updateCardView: function() {
var onPageWithSynapseCard = Metamaps.Active.Map || Metamaps.Active.Topic;
var edge = this.get('edge');
// update synapse card, if this synapse is the one open there
if (onPageWithSynapseCard && edge == Metamaps.SynapseCard.openSynapseCard) {
Metamaps.SynapseCard.showCard(edge);
}
},
updateEdgeView: function() {
var onPageWithSynapseCard = Metamaps.Active.Map || Metamaps.Active.Topic;
var edge = this.get('edge');
// update the edge on the map
if (onPageWithSynapseCard && edge) {
Metamaps.Visualize.mGraph.plot();
}
}
});
self.SynapseCollection = Backbone.Collection.extend({
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
});
}
},
getMap: function () {
return Metamaps.Map.get(this.get('map_id'));
},
getTopic: function () {
if (this.get('mappable_type') === 'Topic') return Metamaps.Topic.get(this.get('mappable_id'));
else return false;
},
getSynapse: function () {
if (this.get('mappable_type') === 'Synapse') return Metamaps.Synapse.get(this.get('mappable_id'));
else return false;
}
});
self.MappingCollection = Backbone.Collection.extend({
model: self.Mapping,
url: '/mappings'
});
Metamaps.Metacodes = Metamaps.Metacodes ? new self.MetacodeCollection(Metamaps.Metacodes) : new self.MetacodeCollection();
Metamaps.Topics = Metamaps.Topics ? new self.TopicCollection(Metamaps.Topics) : new self.TopicCollection();
Metamaps.Synapses = Metamaps.Synapses ? new self.SynapseCollection(Metamaps.Synapses) : new self.SynapseCollection();
Metamaps.Mappers = Metamaps.Mappers ? new self.MapperCollection(Metamaps.Mappers) : new self.MapperCollection();
// this is for topic view
Metamaps.Creators = Metamaps.Creators ? new self.MapperCollection(Metamaps.Creators) : new self.MapperCollection();
if (Metamaps.Active.Map) {
Metamaps.Mappings = Metamaps.Mappings ? new self.MappingCollection(Metamaps.Mappings) : new self.MappingCollection();
Metamaps.Active.Map = new self.Map(Metamaps.Active.Map);
}
if (Metamaps.Active.Topic) Metamaps.Active.Topic = new self.Topic(Metamaps.Active.Topic);
//attach collection event listeners
self.attachCollectionEvents = function () {
Metamaps.Topics.on("add remove", function(topic){
Metamaps.Map.InfoBox.updateNumbers();
Metamaps.Filter.checkMetacodes();
Metamaps.Filter.checkMappers();
});
Metamaps.Synapses.on("add remove", function(synapse){
Metamaps.Map.InfoBox.updateNumbers();
Metamaps.Filter.checkSynapses();
Metamaps.Filter.checkMappers();
});
if (Metamaps.Active.Map) {
Metamaps.Mappings.on("add remove", function(mapping){
Metamaps.Map.InfoBox.updateNumbers();
Metamaps.Filter.checkSynapses();
Metamaps.Filter.checkMetacodes();
Metamaps.Filter.checkMappers();
});
}
}
self.attachCollectionEvents();
}; // end Metamaps.Backbone.init
/*
*
* CREATE
*
*/
Metamaps.Create = {
isSwitchingSet: false, // indicates whether the metacode set switch lightbox is open
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 codesToSwitchToIds;
var metacodeModels = new Metamaps.Backbone.MetacodeCollection();
Metamaps.Create.selectedMetacodeSetIndex = index;
Metamaps.Create.selectedMetacodeSet = "metacodeset-" + set;
if (!custom) {
codesToSwitchToIds = $('#metacodeSwitchTabs' + set).attr('data-metacodes').split(',');
$('.customMetacodeList li').addClass('toggledOff');
Metamaps.Create.selectedMetacodes = [];
Metamaps.Create.selectedMetacodeNames = [];
Metamaps.Create.newSelectedMetacodes = [];
Metamaps.Create.newSelectedMetacodeNames = [];
}
else 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);
codesToSwitchToIds = Metamaps.Create.selectedMetacodes.slice(0);
}
// sort by name
for (var i = 0; i < codesToSwitchToIds.length; i++) {
metacodeModels.add( Metamaps.Metacodes.get(codesToSwitchToIds[i]) );
};
metacodeModels.sort();
$('#metacodeImg, #metacodeImgTitle').empty();
$('#metacodeImg').removeData('cloudcarousel');
var newMetacodes = "";
metacodeModels.each(function(metacode){
newMetacodes += '';
});
$('#metacodeImg').empty().append(newMetacodes).CloudCarousel({
titleBox: $('#metacodeImgTitle'),
yRadius: 40,
xRadius: 190,
xPos: 170,
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("option", "active", self.selectedMetacodeSetIndex);
$('#topic_name').focus();
},
newTopic: {
init: function () {
$('#topic_name').keyup(function () {
Metamaps.Create.newTopic.name = $(this).val();
});
var topicBloodhound = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: '/topics/autocomplete_topic?term=%QUERY',
wildcard: '%QUERY',
},
});
// initialize the autocomplete results for the metacode spinner
$('#topic_name').typeahead(
{
highlight: true,
minLength: 2,
},
[{
name: 'topic_autocomplete',
limit: 8,
display: function (s) { return s.label; },
templates: {
suggestion: function(s) {
return Hogan.compile($('#topicAutocompleteTemplate').html()).render(s);
},
},
source: topicBloodhound,
}]
);
// tell the autocomplete to submit the form with the topic you clicked on if you pick from the autocomplete
$('#topic_name').bind('typeahead:select', function (event, datum, dataset) {
Metamaps.Topic.getTopicFromAutocomplete(datum.id);
});
// initialize metacode spinner and then hide it
$("#metacodeImg").CloudCarousel({
titleBox: $('#metacodeImgTitle'),
yRadius: 40,
xRadius: 190,
xPos: 170,
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;
Metamaps.Create.newTopic.name = "";
},
hide: function () {
$('#new_topic').fadeOut('fast');
$("#topic_name").typeahead('val', '');
Metamaps.Create.newTopic.beingCreated = false;
}
},
newSynapse: {
init: function () {
var self = Metamaps.Create.newSynapse;
var synapseBloodhound = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: '/search/synapses?term=%QUERY',
wildcard: '%QUERY',
},
});
var existingSynapseBloodhound = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: '/search/synapses?topic1id=%TOPIC1&topic2id=%TOPIC2',
prepare: function(query, settings) {
var self = Metamaps.Create.newSynapse;
if (Metamaps.Selected.Nodes.length < 2) {
settings.url = settings.url.replace("%TOPIC1", self.topic1id).replace("%TOPIC2", self.topic2id);
return settings;
} else {
return null;
}
},
},
});
// initialize the autocomplete results for synapse creation
$('#synapse_desc').typeahead(
{
highlight: true,
minLength: 2,
},
[{
name: 'synapse_autocomplete',
display: function(s) { return s.label; },
templates: {
suggestion: function(s) {
return Hogan.compile("
"
},
source: existingSynapseBloodhound,
}]
);
$('#synapse_desc').keyup(function (e) {
var ESC = 27, BACKSPACE = 8, DELETE = 46;
if (e.keyCode === BACKSPACE && $(this).val() === "" ||
e.keyCode === DELETE && $(this).val() === "" ||
e.keyCode === ESC) {
Metamaps.Create.newSynapse.hide();
}//if
Metamaps.Create.newSynapse.description = $(this).val();
});
$('#synapse_desc').focusout(function() {
if (Metamaps.Create.newSynapse.beingCreated) {
Metamaps.Synapse.createSynapseLocally();
}
});
$('#synapse_desc').bind('typeahead:select', function (event, datum, dataset) {
if (datum.id) { // if they clicked on an existing synapse get it
Metamaps.Synapse.getSynapseFromAutocomplete(datum.id);
}
else {
Metamaps.Create.newSynapse.description = datum.value;
Metamaps.Synapse.createSynapseLocally();
}
});
},
beingCreated: false,
description: null,
topic1id: null,
topic2id: null,
newSynapseId: null,
open: function () {
$('#new_synapse').fadeIn(100, function () {
$('#synapse_desc').focus();
});
Metamaps.Create.newSynapse.beingCreated = true;
},
hide: function () {
$('#new_synapse').fadeOut('fast');
$("#synapse_desc").typeahead('val', '');
Metamaps.Create.newSynapse.beingCreated = false;
Metamaps.Create.newTopic.addSynapse = false;
Metamaps.Create.newSynapse.topic1id = 0;
Metamaps.Create.newSynapse.topic2id = 0;
Metamaps.Mouse.synapseStartCoordinates = [];
Metamaps.Visualize.mGraph.plot();
},
}
}; // end Metamaps.Create
////////////////// TOPIC AND SYNAPSE CARDS //////////////////////////
/*
*
* TOPICCARD
*
*/
Metamaps.TopicCard = {
openTopicCard: null, //stores the topic that's currently open
authorizedToEdit: false, // stores boolean for edit permission for open topic card
init: function () {
var self = Metamaps.TopicCard;
// initialize best_in_place editing
$('.authenticated div.permission.canEdit .best_in_place').best_in_place();
Metamaps.TopicCard.generateShowcardHTML = Hogan.compile($('#topicCardTemplate').html());
// initialize topic card draggability and resizability
$('.showcard').draggable({
handle: ".metacodeImage"
});
embedly('on', 'card.rendered', self.embedlyCardRendered);
},
/**
* Will open the Topic Card for the node that it's passed
* @param {$jit.Graph.Node} node
*/
showCard: function (node) {
var self = Metamaps.TopicCard;
var topic = node.getData('topic');
self.openTopicCard = topic;
self.authorizedToEdit = topic.authorizeToEdit(Metamaps.Active.Mapper);
//populate the card that's about to show with the right topics data
self.populateShowCard(topic);
$('.showcard').fadeIn('fast');
},
hideCard: function () {
var self = Metamaps.TopicCard;
$('.showcard').fadeOut('fast');
self.openTopicCard = null;
self.authorizedToEdit = false;
},
embedlyCardRendered: function (iframe) {
var self = Metamaps.TopicCard;
$('#embedlyLinkLoader').hide();
// means that the embedly call returned 404 not found
if ($('#embedlyLink')[0]) {
$('#embedlyLink').css('display', 'block').fadeIn('fast');
$('.embeds').addClass('nonEmbedlyLink');
}
$('.CardOnGraph').addClass('hasAttachment');
if (self.authorizedToEdit) {
$('.embeds').append('');
$('#linkremove').click(self.removeLink);
}
},
removeLink: function () {
var self = Metamaps.TopicCard;
self.openTopicCard.save({
link: null
});
$('.embeds').empty().removeClass('nonEmbedlyLink');
$('#addLinkInput input').val("");
$('.attachments').removeClass('hidden');
$('.CardOnGraph').removeClass('hasAttachment');
},
bindShowCardListeners: function (topic) {
var self = Metamaps.TopicCard;
var showCard = document.getElementById('showcard');
var authorized = self.authorizedToEdit;
// get mapper image
var setMapperImage = function (mapper) {
$('.contributorIcon').attr('src', mapper.get('image'));
};
Metamaps.Mapper.get(topic.get('user_id'), setMapperImage);
// starting embed.ly
var resetFunc = function () {
$('#addLinkInput input').val("");
$('#addLinkInput input').focus();
};
var inputEmbedFunc = function (event) {
var element = this;
setTimeout(function () {
var text = $(element).val();
if (event.type=="paste" || (event.type=="keyup" && event.which==13)){
// TODO evaluate converting this to '//' no matter what (infer protocol)
if (text.slice(0, 7) !== 'http://' &&
text.slice(0, 8) !== 'https://' &&
text.slice(0, 2) !== '//') {
text='//'+text;
}
topic.save({
link: text
});
var embedlyEl = $('', {
id: 'embedlyLink',
'data-card-description': '0',
href: text
}).html(text);
$('.attachments').addClass('hidden');
$('.embeds').append(embedlyEl);
$('.embeds').append('');
var loader = new CanvasLoader('embedlyLinkLoader');
loader.setColor('#4fb5c0'); // default is '#000000'
loader.setDiameter(28); // default is 40
loader.setDensity(41); // default is 40
loader.setRange(0.9); // default is 1.3
loader.show(); // Hidden by default
var e = embedly('card', document.getElementById('embedlyLink'));
if (!e) {
self.handleInvalidLink();
}
}
}, 100);
};
$('#addLinkReset').click(resetFunc);
$('#addLinkInput input').bind("paste keyup",inputEmbedFunc);
// initialize the link card, if there is a link
if (topic.get('link') && topic.get('link') !== '') {
var loader = new CanvasLoader('embedlyLinkLoader');
loader.setColor('#4fb5c0'); // default is '#000000'
loader.setDiameter(28); // default is 40
loader.setDensity(41); // default is 40
loader.setRange(0.9); // default is 1.3
loader.show(); // Hidden by default
var e = embedly('card', document.getElementById('embedlyLink'));
if (!e) {
self.handleInvalidLink();
}
}
var selectingMetacode = false;
// attach the listener that shows the metacode title when you hover over the image
$('.showcard .metacodeImage').mouseenter(function () {
$('.showcard .icon').css('z-index', '4');
$('.showcard .metacodeTitle').show();
});
$('.showcard .linkItem.icon').mouseleave(function () {
if (!selectingMetacode) {
$('.showcard .metacodeTitle').hide();
$('.showcard .icon').css('z-index', '1');
}
});
var metacodeLiClick = function () {
selectingMetacode = false;
var metacodeId = parseInt($(this).attr('data-id'));
var metacode = Metamaps.Metacodes.get(metacodeId);
$('.CardOnGraph').find('.metacodeTitle').html(metacode.get('name'))
.append('')
.attr('class', 'metacodeTitle mbg' + metacode.id);
$('.CardOnGraph').find('.metacodeImage').css('background-image', 'url(' + metacode.get('icon') + ')');
topic.save({
metacode_id: metacode.id
});
Metamaps.Visualize.mGraph.plot();
$('.metacodeSelect').hide().removeClass('onRightEdge onBottomEdge');
$('.metacodeTitle').hide();
$('.showcard .icon').css('z-index', '1');
};
var openMetacodeSelect = function (event) {
var windowWidth;
var showcardLeft;
var TOPICCARD_WIDTH = 300;
var METACODESELECT_WIDTH = 404;
var distanceFromEdge;
var MAX_METACODELIST_HEIGHT = 270;
var windowHeight;
var showcardTop;
var topicTitleHeight;
var distanceFromBottom;
if (!selectingMetacode) {
selectingMetacode = true;
// this is to make sure the metacode
// select is accessible onscreen, when opened
// while topic card is close to the right
// edge of the screen
windowWidth = $(window).width();
showcardLeft = parseInt($('.showcard').css('left'));
distanceFromEdge = windowWidth - (showcardLeft + TOPICCARD_WIDTH);
if (distanceFromEdge < METACODESELECT_WIDTH) {
$('.metacodeSelect').addClass('onRightEdge');
}
// this is to make sure the metacode
// select is accessible onscreen, when opened
// while topic card is close to the bottom
// edge of the screen
windowHeight = $(window).height();
showcardTop = parseInt($('.showcard').css('top'));
topicTitleHeight = $('.showcard .title').height() + parseInt($('.showcard .title').css('padding-top')) + parseInt($('.showcard .title').css('padding-bottom'));
heightOfSetList = $('.showcard .metacodeSelect').height();
distanceFromBottom = windowHeight - (showcardTop + topicTitleHeight);
if (distanceFromBottom < MAX_METACODELIST_HEIGHT) {
$('.metacodeSelect').addClass('onBottomEdge');
}
$('.metacodeSelect').show();
event.stopPropagation();
}
};
var hideMetacodeSelect = function () {
selectingMetacode = false;
$('.metacodeSelect').hide().removeClass('onRightEdge onBottomEdge');
$('.metacodeTitle').hide();
$('.showcard .icon').css('z-index', '1');
};
if (authorized) {
$('.showcard .metacodeTitle').click(openMetacodeSelect);
$('.showcard').click(hideMetacodeSelect);
$('.metacodeSelect > ul > li').click(function (event){
event.stopPropagation();
});
$('.metacodeSelect li li').click(metacodeLiClick);
var bipName = $(showCard).find('.best_in_place_name');
bipName.bind("best_in_place:activate", function () {
var $el = bipName.find('textarea');
var el = $el[0];
$el.attr('maxlength', '140');
$('.showcard .title').append('');
var callback = function (data) {
$('.nameCounter.forTopic').html(data.all + '/140');
};
Countable.live(el, callback);
});
bipName.bind("best_in_place:deactivate", function () {
$('.nameCounter.forTopic').remove();
});
//bind best_in_place ajax callbacks
bipName.bind("ajax:success", function () {
var name = Metamaps.Util.decodeEntities($(this).html());
topic.set("name", name);
topic.trigger('saved');
});
$(showCard).find('.best_in_place_desc').bind("ajax:success", function () {
this.innerHTML = this.innerHTML.replace(/\r/g, '');
var desc = $(this).html() === $(this).data('nil') ? "" : $(this).html();
topic.set("desc", desc);
topic.trigger('saved');
});
}
var permissionLiClick = function (event) {
selectingPermission = false;
var permission = $(this).attr('class');
topic.save({
permission: permission
});
$('.showcard .mapPerm').removeClass('co pu pr minimize').addClass(permission.substring(0, 2));
$('.showcard .permissionSelect').remove();
event.stopPropagation();
};
var openPermissionSelect = function (event) {
if (!selectingPermission) {
selectingPermission = true;
$(this).addClass('minimize'); // this line flips the drop down arrow to a pull up arrow
if ($(this).hasClass('co')) {
$(this).append('
');
} else if ($(this).hasClass('pu')) {
$(this).append('
');
} else if ($(this).hasClass('pr')) {
$(this).append('
');
}
$('.showcard .permissionSelect li').click(permissionLiClick);
event.stopPropagation();
}
};
var hidePermissionSelect = function () {
selectingPermission = false;
$('.showcard .yourTopic .mapPerm').removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow
$('.showcard .permissionSelect').remove();
};
// ability to change permission
var selectingPermission = false;
if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) {
$('.showcard .yourTopic .mapPerm').click(openPermissionSelect);
$('.showcard').click(hidePermissionSelect);
}
$('.links .mapCount').unbind().click(function(event){
$('.mapCount .tip').toggle();
$('.showcard .hoverTip').toggleClass('hide');
event.stopPropagation();
});
$('.mapCount .tip').unbind().click(function(event){
event.stopPropagation();
});
$('.showcard').unbind('.hideTip').bind('click.hideTip', function(){
$('.mapCount .tip').hide();
$('.showcard .hoverTip').removeClass('hide');
});
$('.mapCount .tip li a').click(Metamaps.Router.intercept);
var originalText = $('.showMore').html();
$('.mapCount .tip .showMore').unbind().toggle(
function(event){
$('.extraText').toggleClass("hideExtra");
$('.showMore').html('Show less...');
},
function(event){
$('.extraText').toggleClass("hideExtra");
$('.showMore').html(originalText);
});
$('.mapCount .tip showMore').unbind().click(function(event){
event.stopPropagation();
});
},
handleInvalidLink: function() {
var self = Metamaps.TopicCard;
self.removeLink();
Metamaps.GlobalUI.notifyUser("Invalid link");
},
populateShowCard: function (topic) {
var self = Metamaps.TopicCard;
var showCard = document.getElementById('showcard');
$(showCard).find('.permission').remove();
var topicForTemplate = self.buildObject(topic);
var html = self.generateShowcardHTML.render(topicForTemplate);
if (topic.authorizeToEdit(Metamaps.Active.Mapper)) {
var perm = document.createElement('div');
var string = 'permission canEdit';
if (topic.authorizePermissionChange(Metamaps.Active.Mapper)) string += ' yourTopic';
perm.className = string;
perm.innerHTML = html;
showCard.appendChild(perm);
} else {
var perm = document.createElement('div');
perm.className = 'permission cannotEdit';
perm.innerHTML = html;
showCard.appendChild(perm);
}
Metamaps.TopicCard.bindShowCardListeners(topic);
},
generateShowcardHTML: null, // will be initialized into a Hogan template within init function
//generateShowcardHTML
buildObject: function (topic) {
var self=Metamaps.TopicCard;
var nodeValues = {};
var authorized = topic.authorizeToEdit(Metamaps.Active.Mapper);
if (!authorized) {
} else {
}
var desc_nil = "Click to add description...";
nodeValues.attachmentsHidden = '';
if (topic.get('link') && topic.get('link')!== '') {
nodeValues.embeds = '';
nodeValues.embeds += topic.get('link');
nodeValues.embeds += '';
nodeValues.attachmentsHidden = 'hidden';
nodeValues.hasAttachment = "hasAttachment";
}
else {
nodeValues.embeds = '';
nodeValues.hasAttachment = '';
}
if (authorized) {
nodeValues.attachments = '
';
nodeValues.attachments += '
';
nodeValues.attachments += '
';
} else {
nodeValues.attachmentsHidden = 'hidden';
nodeValues.attachments = '';
}
var inmapsAr = topic.get("inmaps");
var inmapsLinks = topic.get("inmapsLinks");
nodeValues.inmaps ='';
if (inmapsAr.length < 6) {
for (i = 0; i < inmapsAr.length; i++) {
var url = "/maps/" + inmapsLinks[i];
nodeValues.inmaps += '
';
}
}
nodeValues.permission = topic.get("permission");
nodeValues.mk_permission = topic.get("permission").substring(0, 2);
nodeValues.map_count = topic.get("map_count").toString();
nodeValues.synapse_count = topic.get("synapse_count").toString();
nodeValues.id = topic.isNew() ? topic.cid : topic.id;
nodeValues.metacode = topic.getMetacode().get("name");
nodeValues.metacode_class = 'mbg' + topic.get('metacode_id');
nodeValues.imgsrc = topic.getMetacode().get("icon");
nodeValues.name = topic.get("name");
nodeValues.userid = topic.get("user_id");
nodeValues.username = topic.get("user_name");
nodeValues.date = topic.getDate();
// the code for this is stored in /views/main/_metacodeOptions.html.erb
nodeValues.metacode_select = $('#metacodeOptions').html();
nodeValues.desc_nil = desc_nil;
nodeValues.desc = (topic.get("desc") == "" && authorized) ? desc_nil : topic.get("desc");
return nodeValues;
}
}; // end Metamaps.TopicCard
/*
*
* SYNAPSECARD
*
*/
Metamaps.SynapseCard = {
openSynapseCard: null,
showCard: function (edge, e) {
var self = Metamaps.SynapseCard;
//reset so we don't interfere with other edges, but first, save its x and y
var myX = $('#edit_synapse').css('left');
var myY = $('#edit_synapse').css('top');
$('#edit_synapse').remove();
//so label is missing while editing
Metamaps.Control.deselectEdge(edge);
var index = edge.getData("displayIndex") ? edge.getData("displayIndex") : 0;
var synapse = edge.getData('synapses')[index]; // for now, just get the first synapse
//create the wrapper around the form elements, including permissions
//classes to make best_in_place happy
var edit_div = document.createElement('div');
edit_div.innerHTML = '';
edit_div.setAttribute('id', 'edit_synapse');
if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) {
edit_div.className = 'permission canEdit';
edit_div.className += synapse.authorizePermissionChange(Metamaps.Active.Mapper) ? ' yourEdge' : '';
} else {
edit_div.className = 'permission cannotEdit';
}
$('#wrapper').append(edit_div);
self.populateShowCard(edge, synapse);
//drop it in the right spot, activate it
$('#edit_synapse').css('position', 'absolute');
if (e) {
$('#edit_synapse').css('left', e.clientX);
$('#edit_synapse').css('top', e.clientY);
} else {
$('#edit_synapse').css('left', myX);
$('#edit_synapse').css('top', myY);
}
//$('#edit_synapse_name').click(); //required in case name is empty
//$('#edit_synapse_name input').focus();
$('#edit_synapse').show();
self.openSynapseCard = edge;
},
hideCard: function () {
$('#edit_synapse').remove();
Metamaps.SynapseCard.openSynapseCard = null;
},
populateShowCard: function (edge, synapse) {
var self = Metamaps.SynapseCard;
self.add_synapse_count(edge);
self.add_desc_form(synapse);
self.add_drop_down(edge, synapse);
self.add_user_info(synapse);
self.add_perms_form(synapse);
self.add_direction_form(synapse);
},
add_synapse_count: function (edge) {
var count = edge.getData("synapses").length;
$('#editSynUpperBar').append('
' + count + '
')
},
add_desc_form: function (synapse) {
var data_nil = 'Click to add description.';
// TODO make it so that this would work even in sandbox mode,
// currently with Best_in_place it won't
//desc editing form
$('#editSynUpperBar').append('');
$('#edit_synapse_desc').attr('class', 'best_in_place best_in_place_desc');
$('#edit_synapse_desc').attr('data-object', 'synapse');
$('#edit_synapse_desc').attr('data-attribute', 'desc');
$('#edit_synapse_desc').attr('data-type', 'textarea');
$('#edit_synapse_desc').attr('data-nil', data_nil);
$('#edit_synapse_desc').attr('data-url', '/synapses/' + synapse.id);
$('#edit_synapse_desc').html(synapse.get("desc"));
//if edge data is blank or just whitespace, populate it with data_nil
if ($('#edit_synapse_desc').html().trim() == '') {
if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) {
$('#edit_synapse_desc').html(data_nil);
}
else {
$('#edit_synapse_desc').html("(no description)");
}
}
$('#edit_synapse_desc').bind("ajax:success", function () {
var desc = $(this).html();
if (desc == data_nil) {
synapse.set("desc", '');
} else {
synapse.set("desc", desc);
}
synapse.trigger('saved');
Metamaps.Control.selectEdge(synapse.get('edge'));
Metamaps.Visualize.mGraph.plot();
});
},
add_drop_down: function (edge, synapse) {
var list, i, synapses, l, desc;
synapses = edge.getData("synapses");
l = synapses.length;
if (l > 1) {
// append the element that you click to show dropdown select
$('#editSynUpperBar').append('');
$('#dropdownSynapses').click(function(e){
e.preventDefault();
e.stopPropagation(); // stop it from immediately closing it again
$('#switchSynapseList').toggle();
});
// hide the dropdown again if you click anywhere else on the synapse card
$('#edit_synapse').click(function(){
$('#switchSynapseList').hide();
});
// generate the list of other synapses
list = '
';
for (i = 0; i < l; i++) {
if (synapses[i] !== synapse) { // don't add the current one to the list
desc = synapses[i].get('desc');
desc = desc === "" || desc === null ? "(no description)" : desc;
list += '
' + desc + '
';
}
}
list += '
'
// add the list of the other synapses
$('#editSynLowerBar').append(list);
// attach click listeners to list items that
// will cause it to switch the displayed synapse
// when you click it
$('#switchSynapseList li').click(function(e){
e.stopPropagation();
var index = parseInt($(this).attr('data-synapse-index'));
edge.setData('displayIndex', index);
Metamaps.Visualize.mGraph.plot();
Metamaps.SynapseCard.showCard(edge, false);
});
}
},
add_user_info: function (synapse) {
var u = '
';
u += ''
u += '
' + synapse.get("user_name") + '
';
$('#editSynLowerBar').append(u);
// get mapper image
var setMapperImage = function (mapper) {
$('#edgeUser img').attr('src', mapper.get('image'));
};
Metamaps.Mapper.get(synapse.get('user_id'), setMapperImage);
},
add_perms_form: function (synapse) {
//permissions - if owner, also allow permission editing
$('#editSynLowerBar').append('');
// ability to change permission
var selectingPermission = false;
var permissionLiClick = function (event) {
selectingPermission = false;
var permission = $(this).attr('class');
synapse.save({
permission: permission
});
$('#edit_synapse .mapPerm').removeClass('co pu pr minimize').addClass(permission.substring(0, 2));
$('#edit_synapse .permissionSelect').remove();
event.stopPropagation();
};
var openPermissionSelect = function (event) {
if (!selectingPermission) {
selectingPermission = true;
$(this).addClass('minimize'); // this line flips the drop down arrow to a pull up arrow
if ($(this).hasClass('co')) {
$(this).append('
');
} else if ($(this).hasClass('pu')) {
$(this).append('
');
} else if ($(this).hasClass('pr')) {
$(this).append('
');
}
$('#edit_synapse .permissionSelect li').click(permissionLiClick);
event.stopPropagation();
}
};
var hidePermissionSelect = function () {
selectingPermission = false;
$('#edit_synapse.yourEdge .mapPerm').removeClass('minimize'); // this line flips the pull up arrow to a drop down arrow
$('#edit_synapse .permissionSelect').remove();
};
if (synapse.authorizePermissionChange(Metamaps.Active.Mapper)) {
$('#edit_synapse.yourEdge .mapPerm').click(openPermissionSelect);
$('#edit_synapse').click(hidePermissionSelect);
}
}, //add_perms_form
add_direction_form: function (synapse) {
//directionality checkboxes
$('#editSynLowerBar').append('');
$('#editSynLowerBar').append('');
var edge = synapse.get('edge');
//determine which node is to the left and the right
//if directly in a line, top is left
if (edge.nodeFrom.pos.x < edge.nodeTo.pos.x ||
edge.nodeFrom.pos.x == edge.nodeTo.pos.x &&
edge.nodeFrom.pos.y < edge.nodeTo.pos.y) {
var left = edge.nodeTo.getData("topic");
var right = edge.nodeFrom.getData("topic");
} else {
var left = edge.nodeFrom.getData("topic");
var right = edge.nodeTo.getData("topic");
}
/*
* One node is actually on the left onscreen. Call it left, & the other right.
* If category is from-to, and that node is first, check the 'right' checkbox.
* Else check the 'left' checkbox since the arrow is incoming.
*/
var directionCat = synapse.get('category'); //both, none, from-to
if (directionCat == 'from-to') {
var from_to = [synapse.get("node1_id"), synapse.get("node2_id")];
if (from_to[0] == left.id) {
//check left checkbox
$('#edit_synapse_left').addClass('checked');
} else {
//check right checkbox
$('#edit_synapse_right').addClass('checked');
}
} else if (directionCat == 'both') {
//check both checkboxes
$('#edit_synapse_left').addClass('checked');
$('#edit_synapse_right').addClass('checked');
}
if (synapse.authorizeToEdit(Metamaps.Active.Mapper)) {
$('#edit_synapse_left, #edit_synapse_right').click(function () {
$(this).toggleClass('checked');
var leftChecked = $('#edit_synapse_left').is('.checked');
var rightChecked = $('#edit_synapse_right').is('.checked');
var dir = synapse.getDirection();
var dirCat = 'none';
if (leftChecked && rightChecked) {
dirCat = 'both';
} else if (!leftChecked && rightChecked) {
dirCat = 'from-to';
dir = [right.id, left.id];
} else if (leftChecked && !rightChecked) {
dirCat = 'from-to';
dir = [left.id, right.id];
}
synapse.save({
category: dirCat,
node1_id: dir[0],
node2_id: dir[1]
});
Metamaps.Visualize.mGraph.plot();
});
} // if
} //add_direction_form
}; // end Metamaps.SynapseCard
////////////////////// END TOPIC AND SYNAPSE CARDS //////////////////////////////////
/*
*
* VISUALIZE
*
*/
Metamaps.Visualize = {
mGraph: null, // a reference to the graph object.
cameraPosition: null, // stores the camera position when using a 3D visualization
type: "ForceDirected", // the type of graph we're building, could be "RGraph", "ForceDirected", or "ForceDirected3D"
loadLater: false, // indicates whether there is JSON that should be loaded right in the offset, or whether to wait till the first topic is created
init: function () {
var self = Metamaps.Visualize;
// disable awkward dragging of the canvas element that would sometimes happen
$('#infovis-canvas').on('dragstart', function (event) {
event.preventDefault();
});
// prevent touch events on the canvas from default behaviour
$("#infovis-canvas").bind('touchstart', function (event) {
event.preventDefault();
self.mGraph.events.touched = true;
});
// prevent touch events on the canvas from default behaviour
$("#infovis-canvas").bind('touchmove', function (event) {
//Metamaps.JIT.touchPanZoomHandler(event);
});
// prevent touch events on the canvas from default behaviour
$("#infovis-canvas").bind('touchend touchcancel', function (event) {
lastDist = 0;
if (!self.mGraph.events.touchMoved && !Metamaps.Touch.touchDragNode) Metamaps.TopicCard.hideCurrentCard();
self.mGraph.events.touched = self.mGraph.events.touchMoved = false;
Metamaps.Touch.touchDragNode = false;
});
},
computePositions: function () {
var self = Metamaps.Visualize,
mapping;
if (self.type == "RGraph") {
var i, l, startPos, endPos, topic, synapse;
self.mGraph.graph.eachNode(function (n) {
topic = Metamaps.Topics.get(n.id);
topic.set({ node: n }, { silent: true });
topic.updateNode();
n.eachAdjacency(function (edge) {
if(!edge.getData('init')) {
edge.setData('init', true);
l = edge.getData('synapseIDs').length;
for (i = 0; i < l; i++) {
synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]);
synapse.set({ edge: edge }, { silent: true });
synapse.updateEdge();
}
}
});
var pos = n.getPos();
pos.setc(-200, -200);
});
self.mGraph.compute('end');
} else if (self.type == "ForceDirected") {
var i, l, startPos, endPos, topic, synapse;
self.mGraph.graph.eachNode(function (n) {
topic = Metamaps.Topics.get(n.id);
topic.set({ node: n }, { silent: true });
topic.updateNode();
mapping = topic.getMapping();
n.eachAdjacency(function (edge) {
if(!edge.getData('init')) {
edge.setData('init', true);
l = edge.getData('synapseIDs').length;
for (i = 0; i < l; i++) {
synapse = Metamaps.Synapses.get(edge.getData('synapseIDs')[i]);
synapse.set({ edge: edge }, { silent: true });
synapse.updateEdge();
}
}
});
startPos = new $jit.Complex(0, 0);
endPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc'));
n.setPos(startPos, 'start');
n.setPos(endPos, 'end');
});
} else if (self.type == "ForceDirected3D") {
self.mGraph.compute();
}
},
/**
* render does the heavy lifting of creating the engine that renders the graph with the properties we desire
*
*/
render: function () {
var self = Metamaps.Visualize, RGraphSettings, FDSettings;
if (self.type == "RGraph" && (!self.mGraph || self.mGraph instanceof $jit.ForceDirected)) {
RGraphSettings = $.extend(true, {}, Metamaps.JIT.ForceDirected.graphSettings);
$jit.RGraph.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings);
$jit.RGraph.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings);
RGraphSettings.width = $(document).width();
RGraphSettings.height = $(document).height();
RGraphSettings.background = Metamaps.JIT.RGraph.background;
RGraphSettings.levelDistance = Metamaps.JIT.RGraph.levelDistance;
self.mGraph = new $jit.RGraph(RGraphSettings);
} else if (self.type == "ForceDirected" && (!self.mGraph || self.mGraph instanceof $jit.RGraph)) {
FDSettings = $.extend(true, {}, Metamaps.JIT.ForceDirected.graphSettings);
$jit.ForceDirected.Plot.NodeTypes.implement(Metamaps.JIT.ForceDirected.nodeSettings);
$jit.ForceDirected.Plot.EdgeTypes.implement(Metamaps.JIT.ForceDirected.edgeSettings);
FDSettings.width = $('body').width();
FDSettings.height = $('body').height();
self.mGraph = new $jit.ForceDirected(FDSettings);
} else if (self.type == "ForceDirected3D" && !self.mGraph) {
// init ForceDirected3D
self.mGraph = new $jit.ForceDirected3D(Metamaps.JIT.ForceDirected3D.graphSettings);
self.cameraPosition = self.mGraph.canvas.canvases[0].camera.position;
}
else {
self.mGraph.graph.empty();
}
function runAnimation() {
Metamaps.Loading.hide();
// load JSON data, if it's not empty
if (!self.loadLater) {
//load JSON data.
var rootIndex = 0;
if (Metamaps.Active.Topic) {
var node = _.find(Metamaps.JIT.vizData, function(node){
return node.id === Metamaps.Active.Topic.id;
});
rootIndex = _.indexOf(Metamaps.JIT.vizData, node);
}
self.mGraph.loadJSON(Metamaps.JIT.vizData, rootIndex);
//compute positions and plot.
self.computePositions();
self.mGraph.busy = true;
if (self.type == "RGraph") {
self.mGraph.fx.animate(Metamaps.JIT.RGraph.animate);
} else if (self.type == "ForceDirected") {
self.mGraph.animate(Metamaps.JIT.ForceDirected.animateSavedLayout);
} else if (self.type == "ForceDirected3D") {
self.mGraph.animate(Metamaps.JIT.ForceDirected.animateFDLayout);
}
}
}
// hold until all the needed metacode images are loaded
// hold for a maximum of 80 passes, or 4 seconds of waiting time
var tries = 0;
function hold() {
var unique = _.uniq(Metamaps.Topics.models, function (metacode) { return metacode.get('metacode_id'); }),
requiredMetacodes = _.map(unique, function (metacode) { return metacode.get('metacode_id'); }),
loadedCount = 0;
_.each(requiredMetacodes, function (metacode_id) {
var metacode = Metamaps.Metacodes.get(metacode_id),
img = metacode ? metacode.get('image') : false;
if (img && (img.complete || (typeof img.naturalWidth !== "undefined" && img.naturalWidth !== 0))) {
loadedCount += 1;
}
});
if (loadedCount === requiredMetacodes.length || tries > 80) runAnimation();
else setTimeout(function(){ tries++; hold() }, 50);
}
hold();
// update the url now that the map is ready
clearTimeout(Metamaps.Router.timeoutId);
Metamaps.Router.timeoutId = setTimeout(function(){
var m = Metamaps.Active.Map;
var t = Metamaps.Active.Topic;
if (m && window.location.pathname !== "/maps/" + m.id) {
Metamaps.Router.navigate("/maps/" + m.id);
}
else if (t && window.location.pathname !== "/topics/" + t.id) {
Metamaps.Router.navigate("/topics/" + t.id);
}
}, 800);
}
}; // end Metamaps.Visualize
/*
*
* UTIL
*
*/
Metamaps.Util = {
// helper function to determine how many lines are needed
// Line Splitter Function
// copyright Stephen Chapman, 19th April 2006
// you may copy this code but please keep the copyright notice as well
splitLine: function (st, n) {
var b = '';
var s = st ? st : '';
while (s.length > n) {
var c = s.substring(0, n);
var d = c.lastIndexOf(' ');
var e = c.lastIndexOf('\n');
if (e != -1) d = e;
if (d == -1) d = n;
b += c.substring(0, d) + '\n';
s = s.substring(d + 1);
}
return b + s;
},
nowDateFormatted: function () {
var date = new Date(Date.now());
var month = (date.getMonth() + 1) < 10 ? '0' + (date.getMonth() + 1) : (date.getMonth() + 1);
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate();
var year = date.getFullYear();
return month + '/' + day + '/' + year;
},
decodeEntities: function (desc) {
var str, temp = document.createElement('p');
temp.innerHTML = desc; //browser handles the topics
str = temp.textContent || temp.innerText;
temp = null; //delete the element;
return str;
}, //decodeEntities
getDistance: function (p1, p2) {
return Math.sqrt(Math.pow((p2.x - p1.x), 2) + Math.pow((p2.y - p1.y), 2));
},
coordsToPixels: function (coords) {
if (Metamaps.Visualize.mGraph) {
var canvas = Metamaps.Visualize.mGraph.canvas,
s = canvas.getSize(),
p = canvas.getPos(),
ox = canvas.translateOffsetX,
oy = canvas.translateOffsetY,
sx = canvas.scaleOffsetX,
sy = canvas.scaleOffsetY;
var pixels = {
x: (coords.x / (1/sx)) + p.x + s.width/2 + ox,
y: (coords.y / (1/sy)) + p.y + s.height/2 + oy
};
return pixels;
}
else {
return {
x: 0,
y: 0
};
}
},
pixelsToCoords: function (pixels) {
var coords;
if (Metamaps.Visualize.mGraph) {
var canvas = Metamaps.Visualize.mGraph.canvas,
s = canvas.getSize(),
p = canvas.getPos(),
ox = canvas.translateOffsetX,
oy = canvas.translateOffsetY,
sx = canvas.scaleOffsetX,
sy = canvas.scaleOffsetY;
coords = {
x: (pixels.x - p.x - s.width/2 - ox) * (1/sx),
y: (pixels.y - p.y - s.height/2 - oy) * (1/sy),
};
}
else {
coords = {
x: 0,
y: 0
};
}
return coords;
},
getPastelColor: function () {
var r = (Math.round(Math.random()* 127) + 127).toString(16);
var g = (Math.round(Math.random()* 127) + 127).toString(16);
var b = (Math.round(Math.random()* 127) + 127).toString(16);
return Metamaps.Util.colorLuminance('#' + r + g + b, -0.4);
},
// darkens a hex value by 'lum' percentage
colorLuminance: function (hex, lum) {
// validate hex string
hex = String(hex).replace(/[^0-9a-f]/gi, '');
if (hex.length < 6) {
hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
}
lum = lum || 0;
// convert to decimal and change luminosity
var rgb = "#", c, i;
for (i = 0; i < 3; i++) {
c = parseInt(hex.substr(i*2,2), 16);
c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
rgb += ("00"+c).substr(c.length);
}
return rgb;
},
generateOptionsList: function (data) {
var newlist = "";
for (var i = 0; i < data.length; i++) {
newlist = newlist + '';
}
return newlist;
},
checkURLisImage: function (url) {
// when the page reloads the following regular expression will be screwed up
// please replace it with this one before you save: /*backslashhere*.(jpeg|jpg|gif|png)$/
return (url.match(/\.(jpeg|jpg|gif|png)$/) != null);
},
checkURLisYoutubeVideo: function (url) {
return (url.match(/^https?:\/\/(?:www\.)?youtube.com\/watch\?(?=[^?]*v=\w+)(?:[^\s?]+)?$/) != null);
}
}; // end Metamaps.Util
/*
*
* REALTIME
*
*/
Metamaps.Realtime = {
videoId: 'video-wrapper',
socket: null,
webrtc: null,
readyToCall: false,
mappersOnMap: {},
disconnected: false,
chatOpen: false,
status: true, // stores whether realtime is True/On or False/Off,
broadcastingStatus: false,
inConversation: false,
localVideo: null,
init: function () {
var self = Metamaps.Realtime;
self.addJuntoListeners();
self.socket = new SocketIoConnection({ url: '<%= ENV['REALTIME_SERVER'] %>' });
self.socket.on('connect', function () {
console.log('connected');
if (!self.disconnected) {
self.startActiveMap();
} else self.disconnected = false;
});
self.socket.on('disconnect', function () {
self.disconnected = true;
});
if (Metamaps.Active.Mapper) {
self.webrtc = new SimpleWebRTC({
connection: self.socket,
localVideoEl: self.videoId,
remoteVideosEl: '',
detectSpeakingEvents: true,
autoAdjustMic: false, //true,
autoRequestMedia: false,
localVideo: {
autoplay: true,
mirror: true,
muted: true
},
media: {
video: true,
audio: true
},
nick: Metamaps.Active.Mapper.id
});
var
$video = $('').attr('id', self.videoId);
self.localVideo = {
$video: $video,
view: new Metamaps.Views.videoView($video[0], $('body'), 'me', true, {
DOUBLE_CLICK_TOLERANCE: 200,
avatar: Metamaps.Active.Mapper ? Metamaps.Active.Mapper.get('image') : ''
})
};
self.room = new Metamaps.Views.room({
webrtc: self.webrtc,
socket: self.socket,
username: Metamaps.Active.Mapper ? Metamaps.Active.Mapper.get('name') : '',
image: Metamaps.Active.Mapper ? Metamaps.Active.Mapper.get('image') : '',
room: 'global',
$video: self.localVideo.$video,
myVideoView: self.localVideo.view,
config: { DOUBLE_CLICK_TOLERANCE: 200 }
});
self.room.videoAdded(self.handleVideoAdded);
self.room.chat.$container.hide();
$('body').prepend(self.room.chat.$container);
} // if Metamaps.Active.Mapper
},
addJuntoListeners: function () {
var self = Metamaps.Realtime;
$(document).on(Metamaps.Views.chatView.events.openTray, function () {
$('.main').addClass('compressed');
self.chatOpen = true;
self.positionPeerIcons();
});
$(document).on(Metamaps.Views.chatView.events.closeTray, function () {
$('.main').removeClass('compressed');
self.chatOpen = false;
self.positionPeerIcons();
});
$(document).on(Metamaps.Views.chatView.events.videosOn, function () {
$('#wrapper').removeClass('hideVideos');
});
$(document).on(Metamaps.Views.chatView.events.videosOff, function () {
$('#wrapper').addClass('hideVideos');
});
$(document).on(Metamaps.Views.chatView.events.cursorsOn, function () {
$('#wrapper').removeClass('hideCursors');
});
$(document).on(Metamaps.Views.chatView.events.cursorsOff, function () {
$('#wrapper').addClass('hideCursors');
});
},
handleVideoAdded: function (v, id) {
var self = Metamaps.Realtime;
self.positionVideos();
v.setParent($('#wrapper'));
v.$container.find('.video-cutoff').css({
border: '4px solid ' + self.mappersOnMap[id].color
});
$('#wrapper').append(v.$container);
},
positionVideos: function () {
var self = Metamaps.Realtime;
var videoIds = Object.keys(self.room.videos);
var numOfVideos = videoIds.length;
var numOfVideosToPosition = _.filter(videoIds, function (id) {
return !self.room.videos[id].manuallyPositioned;
}).length;
var screenHeight = $(document).height();
var screenWidth = $(document).width();
var topExtraPadding = 20;
var topPadding = 30;
var leftPadding = 30;
var videoHeight = 150;
var videoWidth = 180;
var column = 0;
var row = 0;
var yFormula = function () {
var y = topExtraPadding + (topPadding + videoHeight)*row + topPadding;
if (y + videoHeight > screenHeight) {
row = 0;
column += 1;
y = yFormula();
}
row++;
return y;
};
var xFormula = function () {
var x = (leftPadding + videoWidth)*column + leftPadding;
return x;
};
// do self first
var myVideo = Metamaps.Realtime.localVideo.view;
if (!myVideo.manuallyPositioned) {
myVideo.$container.css({
top: yFormula() + 'px',
left: xFormula() + 'px'
});
}
videoIds.forEach(function (id) {
var video = self.room.videos[id];
if (!video.manuallyPositioned) {
video.$container.css({
top: yFormula() + 'px',
left: xFormula() + 'px'
});
}
});
},
startActiveMap: function () {
var self = Metamaps.Realtime;
if (Metamaps.Active.Map && Metamaps.Active.Mapper) {
var commonsMap = Metamaps.Active.Map.get('permission') === 'commons';
var publicMap = Metamaps.Active.Map.get('permission') === 'public';
if (commonsMap) {
self.turnOn();
self.setupSocket();
}
else if (publicMap) {
self.attachMapListener();
}
self.room.addMessages(new Metamaps.Backbone.MessageCollection(Metamaps.Messages), true);
}
},
endActiveMap: function () {
var self = Metamaps.Realtime;
$(document).off('mousemove');
self.socket.removeAllListeners();
if (self.inConversation) self.leaveCall();
self.socket.emit('endMapperNotify');
$(".collabCompass").remove();
self.status = false;
self.room.leave();
self.room.chat.$container.hide();
self.room.chat.close();
},
reenableRealtime: function() {
var confirmString = "The layout of your map has fallen out of sync with the saved copy. ";
confirmString += "To save your changes without overwriting the map, hit 'Cancel' and ";
confirmString += "then use 'Save to new map'. ";
confirmString += "Do you want to discard your changes and enable realtime?";
var c = confirm(confirmString);
if (c) {
Metamaps.Router.maps(Metamaps.Active.Map.id);
}
},
turnOn: function (notify) {
var self = Metamaps.Realtime;
if (notify) self.sendRealtimeOn();
//$(".rtMapperSelf").removeClass('littleRtOff').addClass('littleRtOn');
//$('.rtOn').addClass('active');
//$('.rtOff').removeClass('active');
self.status = true;
//$(".sidebarCollaborateIcon").addClass("blue");
$(".collabCompass").show();
self.room.chat.$container.show();
self.room.room = 'map-' + Metamaps.Active.Map.id;
self.checkForACallToJoin();
self.activeMapper = {
id: Metamaps.Active.Mapper.id,
name: Metamaps.Active.Mapper.get('name'),
username: Metamaps.Active.Mapper.get('name'),
image: Metamaps.Active.Mapper.get('image'),
color: Metamaps.Util.getPastelColor(),
self: true
};
self.localVideo.view.$container.find('.video-cutoff').css({
border: '4px solid ' + self.activeMapper.color
});
self.room.chat.addParticipant(self.activeMapper);
},
checkForACallToJoin: function () {
var self = Metamaps.Realtime;
self.socket.emit('checkForCall', { room: self.room.room, mapid: Metamaps.Active.Map.id });
},
promptToJoin: function () {
var self = Metamaps.Realtime;
var notifyText = 'There\'s a conversation happening, want to join?';
notifyText += ' ';
notifyText += ' ';
Metamaps.GlobalUI.notifyUser(notifyText, true);
self.room.conversationInProgress();
},
conversationHasBegun: function () {
var self = Metamaps.Realtime;
if (self.inConversation) return;
var notifyText = 'There\'s a conversation starting, want to join?';
notifyText += ' ';
notifyText += ' ';
Metamaps.GlobalUI.notifyUser(notifyText, true);
self.room.conversationInProgress();
},
countOthersInConversation: function () {
var self = Metamaps.Realtime;
var count = 0;
for (var key in self.mappersOnMap) {
if (self.mappersOnMap[key].inConversation) count++;
}
return count;
},
mapperJoinedCall: function (id) {
var self = Metamaps.Realtime;
var mapper = self.mappersOnMap[id];
if (mapper) {
if (self.inConversation) {
var username = mapper.name;
var notifyText = username + ' joined the call';
Metamaps.GlobalUI.notifyUser(notifyText);
}
mapper.inConversation = true;
self.room.chat.mapperJoinedCall(id);
}
},
mapperLeftCall: function (id) {
var self = Metamaps.Realtime;
var mapper = self.mappersOnMap[id];
if (mapper) {
if (self.inConversation) {
var username = mapper.name;
var notifyText = username + ' left the call';
Metamaps.GlobalUI.notifyUser(notifyText);
}
mapper.inConversation = false;
self.room.chat.mapperLeftCall(id);
if ((self.inConversation && self.countOthersInConversation() === 0) ||
(!self.inConversation && self.countOthersInConversation() === 1)) {
self.callEnded();
}
}
},
callEnded: function () {
var self = Metamaps.Realtime;
self.room.conversationEnding();
self.room.leaveVideoOnly();
self.inConversation = false;
self.localVideo.view.$container.hide().css({
top: '72px',
left: '30px'
});
self.localVideo.view.audioOn();
self.localVideo.view.videoOn();
self.webrtc.webrtc.localStreams.forEach(function (stream) {
stream.getTracks().forEach(function (track) {
track.stop();
});
});
self.webrtc.webrtc.localStreams = [];
},
invitedToCall: function (inviter) {
var self = Metamaps.Realtime;
self.room.chat.sound.stop('sessioninvite');
self.room.chat.sound.play('sessioninvite');
var username = self.mappersOnMap[inviter].name;
var notifyText = "' style='display: inline-block; margin-top: -12px; vertical-align: top;' />";
notifyText += username + ' is inviting you to a conversation. Join live?';
notifyText += ' ';
notifyText += ' ';
Metamaps.GlobalUI.notifyUser(notifyText, true);
},
invitedToJoin: function (inviter) {
var self = Metamaps.Realtime;
self.room.chat.sound.stop('sessioninvite');
self.room.chat.sound.play('sessioninvite');
var username = self.mappersOnMap[inviter].name;
var notifyText = username + ' is inviting you to the conversation. Join?';
notifyText += ' ';
notifyText += ' ';
Metamaps.GlobalUI.notifyUser(notifyText, true);
},
acceptCall: function (userid) {
var self = Metamaps.Realtime;
self.room.chat.sound.stop('sessioninvite');
self.socket.emit('callAccepted', {
mapid: Metamaps.Active.Map.id,
invited: Metamaps.Active.Mapper.id,
inviter: userid
});
$.post('/maps/' + Metamaps.Active.Map.id + '/events/conversation');
self.joinCall();
Metamaps.GlobalUI.clearNotify();
},
denyCall: function (userid) {
var self = Metamaps.Realtime;
self.room.chat.sound.stop('sessioninvite');
self.socket.emit('callDenied', {
mapid: Metamaps.Active.Map.id,
invited: Metamaps.Active.Mapper.id,
inviter: userid
});
Metamaps.GlobalUI.clearNotify();
},
denyInvite: function (userid) {
var self = Metamaps.Realtime;
self.room.chat.sound.stop('sessioninvite');
self.socket.emit('inviteDenied', {
mapid: Metamaps.Active.Map.id,
invited: Metamaps.Active.Mapper.id,
inviter: userid
});
Metamaps.GlobalUI.clearNotify();
},
inviteACall: function (userid) {
var self = Metamaps.Realtime;
self.socket.emit('inviteACall', {
mapid: Metamaps.Active.Map.id,
inviter: Metamaps.Active.Mapper.id,
invited: userid
});
self.room.chat.invitationPending(userid);
Metamaps.GlobalUI.clearNotify();
},
inviteToJoin: function (userid) {
var self = Metamaps.Realtime;
self.socket.emit('inviteToJoin', {
mapid: Metamaps.Active.Map.id,
inviter: Metamaps.Active.Mapper.id,
invited: userid
});
self.room.chat.invitationPending(userid);
},
callAccepted: function (userid) {
var self = Metamaps.Realtime;
var username = self.mappersOnMap[userid].name;
Metamaps.GlobalUI.notifyUser('Conversation starting...');
self.joinCall();
self.room.chat.invitationAnswered(userid);
},
callDenied: function (userid) {
var self = Metamaps.Realtime;
var username = self.mappersOnMap[userid].name;
Metamaps.GlobalUI.notifyUser(username + ' didn\'t accept your invitation');
self.room.chat.invitationAnswered(userid);
},
inviteDenied: function (userid) {
var self = Metamaps.Realtime;
var username = self.mappersOnMap[userid].name;
Metamaps.GlobalUI.notifyUser(username + ' didn\'t accept your invitation');
self.room.chat.invitationAnswered(userid);
},
joinCall: function () {
var self = Metamaps.Realtime;
self.webrtc.off('readyToCall');
self.webrtc.once('readyToCall', function () {
self.videoInitialized = true;
self.readyToCall = true;
self.localVideo.view.manuallyPositioned = false;
self.positionVideos();
self.localVideo.view.$container.show();
if (self.localVideo && self.status) {
$('#wrapper').append(self.localVideo.view.$container);
}
self.room.join();
});
self.inConversation = true;
self.socket.emit('mapperJoinedCall', {
mapid: Metamaps.Active.Map.id,
id: Metamaps.Active.Mapper.id
});
self.webrtc.startLocalVideo();
Metamaps.GlobalUI.clearNotify();
self.room.chat.mapperJoinedCall(Metamaps.Active.Mapper.id);
},
leaveCall: function () {
var self = Metamaps.Realtime;
self.socket.emit('mapperLeftCall', {
mapid: Metamaps.Active.Map.id,
id: Metamaps.Active.Mapper.id
});
self.room.chat.mapperLeftCall(Metamaps.Active.Mapper.id);
self.room.leaveVideoOnly();
self.inConversation = false;
self.localVideo.view.$container.hide();
// if there's only two people in the room, and we're leaving
// we should shut down the call locally
if (self.countOthersInConversation() === 1) {
self.callEnded();
}
},
turnOff: function (silent) {
var self = Metamaps.Realtime;
if (self.status) {
if (!silent) self.sendRealtimeOff();
//$(".rtMapperSelf").removeClass('littleRtOn').addClass('littleRtOff');
//$('.rtOn').removeClass('active');
//$('.rtOff').addClass('active');
self.status = false;
//$(".sidebarCollaborateIcon").removeClass("blue");
$(".collabCompass").hide();
$('#' + self.videoId).remove();
}
},
setupSocket: function () {
var self = Metamaps.Realtime;
var socket = Metamaps.Realtime.socket;
var myId = Metamaps.Active.Mapper.id;
socket.emit('newMapperNotify', {
userid: myId,
username: Metamaps.Active.Mapper.get("name"),
userimage: Metamaps.Active.Mapper.get("image"),
mapid: Metamaps.Active.Map.id
});
socket.on(myId + '-' + Metamaps.Active.Map.id + '-invitedToCall', self.invitedToCall); // new call
socket.on(myId + '-' + Metamaps.Active.Map.id + '-invitedToJoin', self.invitedToJoin); // call already in progress
socket.on(myId + '-' + Metamaps.Active.Map.id + '-callAccepted', self.callAccepted);
socket.on(myId + '-' + Metamaps.Active.Map.id + '-callDenied', self.callDenied);
socket.on(myId + '-' + Metamaps.Active.Map.id + '-inviteDenied', self.inviteDenied);
// receive word that there's a conversation in progress
socket.on('maps-' + Metamaps.Active.Map.id + '-callInProgress', self.promptToJoin);
socket.on('maps-' + Metamaps.Active.Map.id + '-callStarting', self.conversationHasBegun);
socket.on('maps-' + Metamaps.Active.Map.id + '-mapperJoinedCall', self.mapperJoinedCall);
socket.on('maps-' + Metamaps.Active.Map.id + '-mapperLeftCall', self.mapperLeftCall);
// if you're the 'new guy' update your list with who's already online
socket.on(myId + '-' + Metamaps.Active.Map.id + '-UpdateMapperList', self.updateMapperList);
// receive word that there's a new mapper on the map
socket.on('maps-' + Metamaps.Active.Map.id + '-newmapper', self.newPeerOnMap);
// receive word that a mapper left the map
socket.on('maps-' + Metamaps.Active.Map.id + '-lostmapper', self.lostPeerOnMap);
// receive word that there's a mapper turned on realtime
socket.on('maps-' + Metamaps.Active.Map.id + '-newrealtime', self.newCollaborator);
// receive word that there's a mapper turned on realtime
socket.on('maps-' + Metamaps.Active.Map.id + '-lostrealtime', self.lostCollaborator);
//
socket.on('maps-' + Metamaps.Active.Map.id + '-topicDrag', self.topicDrag);
//
socket.on('maps-' + Metamaps.Active.Map.id + '-newTopic', self.newTopic);
//
socket.on('maps-' + Metamaps.Active.Map.id + '-newMessage', self.newMessage);
//
socket.on('maps-' + Metamaps.Active.Map.id + '-removeTopic', self.removeTopic);
//
socket.on('maps-' + Metamaps.Active.Map.id + '-newSynapse', self.newSynapse);
//
socket.on('maps-' + Metamaps.Active.Map.id + '-removeSynapse', self.removeSynapse);
// update mapper compass position
socket.on('maps-' + Metamaps.Active.Map.id + '-updatePeerCoords', self.updatePeerCoords);
// deletions
socket.on('deleteTopicFromServer', self.removeTopic);
socket.on('deleteSynapseFromServer', self.removeSynapse);
socket.on('topicChangeFromServer', self.topicChange);
socket.on('synapseChangeFromServer', self.synapseChange);
self.attachMapListener();
// local event listeners that trigger events
var sendCoords = function (event) {
var pixels = {
x: event.pageX,
y: event.pageY
};
var coords = Metamaps.Util.pixelsToCoords(pixels);
self.sendCoords(coords);
};
$(document).mousemove(sendCoords);
var zoom = function (event, e) {
if (e) {
var pixels = {
x: e.pageX,
y: e.pageY
};
var coords = Metamaps.Util.pixelsToCoords(pixels);
self.sendCoords(coords);
}
self.positionPeerIcons();
};
$(document).on(Metamaps.JIT.events.zoom, zoom);
$(document).on(Metamaps.JIT.events.pan, self.positionPeerIcons);
var sendTopicDrag = function (event, positions) {
self.sendTopicDrag(positions);
};
$(document).on(Metamaps.JIT.events.topicDrag, sendTopicDrag);
var sendNewTopic = function (event, data) {
self.sendNewTopic(data);
};
$(document).on(Metamaps.JIT.events.newTopic, sendNewTopic);
var sendDeleteTopic = function (event, data) {
self.sendDeleteTopic(data);
};
$(document).on(Metamaps.JIT.events.deleteTopic, sendDeleteTopic);
var sendRemoveTopic = function (event, data) {
self.sendRemoveTopic(data);
};
$(document).on(Metamaps.JIT.events.removeTopic, sendRemoveTopic);
var sendNewSynapse = function (event, data) {
self.sendNewSynapse(data);
};
$(document).on(Metamaps.JIT.events.newSynapse, sendNewSynapse);
var sendDeleteSynapse = function (event, data) {
self.sendDeleteSynapse(data);
};
$(document).on(Metamaps.JIT.events.deleteSynapse, sendDeleteSynapse);
var sendRemoveSynapse = function (event, data) {
self.sendRemoveSynapse(data);
};
$(document).on(Metamaps.JIT.events.removeSynapse, sendRemoveSynapse);
var sendNewMessage = function (event, data) {
self.sendNewMessage(data);
};
$(document).on(Metamaps.Views.room.events.newMessage, sendNewMessage);
},
attachMapListener: function(){
var self = Metamaps.Realtime;
var socket = Metamaps.Realtime.socket;
socket.on('mapChangeFromServer', self.mapChange);
},
sendRealtimeOn: function () {
var self = Metamaps.Realtime;
var socket = Metamaps.Realtime.socket;
// send this new mapper back your details, and the awareness that you're online
var update = {
username: Metamaps.Active.Mapper.get("name"),
userid: Metamaps.Active.Mapper.id,
mapid: Metamaps.Active.Map.id
};
socket.emit('notifyStartRealtime', update);
},
sendRealtimeOff: function () {
var self = Metamaps.Realtime;
var socket = Metamaps.Realtime.socket;
// send this new mapper back your details, and the awareness that you're online
var update = {
username: Metamaps.Active.Mapper.get("name"),
userid: Metamaps.Active.Mapper.id,
mapid: Metamaps.Active.Map.id
};
socket.emit('notifyStopRealtime', update);
},
updateMapperList: function (data) {
var self = Metamaps.Realtime;
var socket = Metamaps.Realtime.socket;
// data.userid
// data.username
// data.userimage
// data.userrealtime
self.mappersOnMap[data.userid] = {
id: data.userid,
name: data.username,
username: data.username,
image: data.userimage,
color: Metamaps.Util.getPastelColor(),
realtime: data.userrealtime,
inConversation: data.userinconversation,
coords: {
x: 0,
y: 0
}
};
if (data.userid !== Metamaps.Active.Mapper.id) {
self.room.chat.addParticipant(self.mappersOnMap[data.userid]);
if (data.userinconversation) self.room.chat.mapperJoinedCall(data.userid);
// create a div for the collaborators compass
self.createCompass(data.username, data.userid, data.userimage, self.mappersOnMap[data.userid].color, !self.status);
}
},
newPeerOnMap: function (data) {
var self = Metamaps.Realtime;
var socket = Metamaps.Realtime.socket;
// data.userid
// data.username
// data.userimage
// data.coords
var firstOtherPerson = Object.keys(self.mappersOnMap).length === 0;
self.mappersOnMap[data.userid] = {
id: data.userid,
name: data.username,
username: data.username,
image: data.userimage,
color: Metamaps.Util.getPastelColor(),
realtime: true,
coords: {
x: 0,
y: 0
},
};
// create an item for them in the realtime box
if (data.userid !== Metamaps.Active.Mapper.id && self.status) {
self.room.chat.sound.play('joinmap');
self.room.chat.addParticipant(self.mappersOnMap[data.userid]);
// create a div for the collaborators compass
self.createCompass(data.username, data.userid, data.userimage, self.mappersOnMap[data.userid].color, !self.status);
var notifyMessage = data.username + ' just joined the map';
if (firstOtherPerson) {
notifyMessage += ' ';
}
Metamaps.GlobalUI.notifyUser(notifyMessage);
// send this new mapper back your details, and the awareness that you've loaded the map
var update = {
userToNotify: data.userid,
username: Metamaps.Active.Mapper.get("name"),
userimage: Metamaps.Active.Mapper.get("image"),
userid: Metamaps.Active.Mapper.id,
userrealtime: self.status,
userinconversation: self.inConversation,
mapid: Metamaps.Active.Map.id
};
socket.emit('updateNewMapperList', update);
}
},
createCompass: function(name, id, image, color, hide) {
var str = '
'+name+'
';
str += '';
$('#compass' + id).remove();
$('', {
id: 'compass' + id,
class: 'collabCompass'
}).html(str).appendTo('#wrapper');
if (hide) {
$('#compass' + id).hide();
}
$('#compass' + id + ' img').css({
'border': '2px solid ' + color
});
$('#compass' + id + ' p').css({
'background-color': color
});
},
lostPeerOnMap: function (data) {
var self = Metamaps.Realtime;
var socket = Metamaps.Realtime.socket;
// data.userid
// data.username
delete self.mappersOnMap[data.userid];
self.room.chat.sound.play('leavemap');
//$('#mapper' + data.userid).remove();
$('#compass' + data.userid).remove();
self.room.chat.removeParticipant(data.username);
Metamaps.GlobalUI.notifyUser(data.username + ' just left the map');
if ((self.inConversation && self.countOthersInConversation() === 0) ||
(!self.inConversation && self.countOthersInConversation() === 1)) {
self.callEnded();
}
},
newCollaborator: function (data) {
var self = Metamaps.Realtime;
var socket = Metamaps.Realtime.socket;
// data.userid
// data.username
self.mappersOnMap[data.userid].realtime = true;
//$('#mapper' + data.userid).removeClass('littleRtOff').addClass('littleRtOn');
$('#compass' + data.userid).show();
Metamaps.GlobalUI.notifyUser(data.username + ' just turned on realtime');
},
lostCollaborator: function (data) {
var self = Metamaps.Realtime;
var socket = Metamaps.Realtime.socket;
// data.userid
// data.username
self.mappersOnMap[data.userid].realtime = false;
//$('#mapper' + data.userid).removeClass('littleRtOn').addClass('littleRtOff');
$('#compass' + data.userid).hide();
Metamaps.GlobalUI.notifyUser(data.username + ' just turned off realtime');
},
updatePeerCoords: function (data) {
var self = Metamaps.Realtime;
var socket = Metamaps.Realtime.socket;
self.mappersOnMap[data.userid].coords={x: data.usercoords.x,y:data.usercoords.y};
self.positionPeerIcon(data.userid);
},
positionPeerIcons: function () {
var self = Metamaps.Realtime;
var socket = Metamaps.Realtime.socket;
if (self.status) { // if i have realtime turned on
for (var key in self.mappersOnMap) {
var mapper = self.mappersOnMap[key];
if (mapper.realtime) {
self.positionPeerIcon(key);
}
}
}
},
positionPeerIcon: function (id) {
var self = Metamaps.Realtime;
var socket = Metamaps.Realtime.socket;
var boundary = self.chatOpen ? '#wrapper' : document;
var mapper = self.mappersOnMap[id];
var xMax=$(boundary).width();
var yMax=$(boundary).height();
var compassDiameter=56;
var compassArrowSize=24;
var origPixels = Metamaps.Util.coordsToPixels(mapper.coords);
var pixels = self.limitPixelsToScreen(origPixels);
$('#compass' + id).css({
left: pixels.x + 'px',
top: pixels.y + 'px'
});
/* showing the arrow if the collaborator is off of the viewport screen */
if (origPixels.x !== pixels.x || origPixels.y !== pixels.y) {
var dy = origPixels.y - pixels.y; //opposite
var dx = origPixels.x - pixels.x; // adjacent
var ratio = dy / dx;
var angle = Math.atan2(dy, dx);
$('#compassArrow' + id).show().css({
transform: 'rotate(' + angle + 'rad)',
"-webkit-transform": 'rotate(' + angle + 'rad)',
});
if (dx > 0) {
$('#compass' + id).addClass('labelLeft');
}
} else {
$('#compassArrow' + id).hide();
$('#compass' + id).removeClass('labelLeft');
}
},
limitPixelsToScreen: function (pixels) {
var self = Metamaps.Realtime;
var socket = Metamaps.Realtime.socket;
var boundary = self.chatOpen ? '#wrapper' : document;
var xLimit, yLimit;
var xMax=$(boundary).width();
var yMax=$(boundary).height();
var compassDiameter=56;
var compassArrowSize=24;
xLimit = Math.max(0 + compassArrowSize, pixels.x);
xLimit = Math.min(xLimit, xMax - compassDiameter);
yLimit = Math.max(0 + compassArrowSize, pixels.y);
yLimit = Math.min(yLimit, yMax - compassDiameter);
return {x:xLimit,y:yLimit};
},
sendCoords: function (coords) {
var self = Metamaps.Realtime;
var socket = Metamaps.Realtime.socket;
var map = Metamaps.Active.Map;
var mapper = Metamaps.Active.Mapper;
if (self.status && map.authorizeToEdit(mapper) && socket) {
var update = {
usercoords: coords,
userid: Metamaps.Active.Mapper.id,
mapid: Metamaps.Active.Map.id
};
socket.emit('updateMapperCoords', update);
}
},
sendTopicDrag: function (positions) {
var self = Metamaps.Realtime;
var socket = self.socket;
if (Metamaps.Active.Map && self.status) {
positions.mapid = Metamaps.Active.Map.id;
socket.emit('topicDrag', positions);
}
},
topicDrag: function (positions) {
var self = Metamaps.Realtime;
var socket = self.socket;
var topic;
var node;
if (Metamaps.Active.Map && self.status) {
for (var key in positions) {
topic = Metamaps.Topics.get(key);
if (topic) node = topic.get('node');
if (node) node.pos.setc(positions[key].x, positions[key].y);
} //for
Metamaps.Visualize.mGraph.plot();
}
},
sendTopicChange: function (topic) {
var self = Metamaps.Realtime;
var socket = self.socket;
var data = {
topicId: topic.id
}
socket.emit('topicChangeFromClient', data);
},
topicChange: function (data) {
var topic = Metamaps.Topics.get(data.topicId);
if (topic) {
var node = topic.get('node');
topic.fetch({
success: function (model) {
model.set({ node: node });
model.trigger('changeByOther');
}
});
}
},
sendSynapseChange: function (synapse) {
var self = Metamaps.Realtime;
var socket = self.socket;
var data = {
synapseId: synapse.id
}
socket.emit('synapseChangeFromClient', data);
},
synapseChange: function (data) {
var synapse = Metamaps.Synapses.get(data.synapseId);
if (synapse) {
// edge reset necessary because fetch causes model reset
var edge = synapse.get('edge');
synapse.fetch({
success: function (model) {
model.set({ edge: edge });
model.trigger('changeByOther');
}
});
}
},
sendMapChange: function (map) {
var self = Metamaps.Realtime;
var socket = self.socket;
var data = {
mapId: map.id
}
socket.emit('mapChangeFromClient', data);
},
mapChange: function (data) {
var map = Metamaps.Active.Map;
var isActiveMap = map && data.mapId === map.id;
if (isActiveMap) {
var permBefore = map.get('permission');
var idBefore = map.id;
map.fetch({
success: function (model, response) {
var idNow = model.id;
var permNow = model.get('permission');
if (idNow !== idBefore) {
Metamaps.Map.leavePrivateMap(); // this means the map has been changed to private
}
else if (permNow === 'public' && permBefore === 'commons') {
Metamaps.Map.commonsToPublic();
}
else if (permNow === 'commons' && permBefore === 'public') {
Metamaps.Map.publicToCommons();
}
else {
model.fetchContained();
model.trigger('changeByOther');
}
}
});
}
},
// newMessage
sendNewMessage: function (data) {
var self = Metamaps.Realtime;
var socket = self.socket;
var message = data.attributes;
message.mapid = Metamaps.Active.Map.id;
socket.emit('newMessage', message);
},
newMessage: function (data) {
var self = Metamaps.Realtime;
var socket = self.socket;
self.room.addMessages(new Metamaps.Backbone.MessageCollection(data));
},
// newTopic
sendNewTopic: function (data) {
var self = Metamaps.Realtime;
var socket = self.socket;
if (Metamaps.Active.Map && self.status) {
data.mapperid = Metamaps.Active.Mapper.id;
data.mapid = Metamaps.Active.Map.id;
socket.emit('newTopic', data);
}
},
newTopic: function (data) {
var topic, mapping, mapper, mapperCallback, cancel;
var self = Metamaps.Realtime;
var socket = self.socket;
if (!self.status) return;
function waitThenRenderTopic() {
if (topic && mapping && mapper) {
Metamaps.Topic.renderTopic(mapping, topic, false, false);
}
else if (!cancel) {
setTimeout(waitThenRenderTopic, 10);
}
}
mapper = Metamaps.Mappers.get(data.mapperid);
if (mapper === undefined) {
mapperCallback = function (m) {
Metamaps.Mappers.add(m);
mapper = m;
};
Metamaps.Mapper.get(data.mapperid, mapperCallback);
}
$.ajax({
url: "/topics/" + data.mappableid + ".json",
success: function (response) {
Metamaps.Topics.add(response);
topic = Metamaps.Topics.get(response.id);
},
error: function () {
cancel = true;
}
});
$.ajax({
url: "/mappings/" + data.mappingid + ".json",
success: function (response) {
Metamaps.Mappings.add(response);
mapping = Metamaps.Mappings.get(response.id);
},
error: function () {
cancel = true;
}
});
waitThenRenderTopic();
},
// removeTopic
sendDeleteTopic: function (data) {
var self = Metamaps.Realtime;
var socket = self.socket;
if (Metamaps.Active.Map) {
socket.emit('deleteTopicFromClient', data);
}
},
// removeTopic
sendRemoveTopic: function (data) {
var self = Metamaps.Realtime;
var socket = self.socket;
if (Metamaps.Active.Map) {
data.mapid = Metamaps.Active.Map.id;
socket.emit('removeTopic', data);
}
},
removeTopic: function (data) {
var self = Metamaps.Realtime;
var socket = self.socket;
if (!self.status) return;
var topic = Metamaps.Topics.get(data.mappableid);
if (topic) {
var node = topic.get('node');
var mapping = topic.getMapping();
Metamaps.Control.hideNode(node.id);
Metamaps.Topics.remove(topic);
Metamaps.Mappings.remove(mapping);
}
},
// newSynapse
sendNewSynapse: function (data) {
var self = Metamaps.Realtime;
var socket = self.socket;
if (Metamaps.Active.Map) {
data.mapperid = Metamaps.Active.Mapper.id;
data.mapid = Metamaps.Active.Map.id;
socket.emit('newSynapse', data);
}
},
newSynapse: function (data) {
var topic1, topic2, node1, node2, synapse, mapping, cancel;
var self = Metamaps.Realtime;
var socket = self.socket;
if (!self.status) return;
function waitThenRenderSynapse() {
if (synapse && mapping && mapper) {
topic1 = synapse.getTopic1();
node1 = topic1.get('node');
topic2 = synapse.getTopic2();
node2 = topic2.get('node');
Metamaps.Synapse.renderSynapse(mapping, synapse, node1, node2, false);
}
else if (!cancel) {
setTimeout(waitThenRenderSynapse, 10);
}
}
mapper = Metamaps.Mappers.get(data.mapperid);
if (mapper === undefined) {
mapperCallback = function (m) {
Metamaps.Mappers.add(m);
mapper = m;
};
Metamaps.Mapper.get(data.mapperid, mapperCallback);
}
$.ajax({
url: "/synapses/" + data.mappableid + ".json",
success: function (response) {
Metamaps.Synapses.add(response);
synapse = Metamaps.Synapses.get(response.id);
},
error: function () {
cancel = true;
}
});
$.ajax({
url: "/mappings/" + data.mappingid + ".json",
success: function (response) {
Metamaps.Mappings.add(response);
mapping = Metamaps.Mappings.get(response.id);
},
error: function () {
cancel = true;
}
});
waitThenRenderSynapse();
},
// deleteSynapse
sendDeleteSynapse: function (data) {
var self = Metamaps.Realtime;
var socket = self.socket;
if (Metamaps.Active.Map) {
data.mapid = Metamaps.Active.Map.id;
socket.emit('deleteSynapseFromClient', data);
}
},
// removeSynapse
sendRemoveSynapse: function (data) {
var self = Metamaps.Realtime;
var socket = self.socket;
if (Metamaps.Active.Map) {
data.mapid = Metamaps.Active.Map.id;
socket.emit('removeSynapse', data);
}
},
removeSynapse: function (data) {
var self = Metamaps.Realtime;
var socket = self.socket;
if (!self.status) return;
var synapse = Metamaps.Synapses.get(data.mappableid);
if (synapse) {
var edge = synapse.get('edge');
var mapping = synapse.getMapping();
if (edge.getData("mappings").length - 1 === 0) {
Metamaps.Control.hideEdge(edge);
}
var index = _.indexOf(edge.getData("synapses"), synapse);
edge.getData("mappings").splice(index, 1);
edge.getData("synapses").splice(index, 1);
if (edge.getData("displayIndex")) {
delete edge.data.$displayIndex;
}
Metamaps.Synapses.remove(synapse);
Metamaps.Mappings.remove(mapping);
}
},
}; // end Metamaps.Realtime
/*
*
* CONTROL
*
*/
Metamaps.Control = {
init: function () {
},
selectNode: function (node,e) {
var filtered = node.getData('alpha') === 0;
if (filtered || Metamaps.Selected.Nodes.indexOf(node) != -1) return;
node.selected = true;
node.setData('dim', 30, 'current');
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.setData('dim', 25, 'current');
//remove the node
Metamaps.Selected.Nodes.splice(
Metamaps.Selected.Nodes.indexOf(node), 1);
},
deleteSelected: function () {
if (!Metamaps.Active.Map) return;
var n = Metamaps.Selected.Nodes.length;
var e = Metamaps.Selected.Edges.length;
var ntext = n == 1 ? "1 topic" : n + " topics";
var etext = e == 1 ? "1 synapse" : e + " synapses";
var text = "You have " + ntext + " and " + etext + " selected. ";
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper);
if (!authorized) {
Metamaps.GlobalUI.notifyUser("Cannot edit Public map.");
return;
}
var r = confirm(text + "Are you sure you want to permanently delete them all? This will remove them from all maps they appear on.");
if (r == true) {
Metamaps.Control.deleteSelectedEdges();
Metamaps.Control.deleteSelectedNodes();
}
},
deleteSelectedNodes: function () { // refers to deleting topics permanently
if (!Metamaps.Active.Map) return;
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper);
if (!authorized) {
Metamaps.GlobalUI.notifyUser("Cannot edit Public map.");
return;
}
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
if (!Metamaps.Active.Map) return;
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper);
if (!authorized) {
Metamaps.GlobalUI.notifyUser("Cannot edit Public map.");
return;
}
var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid);
var topic = node.getData('topic');
var permToDelete = Metamaps.Active.Mapper.id === topic.get('user_id') || Metamaps.Active.Mapper.get('admin');
if (permToDelete) {
var mappableid = topic.id;
var mapping = node.getData('mapping');
topic.destroy();
Metamaps.Mappings.remove(mapping);
$(document).trigger(Metamaps.JIT.events.deleteTopic, [{
mappableid: mappableid
}]);
Metamaps.Control.hideNode(nodeid);
} else {
Metamaps.GlobalUI.notifyUser('Only topics you created can be deleted');
}
},
removeSelectedNodes: function () { // refers to removing topics permanently from a map
if (!Metamaps.Active.Map) return;
var l = Metamaps.Selected.Nodes.length,
i,
node,
authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper);
if (!authorized) {
Metamaps.GlobalUI.notifyUser("Cannot edit Public map.");
return;
}
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
if (!Metamaps.Active.Map) return;
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper);
var node = Metamaps.Visualize.mGraph.graph.getNode(nodeid);
if (!authorized) {
Metamaps.GlobalUI.notifyUser("Cannot edit Public map.");
return;
}
var topic = node.getData('topic');
var mappableid = topic.id;
var mapping = node.getData('mapping');
mapping.destroy();
Metamaps.Topics.remove(topic);
$(document).trigger(Metamaps.JIT.events.removeTopic, [{
mappableid: mappableid
}]);
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);
var graph = Metamaps.Visualize.mGraph;
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 () {
if (nodeid == Metamaps.Visualize.mGraph.root) { // && Metamaps.Visualize.type === "RGraph"
var newroot = _.find(graph.graph.nodes, function(n){ return n.id !== nodeid; });
graph.root = newroot ? newroot.id : null;
}
Metamaps.Visualize.mGraph.graph.removeNode(nodeid);
}, 500);
Metamaps.Filter.checkMetacodes();
Metamaps.Filter.checkMappers();
},
selectEdge: function (edge) {
var filtered = edge.getData('alpha') === 0; // don't select if the edge is filtered
if (filtered || Metamaps.Selected.Edges.indexOf(edge) != -1) return;
var width = Metamaps.Mouse.edgeHoveringOver === edge ? 4 : 2;
edge.setDataset('current', {
showDesc: true,
lineWidth: width,
color: Metamaps.Settings.colors.synapses.selected
});
Metamaps.Visualize.mGraph.plot();
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('current', {
lineWidth: 2,
color: Metamaps.Settings.colors.synapses.normal
});
if (Metamaps.Mouse.edgeHoveringOver == edge) {
edge.setDataset('current', {
showDesc: true,
lineWidth: 4
});
}
Metamaps.Visualize.mGraph.plot();
//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;
if (!Metamaps.Active.Map) return;
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper);
if (!authorized) {
Metamaps.GlobalUI.notifyUser("Cannot edit Public map.");
return;
}
for (var i = l - 1; i >= 0; i -= 1) {
edge = Metamaps.Selected.Edges[i];
Metamaps.Control.deleteEdge(edge);
}
},
deleteEdge: function (edge) {
if (!Metamaps.Active.Map) return;
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper);
if (!authorized) {
Metamaps.GlobalUI.notifyUser("Cannot edit Public map.");
return;
}
var index = edge.getData("displayIndex") ? edge.getData("displayIndex") : 0;
var synapse = edge.getData("synapses")[index];
var mapping = edge.getData("mappings")[index];
var permToDelete = Metamaps.Active.Mapper.id === synapse.get('user_id') || Metamaps.Active.Mapper.get('admin');
if (permToDelete) {
if (edge.getData("synapses").length - 1 === 0) {
Metamaps.Control.hideEdge(edge);
}
var mappableid = synapse.id;
synapse.destroy();
// the server will destroy the mapping, we just need to remove it here
Metamaps.Mappings.remove(mapping);
edge.getData("mappings").splice(index, 1);
edge.getData("synapses").splice(index, 1);
if (edge.getData("displayIndex")) {
delete edge.data.$displayIndex;
}
$(document).trigger(Metamaps.JIT.events.deleteSynapse, [{
mappableid: mappableid
}]);
} else {
Metamaps.GlobalUI.notifyUser('Only synapses you created can be deleted');
}
},
removeSelectedEdges: function () {
var l = Metamaps.Selected.Edges.length,
i,
edge;
if (!Metamaps.Active.Map) return;
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper);
if (!authorized) {
Metamaps.GlobalUI.notifyUser("Cannot edit Public map.");
return;
}
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) {
if (!Metamaps.Active.Map) return;
var authorized = Metamaps.Active.Map.authorizeToEdit(Metamaps.Active.Mapper);
if (!authorized) {
Metamaps.GlobalUI.notifyUser("Cannot edit Public map.");
return;
}
if (edge.getData("mappings").length - 1 === 0) {
Metamaps.Control.hideEdge(edge);
}
var index = edge.getData("displayIndex") ? edge.getData("displayIndex") : 0;
var synapse = edge.getData("synapses")[index];
var mapping = edge.getData("mappings")[index];
var mappableid = synapse.id;
mapping.destroy();
Metamaps.Synapses.remove(synapse);
edge.getData("mappings").splice(index, 1);
edge.getData("synapses").splice(index, 1);
if (edge.getData("displayIndex")) {
delete edge.data.$displayIndex;
}
$(document).trigger(Metamaps.JIT.events.removeSynapse, [{
mappableid: mappableid
}]);
},
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.Control.deselectEdge(edge);
Metamaps.Visualize.mGraph.fx.animate({
modes: ['edge-property:alpha'],
duration: 500
});
setTimeout(function () {
Metamaps.Visualize.mGraph.graph.removeAdjacence(from, to);
}, 500);
Metamaps.Filter.checkSynapses();
Metamaps.Filter.checkMappers();
},
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);
},
updateSelectedMetacodes: function (metacode_id) {
var node, topic;
Metamaps.GlobalUI.notifyUser('Working...');
var metacode = Metamaps.Metacodes.get(metacode_id);
// variables to keep track of how many nodes and synapses you had the ability to change the permission of
var nCount = 0;
// 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.authorizeToEdit(Metamaps.Active.Mapper)) {
topic.save({
'metacode_id': metacode_id
});
nCount++;
}
}
var nString = nCount == 1 ? (nCount.toString() + ' topic') : (nCount.toString() + ' topics');
var message = nString + ' you can edit updated to ' + metacode.get('name');
Metamaps.GlobalUI.notifyUser(message);
Metamaps.Visualize.mGraph.plot();
},
}; // end Metamaps.Control