metamaps--metamaps/app/assets/javascripts/src/Metamaps.Map.js.erb

668 lines
22 KiB
Plaintext
Raw Normal View History

2016-04-14 06:01:21 +00:00
/* global Metamaps, $ */
/*
* Metamaps.Map.js.erb
*
* Dependencies:
* Metamaps.Create
* Metamaps.Filter
* Metamaps.JIT
* Metamaps.Loading
* Metamaps.Maps
* Metamaps.Realtime
* Metamaps.Router
* Metamaps.Selected
* Metamaps.SynapseCard
* Metamaps.TopicCard
* Metamaps.Visualize
* - Metamaps.Active
* - Metamaps.Backbone
* - Metamaps.GlobalUI
* - Metamaps.Mappers
* - Metamaps.Mappings
* - Metamaps.Messages
* - Metamaps.Synapses
* - Metamaps.Topics
*
* Major sub-modules:
* - Metamaps.Map.CheatSheet
* - Metamaps.Map.InfoBox
*/
Metamaps.Map = {
events: {
editedByActiveMapper: 'Metamaps:Map:events:editedByActiveMapper'
},
nextX: 0,
nextY: 0,
sideLength: 1,
turnCount: 0,
nextXshift: 1,
nextYshift: 0,
timeToTurn: 0,
init: function () {
var self = Metamaps.Map
// prevent right clicks on the main canvas, so as to not get in the way of our right clicks
$('#center-container').bind('contextmenu', function (e) {
return false
})
$('.sidebarFork').click(function () {
self.fork()
})
Metamaps.GlobalUI.CreateMap.emptyForkMapForm = $('#fork_map').html()
self.InfoBox.init()
self.CheatSheet.init()
$(document).on(Metamaps.Map.events.editedByActiveMapper, self.editedByActiveMapper)
},
launch: function (id) {
var bb = Metamaps.Backbone
var start = function (data) {
Metamaps.Active.Map = new bb.Map(data.map)
Metamaps.Mappers = new bb.MapperCollection(data.mappers)
Metamaps.Topics = new bb.TopicCollection(data.topics)
Metamaps.Synapses = new bb.SynapseCollection(data.synapses)
Metamaps.Mappings = new bb.MappingCollection(data.mappings)
Metamaps.Messages = data.messages
Metamaps.Backbone.attachCollectionEvents()
var map = Metamaps.Active.Map
var mapper = Metamaps.Active.Mapper
// add class to .wrapper for specifying whether you can edit the map
if (map.authorizeToEdit(mapper)) {
$('.wrapper').addClass('canEditMap')
}
// add class to .wrapper for specifying if the map can
// be collaborated on
if (map.get('permission') === 'commons') {
$('.wrapper').addClass('commonsMap')
}
// set filter mapper H3 text
$('#filter_by_mapper h3').html('MAPPERS')
// build and render the visualization
Metamaps.Visualize.type = 'ForceDirected'
Metamaps.JIT.prepareVizData()
// update filters
Metamaps.Filter.reset()
// reset selected arrays
Metamaps.Selected.reset()
// set the proper mapinfobox content
Metamaps.Map.InfoBox.load()
// these three update the actual filter box with the right list items
Metamaps.Filter.checkMetacodes()
Metamaps.Filter.checkSynapses()
Metamaps.Filter.checkMappers()
Metamaps.Realtime.startActiveMap()
Metamaps.Loading.hide()
}
$.ajax({
url: '/maps/' + id + '/contains.json',
success: start
})
},
end: function () {
if (Metamaps.Active.Map) {
$('.wrapper').removeClass('canEditMap commonsMap')
Metamaps.Map.resetSpiral()
$('.rightclickmenu').remove()
Metamaps.TopicCard.hideCard()
Metamaps.SynapseCard.hideCard()
Metamaps.Create.newTopic.hide()
Metamaps.Create.newSynapse.hide()
Metamaps.Filter.close()
Metamaps.Map.InfoBox.close()
Metamaps.Realtime.endActiveMap()
}
},
fork: function () {
Metamaps.GlobalUI.openLightbox('forkmap')
var nodes_data = '',
synapses_data = ''
var nodes_array = []
var synapses_array = []
// collect the unfiltered topics
Metamaps.Visualize.mGraph.graph.eachNode(function (n) {
// if the opacity is less than 1 then it's filtered
if (n.getData('alpha') === 1) {
var id = n.getData('topic').id
nodes_array.push(id)
var x, y
if (n.pos.x && n.pos.y) {
x = n.pos.x
y = n.pos.y
} else {
var x = Math.cos(n.pos.theta) * n.pos.rho
var y = Math.sin(n.pos.theta) * n.pos.rho
}
nodes_data += id + '/' + x + '/' + y + ','
}
})
// collect the unfiltered synapses
Metamaps.Synapses.each(function (synapse) {
var desc = synapse.get('desc')
var descNotFiltered = Metamaps.Filter.visible.synapses.indexOf(desc) > -1
// make sure that both topics are being added, otherwise, it
// doesn't make sense to add the synapse
var topicsNotFiltered = nodes_array.indexOf(synapse.get('node1_id')) > -1
topicsNotFiltered = topicsNotFiltered && nodes_array.indexOf(synapse.get('node2_id')) > -1
if (descNotFiltered && topicsNotFiltered) {
synapses_array.push(synapse.id)
}
})
synapses_data = synapses_array.join()
nodes_data = nodes_data.slice(0, -1)
Metamaps.GlobalUI.CreateMap.topicsToMap = nodes_data
Metamaps.GlobalUI.CreateMap.synapsesToMap = synapses_data
},
leavePrivateMap: function () {
var map = Metamaps.Active.Map
Metamaps.Maps.Active.remove(map)
Metamaps.Maps.Featured.remove(map)
Metamaps.Router.home()
Metamaps.GlobalUI.notifyUser('Sorry! That map has been changed to Private.')
},
commonsToPublic: function () {
Metamaps.Realtime.turnOff(true); // true is for 'silence'
Metamaps.GlobalUI.notifyUser('Map was changed to Public. Editing is disabled.')
Metamaps.Active.Map.trigger('changeByOther')
},
publicToCommons: function () {
var confirmString = 'This map permission has been changed to Commons! '
confirmString += 'Do you want to reload and enable realtime collaboration?'
var c = confirm(confirmString)
if (c) {
Metamaps.Router.maps(Metamaps.Active.Map.id)
}
},
editedByActiveMapper: function () {
if (Metamaps.Active.Mapper) {
Metamaps.Mappers.add(Metamaps.Active.Mapper)
}
},
getNextCoord: function () {
var self = Metamaps.Map
var nextX = self.nextX
var nextY = self.nextY
var DISTANCE_BETWEEN = 120
self.nextX = self.nextX + DISTANCE_BETWEEN * self.nextXshift
self.nextY = self.nextY + DISTANCE_BETWEEN * self.nextYshift
self.timeToTurn += 1
// if true, it's time to turn
if (self.timeToTurn === self.sideLength) {
self.turnCount += 1
// if true, it's time to increase side length
if (self.turnCount % 2 === 0) {
self.sideLength += 1
}
self.timeToTurn = 0
// going right? turn down
if (self.nextXshift == 1 && self.nextYshift == 0) {
self.nextXshift = 0
self.nextYshift = 1
}
// going down? turn left
else if (self.nextXshift == 0 && self.nextYshift == 1) {
self.nextXshift = -1
self.nextYshift = 0
}
// going left? turn up
else if (self.nextXshift == -1 && self.nextYshift == 0) {
self.nextXshift = 0
self.nextYshift = -1
}
// going up? turn right
else if (self.nextXshift == 0 && self.nextYshift == -1) {
self.nextXshift = 1
self.nextYshift = 0
}
}
return {
x: nextX,
y: nextY
}
},
resetSpiral: function () {
Metamaps.Map.nextX = 0
Metamaps.Map.nextY = 0
Metamaps.Map.nextXshift = 1
Metamaps.Map.nextYshift = 0
Metamaps.Map.sideLength = 1
Metamaps.Map.timeToTurn = 0
Metamaps.Map.turnCount = 0
},
exportImage: function () {
var canvas = {}
canvas.canvas = document.createElement('canvas')
canvas.canvas.width = 1880 // 960
canvas.canvas.height = 1260 // 630
canvas.scaleOffsetX = 1
canvas.scaleOffsetY = 1
canvas.translateOffsetY = 0
canvas.translateOffsetX = 0
canvas.denySelected = true
canvas.getSize = function () {
if (this.size) return this.size
var canvas = this.canvas
return this.size = {
width: canvas.width,
height: canvas.height
}
}
canvas.scale = function (x, y) {
var px = this.scaleOffsetX * x,
py = this.scaleOffsetY * y
var dx = this.translateOffsetX * (x - 1) / px,
dy = this.translateOffsetY * (y - 1) / py
this.scaleOffsetX = px
this.scaleOffsetY = py
this.getCtx().scale(x, y)
this.translate(dx, dy)
}
canvas.translate = function (x, y) {
var sx = this.scaleOffsetX,
sy = this.scaleOffsetY
this.translateOffsetX += x * sx
this.translateOffsetY += y * sy
this.getCtx().translate(x, y)
}
canvas.getCtx = function () {
return this.canvas.getContext('2d')
}
// center it
canvas.getCtx().translate(1880 / 2, 1260 / 2)
var mGraph = Metamaps.Visualize.mGraph
var id = mGraph.root
var root = mGraph.graph.getNode(id)
var T = !!root.visited
// pass true to avoid basing it on a selection
Metamaps.JIT.zoomExtents(null, canvas, true)
var c = canvas.canvas,
ctx = canvas.getCtx(),
scale = canvas.scaleOffsetX
// draw a grey background
ctx.fillStyle = '#d8d9da'
var xPoint = (-(c.width / scale) / 2) - (canvas.translateOffsetX / scale),
yPoint = (-(c.height / scale) / 2) - (canvas.translateOffsetY / scale)
ctx.fillRect(xPoint, yPoint, c.width / scale, c.height / scale)
// draw the graph
mGraph.graph.eachNode(function (node) {
var nodeAlpha = node.getData('alpha')
node.eachAdjacency(function (adj) {
var nodeTo = adj.nodeTo
if (!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
mGraph.fx.plotLine(adj, canvas)
}
})
if (node.drawn) {
mGraph.fx.plotNode(node, canvas)
}
if (!mGraph.labelsHidden) {
if (node.drawn && nodeAlpha >= 0.95) {
mGraph.labels.plotLabel(canvas, node)
} else {
mGraph.labels.hideLabel(node, false)
}
}
node.visited = !T
})
var imageData = {
encoded_image: canvas.canvas.toDataURL()
}
var map = Metamaps.Active.Map
var today = new Date()
var dd = today.getDate()
var mm = today.getMonth() + 1; // January is 0!
var yyyy = today.getFullYear()
if (dd < 10) {
dd = '0' + dd
}
if (mm < 10) {
mm = '0' + mm
}
today = mm + '/' + dd + '/' + yyyy
var mapName = map.get('name').split(' ').join([separator = '-'])
var downloadMessage = ''
downloadMessage += 'Captured map screenshot! '
downloadMessage += "<a href='" + imageData.encoded_image + "' "
downloadMessage += "download='metamap-" + map.id + '-' + mapName + '-' + today + ".png'>DOWNLOAD</a>"
Metamaps.GlobalUI.notifyUser(downloadMessage)
$.ajax({
type: 'POST',
dataType: 'json',
url: '/maps/' + Metamaps.Active.Map.id + '/upload_screenshot',
data: imageData,
success: function (data) {
console.log('successfully uploaded map screenshot')
},
error: function () {
console.log('failed to save map screenshot')
}
})
}
}
/*
*
* CHEATSHEET
*
*/
Metamaps.Map.CheatSheet = {
init: function () {
// tab the cheatsheet
$('#cheatSheet').tabs()
$('#quickReference').tabs().addClass('ui-tabs-vertical ui-helper-clearfix')
$('#quickReference .ui-tabs-nav li').removeClass('ui-corner-top').addClass('ui-corner-left')
// id = the id of a vimeo video
var switchVideo = function (element, id) {
$('.tutorialItem').removeClass('active')
$(element).addClass('active')
$('#tutorialVideo').attr('src', '//player.vimeo.com/video/' + id)
}
$('#gettingStarted').click(function () {
// switchVideo(this,'88334167')
})
$('#upYourSkillz').click(function () {
// switchVideo(this,'100118167')
})
$('#advancedMapping').click(function () {
// switchVideo(this,'88334167')
})
}
}; // end Metamaps.Map.CheatSheet
/*
*
* INFOBOX
*
*/
Metamaps.Map.InfoBox = {
isOpen: false,
changing: false,
selectingPermission: false,
changePermissionText: "<div class='tooltips'>As the creator, you can change the permission of this map, but the permissions of the topics and synapses on it must be changed independently.</div>",
nameHTML: '<span class="best_in_place best_in_place_name" id="best_in_place_map_{{id}}_name" data-url="/maps/{{id}}" data-object="map" data-attribute="name" data-type="textarea" data-activator="#mapInfoName">{{name}}</span>',
descHTML: '<span class="best_in_place best_in_place_desc" id="best_in_place_map_{{id}}_desc" data-url="/maps/{{id}}" data-object="map" data-attribute="desc" data-nil="Click to add description..." data-type="textarea" data-activator="#mapInfoDesc">{{desc}}</span>',
init: function () {
var self = Metamaps.Map.InfoBox
$('.mapInfoIcon').click(self.toggleBox)
$('.mapInfoBox').click(function (event) {
event.stopPropagation()
})
$('body').click(self.close)
self.attachEventListeners()
self.generateBoxHTML = Hogan.compile($('#mapInfoBoxTemplate').html())
},
toggleBox: function (event) {
var self = Metamaps.Map.InfoBox
if (self.isOpen) self.close()
else self.open()
event.stopPropagation()
},
open: function () {
var self = Metamaps.Map.InfoBox
$('.mapInfoIcon div').addClass('hide')
if (!self.isOpen && !self.changing) {
self.changing = true
$('.mapInfoBox').fadeIn(200, function () {
self.changing = false
self.isOpen = true
})
}
},
close: function () {
var self = Metamaps.Map.InfoBox
$('.mapInfoIcon div').removeClass('hide')
if (!self.changing) {
self.changing = true
$('.mapInfoBox').fadeOut(200, function () {
self.changing = false
self.isOpen = false
self.hidePermissionSelect()
$('.mapContributors .tip').hide()
})
}
},
load: function () {
var self = Metamaps.Map.InfoBox
var map = Metamaps.Active.Map
var obj = map.pick('permission', 'contributor_count', 'topic_count', 'synapse_count')
var isCreator = map.authorizePermissionChange(Metamaps.Active.Mapper)
var canEdit = map.authorizeToEdit(Metamaps.Active.Mapper)
var shareable = map.get('permission') !== 'private'
obj['name'] = canEdit ? Hogan.compile(self.nameHTML).render({id: map.id, name: map.get('name')}) : map.get('name')
obj['desc'] = canEdit ? Hogan.compile(self.descHTML).render({id: map.id, desc: map.get('desc')}) : map.get('desc')
obj['map_creator_tip'] = isCreator ? self.changePermissionText : ''
obj['contributors_class'] = Metamaps.Mappers.length > 1 ? 'multiple' : ''
obj['contributors_class'] += Metamaps.Mappers.length === 2 ? ' mTwo' : ''
obj['contributor_image'] = Metamaps.Mappers.length > 0 ? Metamaps.Mappers.models[0].get('image') : "<%= asset_path('user.png') %>"
obj['contributor_list'] = self.createContributorList()
obj['user_name'] = isCreator ? 'You' : map.get('user_name')
obj['created_at'] = map.get('created_at_clean')
obj['updated_at'] = map.get('updated_at_clean')
var classes = isCreator ? 'yourMap' : ''
classes += canEdit ? ' canEdit' : ''
classes += shareable ? ' shareable' : ''
$('.mapInfoBox').removeClass('shareable yourMap canEdit')
.addClass(classes)
.html(self.generateBoxHTML.render(obj))
self.attachEventListeners()
},
attachEventListeners: function () {
var self = Metamaps.Map.InfoBox
$('.mapInfoBox.canEdit .best_in_place').best_in_place()
// because anyone who can edit the map can change the map title
var bipName = $('.mapInfoBox .best_in_place_name')
bipName.unbind('best_in_place:activate').bind('best_in_place:activate', function () {
var $el = bipName.find('textarea')
var el = $el[0]
$el.attr('maxlength', '140')
$('.mapInfoName').append('<div class="nameCounter forMap"></div>')
var callback = function (data) {
$('.nameCounter.forMap').html(data.all + '/140')
}
Countable.live(el, callback)
})
bipName.unbind('best_in_place:deactivate').bind('best_in_place:deactivate', function () {
$('.nameCounter.forMap').remove()
})
$('.mapInfoName .best_in_place_name').unbind('ajax:success').bind('ajax:success', function () {
var name = $(this).html()
Metamaps.Active.Map.set('name', name)
Metamaps.Active.Map.trigger('saved')
})
$('.mapInfoDesc .best_in_place_desc').unbind('ajax:success').bind('ajax:success', function () {
var desc = $(this).html()
Metamaps.Active.Map.set('desc', desc)
Metamaps.Active.Map.trigger('saved')
})
$('.yourMap .mapPermission').unbind().click(self.onPermissionClick)
// .yourMap in the unbind/bind is just a namespace for the events
// not a reference to the class .yourMap on the .mapInfoBox
$('.mapInfoBox.yourMap').unbind('.yourMap').bind('click.yourMap', self.hidePermissionSelect)
$('.yourMap .mapInfoDelete').unbind().click(self.deleteActiveMap)
$('.mapContributors span, #mapContribs').unbind().click(function (event) {
$('.mapContributors .tip').toggle()
event.stopPropagation()
})
$('.mapContributors .tip').unbind().click(function (event) {
event.stopPropagation()
})
$('.mapContributors .tip li a').click(Metamaps.Router.intercept)
$('.mapInfoBox').unbind('.hideTip').bind('click.hideTip', function () {
$('.mapContributors .tip').hide()
})
},
updateNameDescPerm: function (name, desc, perm) {
$('.mapInfoName .best_in_place_name').html(name)
$('.mapInfoDesc .best_in_place_desc').html(desc)
$('.mapInfoBox .mapPermission').removeClass('commons public private').addClass(perm)
},
createContributorList: function () {
var self = Metamaps.Map.InfoBox
var string = ''
string += '<ul>'
Metamaps.Mappers.each(function (m) {
string += '<li><a href="/explore/mapper/' + m.get('id') + '">' + '<img class="rtUserImage" width="25" height="25" src="' + m.get('image') + '" />' + m.get('name') + '</a></li>'
})
string += '</ul>'
return string
},
updateNumbers: function () {
var self = Metamaps.Map.InfoBox
var mapper = Metamaps.Active.Mapper
var contributors_class = ''
if (Metamaps.Mappers.length === 2) contributors_class = 'multiple mTwo'
else if (Metamaps.Mappers.length > 2) contributors_class = 'multiple'
var contributors_image = "<%= asset_path('user.png') %>"
if (Metamaps.Mappers.length > 0) {
// get the first contributor and use their image
contributors_image = Metamaps.Mappers.models[0].get('image')
}
$('.mapContributors img').attr('src', contributors_image).removeClass('multiple mTwo').addClass(contributors_class)
$('.mapContributors span').text(Metamaps.Mappers.length)
$('.mapContributors .tip').html(self.createContributorList())
$('.mapTopics').text(Metamaps.Topics.length)
$('.mapSynapses').text(Metamaps.Synapses.length)
$('.mapEditedAt').html('<span>Last edited: </span>' + Metamaps.Util.nowDateFormatted())
},
onPermissionClick: function (event) {
var self = Metamaps.Map.InfoBox
if (!self.selectingPermission) {
self.selectingPermission = true
$(this).addClass('minimize') // this line flips the drop down arrow to a pull up arrow
if ($(this).hasClass('commons')) {
$(this).append('<ul class="permissionSelect"><li class="public"></li><li class="private"></li></ul>')
} else if ($(this).hasClass('public')) {
$(this).append('<ul class="permissionSelect"><li class="commons"></li><li class="private"></li></ul>')
} else if ($(this).hasClass('private')) {
$(this).append('<ul class="permissionSelect"><li class="commons"></li><li class="public"></li></ul>')
}
$('.mapPermission .permissionSelect li').click(self.selectPermission)
event.stopPropagation()
}
},
hidePermissionSelect: function () {
var self = Metamaps.Map.InfoBox
self.selectingPermission = false
$('.mapPermission').removeClass('minimize') // this line flips the pull up arrow to a drop down arrow
$('.mapPermission .permissionSelect').remove()
},
selectPermission: function (event) {
var self = Metamaps.Map.InfoBox
self.selectingPermission = false
var permission = $(this).attr('class')
var permBefore = Metamaps.Active.Map.get('permission')
Metamaps.Active.Map.save({
permission: permission
})
Metamaps.Active.Map.updateMapWrapper()
if (permBefore !== 'commons' && permission === 'commons') {
Metamaps.Realtime.setupSocket()
Metamaps.Realtime.turnOn()
}
else if (permBefore === 'commons' && permission === 'public') {
Metamaps.Realtime.turnOff(true); // true is to 'silence'
// the notification that would otherwise be sent
}
shareable = permission === 'private' ? '' : 'shareable'
$('.mapPermission').removeClass('commons public private minimize').addClass(permission)
$('.mapPermission .permissionSelect').remove()
$('.mapInfoBox').removeClass('shareable').addClass(shareable)
event.stopPropagation()
},
deleteActiveMap: function () {
var confirmString = 'Are you sure you want to delete this map? '
confirmString += 'This action is irreversible. It will not delete the topics and synapses on the map.'
var doIt = confirm(confirmString)
var map = Metamaps.Active.Map
var mapper = Metamaps.Active.Mapper
var authorized = map.authorizePermissionChange(mapper)
if (doIt && authorized) {
Metamaps.Map.InfoBox.close()
Metamaps.Maps.Active.remove(map)
Metamaps.Maps.Featured.remove(map)
Metamaps.Maps.Mine.remove(map)
map.destroy()
Metamaps.Router.home()
Metamaps.GlobalUI.notifyUser('Map eliminated!')
}
else if (!authorized) {
alert("Hey now. We can't just go around willy nilly deleting other people's maps now can we? Run off and find something constructive to do, eh?")
}
}
}; // end Metamaps.Map.InfoBox