2016-10-02 22:28:00 +08:00
/* global $ */
2016-09-22 23:51:13 +08:00
2016-10-03 06:29:35 +08:00
import outdent from 'outdent'
2016-11-07 15:25:08 -05:00
import { find as _find } from 'lodash'
2016-10-03 06:29:35 +08:00
2016-09-23 08:05:26 +08:00
import Active from '../Active'
import AutoLayout from '../AutoLayout'
import Create from '../Create'
2016-10-02 22:28:00 +08:00
import DataModel from '../DataModel'
import DataModelMap from '../DataModel/Map'
2016-09-23 08:05:26 +08:00
import Filter from '../Filter'
import GlobalUI from '../GlobalUI'
import JIT from '../JIT'
2016-10-02 21:10:41 +08:00
import Loading from '../Loading'
2016-09-23 08:05:26 +08:00
import Realtime from '../Realtime'
import Router from '../Router'
import Selected from '../Selected'
import SynapseCard from '../SynapseCard'
import TopicCard from '../TopicCard'
import Visualize from '../Visualize'
2016-09-22 23:51:13 +08:00
import CheatSheet from './CheatSheet'
import InfoBox from './InfoBox'
const Map = {
events : {
editedByActiveMapper : 'Metamaps:Map:events:editedByActiveMapper'
} ,
2016-11-07 15:25:08 -05:00
init : function ( serverData ) {
2016-09-22 23:51:13 +08:00
var self = Map
2016-09-25 00:27:04 +08:00
2016-11-07 15:25:08 -05:00
$ ( '#wrapper' ) . mousedown ( function ( e ) {
if ( e . button === 1 ) return false
} )
$ ( '.starMap' ) . click ( function ( ) {
2016-09-22 23:51:13 +08:00
if ( $ ( this ) . is ( '.starred' ) ) self . unstar ( )
else self . star ( )
} )
2016-11-07 15:25:08 -05:00
$ ( '.sidebarFork' ) . click ( function ( ) {
2016-09-22 23:51:13 +08:00
self . fork ( )
} )
GlobalUI . CreateMap . emptyForkMapForm = $ ( '#fork_map' ) . html ( )
self . updateStar ( )
2017-01-19 14:50:08 -05:00
InfoBox . init ( serverData , function updateThumbnail ( ) {
self . uploadMapScreenshot ( )
} )
2016-10-03 08:32:37 +08:00
CheatSheet . init ( serverData )
2016-09-22 23:51:13 +08:00
2016-10-16 20:22:00 -04:00
$ ( '.viewOnly .requestAccess' ) . click ( self . requestAccess )
2016-09-22 23:51:13 +08:00
$ ( document ) . on ( Map . events . editedByActiveMapper , self . editedByActiveMapper )
} ,
2016-11-07 15:25:08 -05:00
requestAccess : function ( ) {
2016-10-16 20:22:00 -04:00
$ ( '.viewOnly' ) . removeClass ( 'sendRequest' ) . addClass ( 'sentRequest' )
const mapId = Active . Map . id
$ . post ( {
url : ` /maps/ ${ mapId } /access_request `
} )
2016-11-07 15:25:08 -05:00
GlobalUI . notifyUser ( 'Map creator will be notified of your request' )
2016-10-16 20:22:00 -04:00
} ,
2016-11-07 15:25:08 -05:00
setAccessRequest : function ( requests , activeMapper ) {
2016-10-16 20:22:00 -04:00
let className = 'isViewOnly '
if ( activeMapper ) {
2016-11-07 15:25:08 -05:00
const request = _find ( requests , r => r . user _id === activeMapper . id )
if ( ! request ) className += 'sendRequest'
else if ( request && ! request . answered ) className += 'sentRequest'
2016-10-16 20:22:00 -04:00
else if ( request && request . answered && ! request . approved ) className += 'requestDenied'
}
$ ( '.viewOnly' ) . removeClass ( 'sendRequest sentRequest requestDenied' ) . addClass ( className )
} ,
2016-11-07 15:25:08 -05:00
launch : function ( id ) {
var start = function ( data ) {
2016-10-02 22:28:00 +08:00
Active . Map = new DataModelMap ( data . map )
DataModel . Mappers = new DataModel . MapperCollection ( data . mappers )
DataModel . Collaborators = new DataModel . MapperCollection ( data . collaborators )
DataModel . Topics = new DataModel . TopicCollection ( data . topics )
DataModel . Synapses = new DataModel . SynapseCollection ( data . synapses )
DataModel . Mappings = new DataModel . MappingCollection ( data . mappings )
DataModel . Messages = data . messages
DataModel . Stars = data . stars
DataModel . attachCollectionEvents ( )
2016-09-22 23:51:13 +08:00
var map = Active . Map
var mapper = Active . Mapper
2016-09-23 16:06:28 -04:00
document . title = map . get ( 'name' ) + ' | Metamaps'
2016-09-23 15:47:37 -04:00
2016-09-22 23:51:13 +08:00
// add class to .wrapper for specifying whether you can edit the map
if ( map . authorizeToEdit ( mapper ) ) {
$ ( '.wrapper' ) . addClass ( 'canEditMap' )
2016-11-07 15:25:08 -05:00
} else {
2016-10-16 20:22:00 -04:00
Map . setAccessRequest ( data . requests , mapper )
}
2016-09-22 23:51:13 +08:00
// add class to .wrapper for specifying if the map can
// be collaborated on
if ( map . get ( 'permission' ) === 'commons' ) {
$ ( '.wrapper' ) . addClass ( 'commonsMap' )
}
2016-11-07 15:25:08 -05:00
Map . updateStar ( )
2016-09-22 23:51:13 +08:00
// set filter mapper H3 text
$ ( '#filter_by_mapper h3' ) . html ( 'MAPPERS' )
// build and render the visualization
Visualize . type = 'ForceDirected'
JIT . prepareVizData ( )
// update filters
Filter . reset ( )
// reset selected arrays
Selected . reset ( )
// set the proper mapinfobox content
2016-09-25 00:27:04 +08:00
InfoBox . load ( )
2016-09-22 23:51:13 +08:00
// these three update the actual filter box with the right list items
Filter . checkMetacodes ( )
Filter . checkSynapses ( )
Filter . checkMappers ( )
Realtime . startActiveMap ( )
2016-10-02 21:10:41 +08:00
Loading . hide ( )
2016-11-07 15:25:08 -05:00
2016-09-22 23:51:13 +08:00
// for mobile
$ ( '#header_content' ) . html ( map . get ( 'name' ) )
}
$ . ajax ( {
url : '/maps/' + id + '/contains.json' ,
success : start
} )
} ,
2016-11-07 15:25:08 -05:00
end : function ( ) {
2016-09-22 23:51:13 +08:00
if ( Active . Map ) {
$ ( '.wrapper' ) . removeClass ( 'canEditMap commonsMap' )
AutoLayout . resetSpiral ( )
$ ( '.rightclickmenu' ) . remove ( )
TopicCard . hideCard ( )
SynapseCard . hideCard ( )
Create . newTopic . hide ( true ) // true means force (and override pinned)
Create . newSynapse . hide ( )
Filter . close ( )
2016-09-25 00:27:04 +08:00
InfoBox . close ( )
2016-09-22 23:51:13 +08:00
Realtime . endActiveMap ( )
2016-10-16 20:22:00 -04:00
$ ( '.viewOnly' ) . removeClass ( 'isViewOnly' )
2016-09-22 23:51:13 +08:00
}
} ,
2016-11-07 15:25:08 -05:00
updateStar : function ( ) {
2016-10-02 22:28:00 +08:00
if ( ! Active . Mapper || ! DataModel . Stars ) return
2016-09-22 23:51:13 +08:00
// update the star/unstar icon
2016-11-07 15:25:08 -05:00
if ( DataModel . Stars . find ( function ( s ) { return s . user _id === Active . Mapper . id } ) ) {
2016-09-22 23:51:13 +08:00
$ ( '.starMap' ) . addClass ( 'starred' )
$ ( '.starMap .tooltipsAbove' ) . html ( 'Unstar' )
} else {
$ ( '.starMap' ) . removeClass ( 'starred' )
$ ( '.starMap .tooltipsAbove' ) . html ( 'Star' )
}
} ,
2016-11-07 15:25:08 -05:00
star : function ( ) {
2016-09-22 23:51:13 +08:00
var self = Map
if ( ! Active . Map ) return
$ . post ( '/maps/' + Active . Map . id + '/star' )
2016-10-02 22:28:00 +08:00
DataModel . Stars . push ( { user _id : Active . Mapper . id , map _id : Active . Map . id } )
DataModel . Maps . Starred . add ( Active . Map )
2016-09-22 23:51:13 +08:00
GlobalUI . notifyUser ( 'Map is now starred' )
self . updateStar ( )
} ,
2016-11-07 15:25:08 -05:00
unstar : function ( ) {
2016-09-22 23:51:13 +08:00
var self = Map
if ( ! Active . Map ) return
$ . post ( '/maps/' + Active . Map . id + '/unstar' )
2016-11-07 15:25:08 -05:00
DataModel . Stars = DataModel . Stars . filter ( function ( s ) { return s . user _id !== Active . Mapper . id } )
2016-10-02 22:28:00 +08:00
DataModel . Maps . Starred . remove ( Active . Map )
2016-11-07 15:25:08 -05:00
self . updateStar ( )
2016-09-22 23:51:13 +08:00
} ,
2016-11-07 15:25:08 -05:00
fork : function ( ) {
2016-09-22 23:51:13 +08:00
GlobalUI . openLightbox ( 'forkmap' )
2016-11-07 15:25:08 -05:00
let nodesData = ''
let synapsesData = ''
let nodesArray = [ ]
let synapsesArray = [ ]
2016-09-22 23:51:13 +08:00
// collect the unfiltered topics
2016-11-07 15:25:08 -05:00
Visualize . mGraph . graph . eachNode ( function ( n ) {
2016-09-22 23:51:13 +08:00
// if the opacity is less than 1 then it's filtered
if ( n . getData ( 'alpha' ) === 1 ) {
var id = n . getData ( 'topic' ) . id
2016-11-07 15:25:08 -05:00
nodesArray . push ( id )
let x , y
2016-09-22 23:51:13 +08:00
if ( n . pos . x && n . pos . y ) {
x = n . pos . x
y = n . pos . y
} else {
2016-11-07 15:25:08 -05:00
x = Math . cos ( n . pos . theta ) * n . pos . rho
y = Math . sin ( n . pos . theta ) * n . pos . rho
2016-09-22 23:51:13 +08:00
}
2016-11-07 15:25:08 -05:00
nodesData += id + '/' + x + '/' + y + ','
2016-09-22 23:51:13 +08:00
}
} )
// collect the unfiltered synapses
2016-11-07 15:25:08 -05:00
DataModel . Synapses . each ( function ( synapse ) {
2016-09-22 23:51:13 +08:00
var desc = synapse . get ( 'desc' )
var descNotFiltered = Filter . visible . synapses . indexOf ( desc ) > - 1
// make sure that both topics are being added, otherwise, it
// doesn't make sense to add the synapse
2016-11-07 15:25:08 -05:00
var topicsNotFiltered = nodesArray . indexOf ( synapse . get ( 'topic1_id' ) ) > - 1
topicsNotFiltered = topicsNotFiltered && nodesArray . indexOf ( synapse . get ( 'topic2_id' ) ) > - 1
2016-09-22 23:51:13 +08:00
if ( descNotFiltered && topicsNotFiltered ) {
2016-11-07 15:25:08 -05:00
synapsesArray . push ( synapse . id )
2016-09-22 23:51:13 +08:00
}
} )
2016-11-07 15:25:08 -05:00
synapsesData = synapsesArray . join ( )
nodesData = nodesData . slice ( 0 , - 1 )
2016-09-22 23:51:13 +08:00
2016-11-07 15:25:08 -05:00
GlobalUI . CreateMap . topicsToMap = nodesData
GlobalUI . CreateMap . synapsesToMap = synapsesData
2016-09-22 23:51:13 +08:00
} ,
2016-11-07 15:25:08 -05:00
leavePrivateMap : function ( ) {
2016-09-22 23:51:13 +08:00
var map = Active . Map
2016-10-02 22:28:00 +08:00
DataModel . Maps . Active . remove ( map )
DataModel . Maps . Featured . remove ( map )
2016-09-22 23:51:13 +08:00
Router . home ( )
GlobalUI . notifyUser ( 'Sorry! That map has been changed to Private.' )
} ,
2016-11-07 15:25:08 -05:00
cantEditNow : function ( ) {
Realtime . turnOff ( true ) // true is for 'silence'
2016-09-22 23:51:13 +08:00
GlobalUI . notifyUser ( 'Map was changed to Public. Editing is disabled.' )
Active . Map . trigger ( 'changeByOther' )
} ,
2016-11-07 15:25:08 -05:00
canEditNow : function ( ) {
2016-09-22 23:51:13 +08:00
var confirmString = "You've been granted permission to edit this map. "
confirmString += 'Do you want to reload and enable realtime collaboration?'
2016-10-04 23:38:32 +08:00
var c = window . confirm ( confirmString )
2016-09-22 23:51:13 +08:00
if ( c ) {
Router . maps ( Active . Map . id )
}
} ,
2016-11-07 15:25:08 -05:00
editedByActiveMapper : function ( ) {
2016-09-22 23:51:13 +08:00
if ( Active . Mapper ) {
2016-10-02 22:28:00 +08:00
DataModel . Mappers . add ( Active . Mapper )
2016-09-22 23:51:13 +08:00
}
} ,
2016-11-07 15:25:08 -05:00
exportImage : function ( ) {
2017-01-19 14:50:08 -05:00
Map . uploadMapScreenshot ( )
Map . offerScreenshotDownload ( )
GlobalUI . notifyUser ( 'Note: this button is going away. Check the map card or the import box for setting the map thumbnail or downloading a screenshot.' )
} ,
offerScreenshotDownload : ( ) => {
const canvas = Map . getMapCanvasForScreenshots ( )
const filename = Map . getMapScreenshotFilename ( Active . Map )
var downloadMessage = outdent `
Captured map screenshot !
< a id = "map-screenshot-download-link"
href = "${canvas.canvas.toDataURL()}"
download = "${filename}"
>
DOWNLOAD
< / a > `
GlobalUI . notifyUser ( downloadMessage )
} ,
uploadMapScreenshot : ( ) => {
const canvas = Map . getMapCanvasForScreenshots ( )
const filename = Map . getMapScreenshotFilename ( Active . Map )
canvas . canvas . toBlob ( imageBlob => {
const formData = new window . FormData ( )
formData . append ( 'map[screenshot]' , imageBlob , filename )
$ . ajax ( {
type : 'PATCH' ,
dataType : 'json' ,
url : ` /maps/ ${ Active . Map . id } ` ,
data : formData ,
processData : false ,
contentType : false ,
success : function ( data ) {
GlobalUI . notifyUser ( 'Successfully updated map screenshot.' )
} ,
error : function ( ) {
GlobalUI . notifyUser ( 'Failed to update map screenshot.' )
}
} )
} )
} ,
getMapCanvasForScreenshots : ( ) => {
2016-09-22 23:51:13 +08:00
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
2016-11-07 15:25:08 -05:00
canvas . getSize = function ( ) {
2016-09-22 23:51:13 +08:00
if ( this . size ) return this . size
var canvas = this . canvas
2016-10-04 23:38:32 +08:00
this . size = {
2016-09-22 23:51:13 +08:00
width : canvas . width ,
height : canvas . height
}
2016-10-04 23:38:32 +08:00
return this . size
2016-09-22 23:51:13 +08:00
}
2016-11-07 15:25:08 -05:00
canvas . scale = function ( x , y ) {
const px = this . scaleOffsetX * x
const py = this . scaleOffsetY * y
const dx = this . translateOffsetX * ( x - 1 ) / px
const dy = this . translateOffsetY * ( y - 1 ) / py
2016-09-22 23:51:13 +08:00
this . scaleOffsetX = px
this . scaleOffsetY = py
this . getCtx ( ) . scale ( x , y )
this . translate ( dx , dy )
}
2016-11-07 15:25:08 -05:00
canvas . translate = function ( x , y ) {
const sx = this . scaleOffsetX
const sy = this . scaleOffsetY
2016-09-22 23:51:13 +08:00
this . translateOffsetX += x * sx
this . translateOffsetY += y * sy
this . getCtx ( ) . translate ( x , y )
}
2016-11-07 15:25:08 -05:00
canvas . getCtx = function ( ) {
2016-09-22 23:51:13 +08:00
return this . canvas . getContext ( '2d' )
}
// center it
canvas . getCtx ( ) . translate ( 1880 / 2 , 1260 / 2 )
var mGraph = 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
JIT . zoomExtents ( null , canvas , true )
2016-11-07 15:25:08 -05:00
const c = canvas . canvas
const ctx = canvas . getCtx ( )
const scale = canvas . scaleOffsetX
2016-09-22 23:51:13 +08:00
// draw a grey background
ctx . fillStyle = '#d8d9da'
2016-11-07 15:25:08 -05:00
const xPoint = ( - ( c . width / scale ) / 2 ) - ( canvas . translateOffsetX / scale )
const yPoint = ( - ( c . height / scale ) / 2 ) - ( canvas . translateOffsetY / scale )
2016-09-22 23:51:13 +08:00
ctx . fillRect ( xPoint , yPoint , c . width / scale , c . height / scale )
// draw the graph
2016-11-07 15:25:08 -05:00
mGraph . graph . eachNode ( function ( node ) {
2016-09-22 23:51:13 +08:00
var nodeAlpha = node . getData ( 'alpha' )
2016-11-07 15:25:08 -05:00
node . eachAdjacency ( function ( adj ) {
2016-09-22 23:51:13 +08:00
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
} )
2017-01-19 14:50:08 -05:00
return canvas
} ,
getMapScreenshotFilename : map => {
2016-09-22 23:51:13 +08:00
var today = new Date ( )
var dd = today . getDate ( )
2016-11-07 15:25:08 -05:00
var mm = today . getMonth ( ) + 1 // January is 0!
2016-09-22 23:51:13 +08:00
var yyyy = today . getFullYear ( )
if ( dd < 10 ) {
dd = '0' + dd
}
if ( mm < 10 ) {
mm = '0' + mm
}
today = mm + '/' + dd + '/' + yyyy
2016-10-03 06:29:35 +08:00
var mapName = map . get ( 'name' ) . split ( ' ' ) . join ( [ '-' ] )
const filename = ` metamap- ${ map . id } - ${ mapName } - ${ today } .png `
2017-01-19 14:50:08 -05:00
return filename
2016-09-22 23:51:13 +08:00
}
}
2016-09-23 08:05:26 +08:00
export { CheatSheet , InfoBox }
2016-09-22 23:51:13 +08:00
export default Map