From 79839a4b48ca68f3d2166a3dbb62403f51bb660e Mon Sep 17 00:00:00 2001 From: Connor Turland Date: Wed, 22 Mar 2017 22:36:59 -0400 Subject: [PATCH] more crazy refactoring --- frontend/src/Metamaps/GlobalUI/ReactApp.js | 4 +- frontend/src/Metamaps/GlobalUI/Search.js | 2 +- frontend/src/Metamaps/Map/ChatView.js | 33 +- frontend/src/Metamaps/Map/Create.js | 4 +- frontend/src/Metamaps/Map/Filter.js | 512 +-- frontend/src/Metamaps/Map/InfoBox.js | 694 ++-- frontend/src/Metamaps/Map/JIT.js | 3483 ++++++++--------- frontend/src/Metamaps/Map/Realtime/index.js | 636 +-- .../src/Metamaps/Map/Realtime/receivable.js | 9 +- .../src/Metamaps/Map/Realtime/sendable.js | 58 +- frontend/src/Metamaps/Map/Views/Room.js | 1 - frontend/src/Metamaps/Map/Views/index.js | 5 - frontend/src/Metamaps/Map/index.js | 17 +- frontend/src/Metamaps/index.js | 2 - .../src/components/MapView/MapChat/index.js | 2 +- realtime/global.js | 2 +- realtime/junto.js | 2 +- realtime/map.js | 2 +- realtime/reducer.js | 2 +- 19 files changed, 2737 insertions(+), 2733 deletions(-) diff --git a/frontend/src/Metamaps/GlobalUI/ReactApp.js b/frontend/src/Metamaps/GlobalUI/ReactApp.js index 4ab4d110..43df6660 100644 --- a/frontend/src/Metamaps/GlobalUI/ReactApp.js +++ b/frontend/src/Metamaps/GlobalUI/ReactApp.js @@ -108,7 +108,7 @@ const ReactApp = { mobileTitleClick: (e) => self.openMap && self.openMap.InfoBox.toggleBox(e), openInviteLightbox: () => self.openLightbox('invite'), serverData: self.serverData, - endActiveMap: mapControl.end, + endActiveMap: () => mapControl.end(self.openMap), launchNewMap: mapControl.launch, mapId: self.mapId, topicId: self.topicId @@ -159,7 +159,7 @@ const ReactApp = { openTopic: TopicCard.openTopic, metacodeSets: self.metacodeSets, updateTopic: (topic, obj) => topic.save(obj), - onTopicFollow: Topic.onTopicFollow // todo + onTopicFollow: () => {} // Topic.onTopicFollow // todo } }, getTopicProps: function() { diff --git a/frontend/src/Metamaps/GlobalUI/Search.js b/frontend/src/Metamaps/GlobalUI/Search.js index 45869f96..07c3a4a3 100644 --- a/frontend/src/Metamaps/GlobalUI/Search.js +++ b/frontend/src/Metamaps/GlobalUI/Search.js @@ -1,4 +1,4 @@ -ReactApp.currentUserReactApp.currentUser/* global $, Hogan, Bloodhound, CanvasLoader */ +/* global $, Hogan, Bloodhound, CanvasLoader */ import { browserHistory } from 'react-router' diff --git a/frontend/src/Metamaps/Map/ChatView.js b/frontend/src/Metamaps/Map/ChatView.js index 7e4043b3..9df36f07 100644 --- a/frontend/src/Metamaps/Map/ChatView.js +++ b/frontend/src/Metamaps/Map/ChatView.js @@ -4,6 +4,7 @@ import Backbone from 'backbone' import { Howl } from 'howler' import ReactApp from '../GlobalUI/ReactApp' +import MessageCollection from '../DataModel/MessageCollection' const ChatView = (map) => { const toExport = { @@ -12,19 +13,6 @@ const toExport = { messages: new Backbone.Collection(), conversationLive: false, isParticipating: false, - init: function(urls) { - const self = toExport - self.sound = new Howl({ - src: urls, - sprite: { - joinmap: [0, 561], - leavemap: [1000, 592], - receivechat: [2000, 318], - sendchat: [3000, 296], - sessioninvite: [4000, 5393, true] - } - }) - }, setNewMap: function() { const self = toExport self.unreadMessages = 0 @@ -118,13 +106,13 @@ const toExport = { addMessage: (message, isInitial, wasMe) => { const self = toExport if (!isInitial && !self.isOpen) self.unreadMessages += 1 - if (!wasMe && !isInitial && self.alertSound) self.sound.play('receivechat') + if (!wasMe && !isInitial && self.alertSound) ChatView.sound.play('receivechat') self.messages.add(message) if (!isInitial && self.isOpen) self.render() }, sendChatMessage: message => { var self = toExport - if (toExport.alertSound) toExport.sound.play('sendchat') + if (toExport.alertSound) ChatView.sound.play('sendchat') var m = new DataModel.Message({ message: message.message, resource_id: map.Active.Map.id, @@ -132,7 +120,7 @@ const toExport = { }) m.save(null, { success: function(model, response) { - self.addMessages(new DataModel.MessageCollection(model), false, true) + self.addMessages(new MessageCollection(model), false, true) }, error: function(model, response) { console.log('error!', response) @@ -150,7 +138,6 @@ const toExport = { } return toExport } - /** * @class * @static @@ -165,5 +152,17 @@ ChatView.events = { videosOff: 'ChatView:videosOff', videosOn: 'ChatView:videosOn' } +ChatView.init = function(urls) { + ChatView.sound = new Howl({ + src: urls, + sprite: { + joinmap: [0, 561], + leavemap: [1000, 592], + receivechat: [2000, 318], + sendchat: [3000, 296], + sessioninvite: [4000, 5393, true] + } + }) +} export default ChatView diff --git a/frontend/src/Metamaps/Map/Create.js b/frontend/src/Metamaps/Map/Create.js index c0db24ee..80c44b39 100644 --- a/frontend/src/Metamaps/Map/Create.js +++ b/frontend/src/Metamaps/Map/Create.js @@ -266,7 +266,7 @@ const toExport = { }) toExport.newTopic.beingCreated = true toExport.newTopic.name = '' - //Map.setHasLearnedTopicCreation(true) + map.Map.setHasLearnedTopicCreation(true) }, hide: function(force) { if (force || !toExport.newTopic.pinned) { @@ -277,7 +277,7 @@ const toExport = { toExport.newTopic.pinned = false } if (map.DataModel.Topics.length === 0) { - Map.setHasLearnedTopicCreation(false) + map.Map.setHasLearnedTopicCreation(false) } toExport.newTopic.beingCreated = false }, diff --git a/frontend/src/Metamaps/Map/Filter.js b/frontend/src/Metamaps/Map/Filter.js index 9b19d284..319a70d3 100644 --- a/frontend/src/Metamaps/Map/Filter.js +++ b/frontend/src/Metamaps/Map/Filter.js @@ -6,274 +6,274 @@ import GlobalUI, { ReactApp } from '../GlobalUI' import Settings from '../Settings' const Filter = (map) => { -const toExport = { - dataForPresentation: { - metacodes: {}, - mappers: {}, - synapses: {} - }, - filters: { - metacodes: [], - mappers: [], - synapses: [] - }, - visible: { - metacodes: [], - mappers: [], - synapses: [] - }, - reset: function() { - var self = toExport - self.filters.metacodes = [] - self.filters.mappers = [] - self.filters.synapses = [] - self.visible.metacodes = [] - self.visible.mappers = [] - self.visible.synapses = [] - self.dataForPresentation.metacodes = {} - self.dataForPresentation.mappers = {} - self.dataForPresentation.synapses = {} - ReactApp.render() - }, - // an abstraction function for checkMetacodes, checkMappers, checkSynapses to reduce - // code redundancy - updateFilters: function(collection, propertyToCheck, correlatedModel, filtersToUse, listToModify) { - var self = toExport - var newList = [] - var removed = [] - var added = [] - // the first option enables us to accept - // ['Topics', 'Synapses'] as 'collection' - if (typeof collection === 'object') { - DataModel[collection[0]].each(function(model) { - var prop = model.get(propertyToCheck) - if (prop !== null) { - prop = prop.toString() - if (newList.indexOf(prop) === -1) { - newList.push(prop) - } - } - }) - DataModel[collection[1]].each(function(model) { - var prop = model.get(propertyToCheck) - if (prop !== null) { - prop = prop.toString() - if (newList.indexOf(prop) === -1) { - newList.push(prop) - } - } - }) - } else if (typeof collection === 'string') { - DataModel[collection].each(function(model) { - var prop = model.get(propertyToCheck) - if (prop !== null) { - prop = prop.toString() - if (newList.indexOf(prop) === -1) { - newList.push(prop) - } - } - }) - } - removed = _.difference(self.filters[filtersToUse], newList) - added = _.difference(newList, self.filters[filtersToUse]) - _.each(removed, function(identifier) { - const index = self.visible[filtersToUse].indexOf(identifier) - self.visible[filtersToUse].splice(index, 1) - delete self.dataForPresentation[filtersToUse][identifier] - }) - _.each(added, function(identifier) { - const model = DataModel[correlatedModel].get(identifier) || - DataModel[correlatedModel].find(function(m) { - return m.get(propertyToCheck) === identifier - }) - self.dataForPresentation[filtersToUse][identifier] = model.prepareDataForFilter() - self.visible[filtersToUse].push(identifier) - }) - // update the list of filters with the new list we just generated - self.filters[filtersToUse] = newList - ReactApp.render() - }, - checkMetacodes: function() { - var self = toExport - self.updateFilters('Topics', 'metacode_id', 'Metacodes', 'metacodes', 'metacode') - }, - checkMappers: function() { - var self = toExport - if (map.Active.Map) { - self.updateFilters('Mappings', 'user_id', 'Mappers', 'mappers', 'mapper') - } else { - // on topic view - self.updateFilters(['Topics', 'Synapses'], 'user_id', 'Creators', 'mappers', 'mapper') - } - }, - checkSynapses: function() { - var self = toExport - self.updateFilters('Synapses', 'desc', 'Synapses', 'synapses', 'synapse') - }, - filterAllMetacodes: function(toVisible) { - var self = toExport - self.visible.metacodes = toVisible ? self.filters.metacodes.slice() : [] - ReactApp.render() - self.passFilters() - }, - filterAllMappers: function(toVisible) { - var self = toExport - self.visible.mappers = toVisible ? self.filters.mappers.slice() : [] - ReactApp.render() - self.passFilters() - }, - filterAllSynapses: function(toVisible) { - var self = toExport - self.visible.synapses = toVisible ? self.filters.synapses.slice() : [] - ReactApp.render() - self.passFilters() - }, - // an abstraction function for toggleMetacode, toggleMapper, toggleSynapse - // to reduce code redundancy - // gets called in the context of a list item in a filter box - toggleLi: function(whichToFilter, id) { - var self = toExport - if (self.visible[whichToFilter].indexOf(id) === -1) { - self.visible[whichToFilter].push(id) - } else { - const index = self.visible[whichToFilter].indexOf(id) - self.visible[whichToFilter].splice(index, 1) - } - ReactApp.render() - self.passFilters() - }, - toggleMetacode: function(id) { - var self = toExport - self.toggleLi('metacodes', id) - }, - toggleMapper: function(id) { - var self = toExport - self.toggleLi('mappers', id) - }, - toggleSynapse: function(id) { - var self = toExport - self.toggleLi('synapses', id) - }, - passFilters: function() { - var self = toExport - var visible = self.visible - - var passesMetacode, passesMapper, passesSynapse - - var opacityForFilter = map.Active.Map ? 0 : 0.4 - - map.DataModel.Topics.each(function(topic) { - var n = topic.get('node') - var metacodeId = topic.get('metacode_id').toString() - - if (visible.metacodes.indexOf(metacodeId) === -1) passesMetacode = false - else passesMetacode = true - - if (map.Active.Map) { - // when on a map, - // we filter by mapper according to the person who added the - // topic or synapse to the map - let userId = topic.getMapping().get('user_id').toString() - if (visible.mappers.indexOf(userId) === -1) passesMapper = false - else passesMapper = true - } else { - // when on a topic view, - // we filter by mapper according to the person who created the - // topic or synapse - let userId = topic.get('user_id').toString() - if (visible.mappers.indexOf(userId) === -1) passesMapper = false - else passesMapper = true - } - - if (passesMetacode && passesMapper) { - if (n) { - n.setData('alpha', 1, 'end') - } else { - console.log(topic) - } - } else { - if (n) { - map.Control.deselectNode(n, true) - n.setData('alpha', opacityForFilter, 'end') - n.eachAdjacency(function(e) { - map.Control.deselectEdge(e, true) - }) - } else { - console.log(topic) - } - } - }) - - // flag all the edges back to 'untouched' - map.DataModel.Synapses.each(function(synapse) { - var e = synapse.get('edge') - e.setData('touched', false) - }) - map.DataModel.Synapses.each(function(synapse) { - var e = synapse.get('edge') - var desc - var userId = synapse.get('user_id').toString() - - if (e && !e.getData('touched')) { - var synapses = e.getData('synapses') - - // if any of the synapses represent by the edge are still unfiltered - // leave the edge visible - passesSynapse = false - for (let i = 0; i < synapses.length; i++) { - desc = synapses[i].get('desc') - if (visible.synapses.indexOf(desc) > -1) passesSynapse = true - } - - // if the synapse description being displayed is now being - // filtered, set the displayIndex to the first unfiltered synapse if there is one - var displayIndex = e.getData('displayIndex') ? e.getData('displayIndex') : 0 - var displayedSynapse = synapses[displayIndex] - desc = displayedSynapse.get('desc') - if (passesSynapse && visible.synapses.indexOf(desc) === -1) { - // iterate and find an unfiltered one - for (let i = 0; i < synapses.length; i++) { - desc = synapses[i].get('desc') - if (visible.synapses.indexOf(desc) > -1) { - e.setData('displayIndex', i) - break + const toExport = { + dataForPresentation: { + metacodes: {}, + mappers: {}, + synapses: {} + }, + filters: { + metacodes: [], + mappers: [], + synapses: [] + }, + visible: { + metacodes: [], + mappers: [], + synapses: [] + }, + reset: function() { + var self = toExport + self.filters.metacodes = [] + self.filters.mappers = [] + self.filters.synapses = [] + self.visible.metacodes = [] + self.visible.mappers = [] + self.visible.synapses = [] + self.dataForPresentation.metacodes = {} + self.dataForPresentation.mappers = {} + self.dataForPresentation.synapses = {} + ReactApp.render() + }, + // an abstraction function for checkMetacodes, checkMappers, checkSynapses to reduce + // code redundancy + updateFilters: function(collection, propertyToCheck, correlatedModel, filtersToUse, listToModify) { + var self = toExport + var newList = [] + var removed = [] + var added = [] + // the first option enables us to accept + // ['Topics', 'Synapses'] as 'collection' + if (typeof collection === 'object') { + map.DataModel[collection[0]].each(function(model) { + var prop = model.get(propertyToCheck) + if (prop !== null) { + prop = prop.toString() + if (newList.indexOf(prop) === -1) { + newList.push(prop) } } - } + }) + map.DataModel[collection[1]].each(function(model) { + var prop = model.get(propertyToCheck) + if (prop !== null) { + prop = prop.toString() + if (newList.indexOf(prop) === -1) { + newList.push(prop) + } + } + }) + } else if (typeof collection === 'string') { + map.DataModel[collection].each(function(model) { + var prop = model.get(propertyToCheck) + if (prop !== null) { + prop = prop.toString() + if (newList.indexOf(prop) === -1) { + newList.push(prop) + } + } + }) + } + removed = _.difference(self.filters[filtersToUse], newList) + added = _.difference(newList, self.filters[filtersToUse]) + _.each(removed, function(identifier) { + const index = self.visible[filtersToUse].indexOf(identifier) + self.visible[filtersToUse].splice(index, 1) + delete self.dataForPresentation[filtersToUse][identifier] + }) + _.each(added, function(identifier) { + const model = map.DataModel[correlatedModel].get(identifier) || + map.DataModel[correlatedModel].find(function(m) { + return m.get(propertyToCheck) === identifier + }) + self.dataForPresentation[filtersToUse][identifier] = model.prepareDataForFilter() + self.visible[filtersToUse].push(identifier) + }) + // update the list of filters with the new list we just generated + self.filters[filtersToUse] = newList + ReactApp.render() + }, + checkMetacodes: function() { + var self = toExport + self.updateFilters('Topics', 'metacode_id', 'Metacodes', 'metacodes', 'metacode') + }, + checkMappers: function() { + var self = toExport + if (map.Active.Map) { + self.updateFilters('Mappings', 'user_id', 'Mappers', 'mappers', 'mapper') + } else { + // on topic view + self.updateFilters(['Topics', 'Synapses'], 'user_id', 'Creators', 'mappers', 'mapper') + } + }, + checkSynapses: function() { + var self = toExport + self.updateFilters('Synapses', 'desc', 'Synapses', 'synapses', 'synapse') + }, + filterAllMetacodes: function(toVisible) { + var self = toExport + self.visible.metacodes = toVisible ? self.filters.metacodes.slice() : [] + ReactApp.render() + self.passFilters() + }, + filterAllMappers: function(toVisible) { + var self = toExport + self.visible.mappers = toVisible ? self.filters.mappers.slice() : [] + ReactApp.render() + self.passFilters() + }, + filterAllSynapses: function(toVisible) { + var self = toExport + self.visible.synapses = toVisible ? self.filters.synapses.slice() : [] + ReactApp.render() + self.passFilters() + }, + // an abstraction function for toggleMetacode, toggleMapper, toggleSynapse + // to reduce code redundancy + // gets called in the context of a list item in a filter box + toggleLi: function(whichToFilter, id) { + var self = toExport + if (self.visible[whichToFilter].indexOf(id) === -1) { + self.visible[whichToFilter].push(id) + } else { + const index = self.visible[whichToFilter].indexOf(id) + self.visible[whichToFilter].splice(index, 1) + } + ReactApp.render() + self.passFilters() + }, + toggleMetacode: function(id) { + var self = toExport + self.toggleLi('metacodes', id) + }, + toggleMapper: function(id) { + var self = toExport + self.toggleLi('mappers', id) + }, + toggleSynapse: function(id) { + var self = toExport + self.toggleLi('synapses', id) + }, + passFilters: function() { + var self = toExport + var visible = self.visible + + var passesMetacode, passesMapper, passesSynapse + + var opacityForFilter = map.Active.Map ? 0 : 0.4 + + map.DataModel.Topics.each(function(topic) { + var n = topic.get('node') + var metacodeId = topic.get('metacode_id').toString() + + if (visible.metacodes.indexOf(metacodeId) === -1) passesMetacode = false + else passesMetacode = true if (map.Active.Map) { // when on a map, // we filter by mapper according to the person who added the // topic or synapse to the map - userId = synapse.getMapping().get('user_id').toString() - } - if (visible.mappers.indexOf(userId) === -1) passesMapper = false - else passesMapper = true - - var color = Settings.colors.synapses.normal - if (passesSynapse && passesMapper) { - e.setData('alpha', 1, 'end') - e.setData('color', color, 'end') + let userId = topic.getMapping().get('user_id').toString() + if (visible.mappers.indexOf(userId) === -1) passesMapper = false + else passesMapper = true } else { - map.Control.deselectEdge(e, true) - e.setData('alpha', opacityForFilter, 'end') + // when on a topic view, + // we filter by mapper according to the person who created the + // topic or synapse + let userId = topic.get('user_id').toString() + if (visible.mappers.indexOf(userId) === -1) passesMapper = false + else passesMapper = true } - e.setData('touched', true) - } else if (!e) { - console.log(synapse) - } - }) + if (passesMetacode && passesMapper) { + if (n) { + n.setData('alpha', 1, 'end') + } else { + console.log(topic) + } + } else { + if (n) { + map.Control.deselectNode(n, true) + n.setData('alpha', opacityForFilter, 'end') + n.eachAdjacency(function(e) { + map.Control.deselectEdge(e, true) + }) + } else { + console.log(topic) + } + } + }) - // run the animation - map.Visualize.mGraph.fx.animate({ - modes: ['node-property:alpha', - 'edge-property:alpha'], - duration: 200 - }) + // flag all the edges back to 'untouched' + map.DataModel.Synapses.each(function(synapse) { + var e = synapse.get('edge') + e.setData('touched', false) + }) + map.DataModel.Synapses.each(function(synapse) { + var e = synapse.get('edge') + var desc + var userId = synapse.get('user_id').toString() + + if (e && !e.getData('touched')) { + var synapses = e.getData('synapses') + + // if any of the synapses represent by the edge are still unfiltered + // leave the edge visible + passesSynapse = false + for (let i = 0; i < synapses.length; i++) { + desc = synapses[i].get('desc') + if (visible.synapses.indexOf(desc) > -1) passesSynapse = true + } + + // if the synapse description being displayed is now being + // filtered, set the displayIndex to the first unfiltered synapse if there is one + var displayIndex = e.getData('displayIndex') ? e.getData('displayIndex') : 0 + var displayedSynapse = synapses[displayIndex] + desc = displayedSynapse.get('desc') + if (passesSynapse && visible.synapses.indexOf(desc) === -1) { + // iterate and find an unfiltered one + for (let i = 0; i < synapses.length; i++) { + desc = synapses[i].get('desc') + if (visible.synapses.indexOf(desc) > -1) { + e.setData('displayIndex', i) + break + } + } + } + + if (map.Active.Map) { + // when on a map, + // we filter by mapper according to the person who added the + // topic or synapse to the map + userId = synapse.getMapping().get('user_id').toString() + } + if (visible.mappers.indexOf(userId) === -1) passesMapper = false + else passesMapper = true + + var color = Settings.colors.synapses.normal + if (passesSynapse && passesMapper) { + e.setData('alpha', 1, 'end') + e.setData('color', color, 'end') + } else { + map.Control.deselectEdge(e, true) + e.setData('alpha', opacityForFilter, 'end') + } + + e.setData('touched', true) + } else if (!e) { + console.log(synapse) + } + }) + + // run the animation + map.Visualize.mGraph.fx.animate({ + modes: ['node-property:alpha', + 'edge-property:alpha'], + duration: 200 + }) + } } -} -return toExport + return toExport } export default Filter diff --git a/frontend/src/Metamaps/Map/InfoBox.js b/frontend/src/Metamaps/Map/InfoBox.js index 344f8edb..9e4c1e5a 100644 --- a/frontend/src/Metamaps/Map/InfoBox.js +++ b/frontend/src/Metamaps/Map/InfoBox.js @@ -3,387 +3,389 @@ import outdent from 'outdent' import { browserHistory } from 'react-router' +import DataModel from '../DataModel' import GlobalUI, { ReactApp } from '../GlobalUI' import Util from '../Util' const InfoBox = (map) => { -const toExport = { - isOpen: false, - selectingPermission: false, - changePermissionText: "
As the creator, you can change the permission of this map, and the permission of all the topics and synapses you have authority to change will change as well.
", - nameHTML: outdent` - {{name}}`, - descHTML: outdent` - {{desc}}`, - userImageUrl: '', - html: '', - init: function(serverData, updateThumbnail) { - var self = toExport + const toExport = { + isOpen: false, + selectingPermission: false, + changePermissionText: "
As the creator, you can change the permission of this map, and the permission of all the topics and synapses you have authority to change will change as well.
", + nameHTML: outdent` + {{name}}`, + descHTML: outdent` + {{desc}}`, + userImageUrl: '', + html: '', + init: function(serverData, updateThumbnail) { + var self = toExport - self.updateThumbnail = updateThumbnail + self.updateThumbnail = updateThumbnail - $('.maptoExport').click(function(event) { - event.stopPropagation() - }) - $('body').click(self.close) + $('.mapInfoBox').click(function(event) { + event.stopPropagation() + }) + $('body').click(self.close) - self.attachEventListeners() + self.attachEventListeners() - self.generateBoxHTML = Hogan.compile($('#maptoExportTemplate').html()) + self.generateBoxHTML = Hogan.compile($('#mapInfoBoxTemplate').html()) - self.userImageUrl = serverData['user.png'] + self.userImageUrl = serverData['user.png'] - var querystring = window.location.search.replace(/^\?/, '') - if (querystring === 'new') { - self.open() - $('.maptoExport').addClass('mapRequestTitle') - $('#mapInfoName').trigger('click') - $('#mapInfoName textarea').focus() - $('#mapInfoName textarea').select() - } - }, - toggleBox: function(event) { - var self = toExport - - if (self.isOpen) self.close() - else self.open() - - event.stopPropagation() - }, - open: function() { - var self = toExport - $('.mapInfoIcon div').addClass('hide') - $('.maptoExport').fadeIn(200, function() { - self.isOpen = true - }) - }, - close: function() { - var self = toExport - $('.mapInfoIcon div').removeClass('hide') - $('.maptoExport').fadeOut(200, function() { - self.isOpen = false - self.hidePermissionSelect() - $('.mapContributors .tip').hide() - }) - }, - load: function() { - var self = toExport - - var map = map.Active.Map - - var obj = map.pick('permission', 'topic_count', 'synapse_count') - - var isCreator = map.authorizePermissionChange(map.Active.Mapper) - var canEdit = map.authorizeToEdit(map.Active.Mapper) - var relevantPeople = map.get('permission') === 'commons' ? map.DataModel.Mappers : map.DataModel.Collaborators - 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['contributor_count'] = relevantPeople.length - obj['contributors_class'] = relevantPeople.length > 1 ? 'multiple' : '' - obj['contributors_class'] += relevantPeople.length === 2 ? ' mTwo' : '' - obj['contributor_image'] = relevantPeople.length > 0 ? relevantPeople.models[0].get('image') : self.userImageUrl - 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') - - self.html = self.generateBoxHTML.render(obj) - ReactApp.render() - self.attachEventListeners() - }, - attachEventListeners: function() { - var self = toExport - - $('.maptoExport.canEdit .best_in_place').best_in_place() - - // because anyone who can edit the map can change the map title - var bipName = $('.maptoExport .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('
') - - var callback = function(data) { - $('.nameCounter.forMap').html(data.all + '/140') + var querystring = window.location.search.replace(/^\?/, '') + if (querystring === 'new') { + self.open() + $('.mapInfoBox').addClass('mapRequestTitle') + $('#mapInfoName').trigger('click') + $('#mapInfoName textarea').focus() + $('#mapInfoName textarea').select() } - Countable.live(el, callback) - }) - bipName.unbind('best_in_place:deactivate').bind('best_in_place:deactivate', function() { - $('.nameCounter.forMap').remove() - }) + }, + toggleBox: function(event) { + var self = toExport - $('.mapInfoName .best_in_place_name').unbind('ajax:success').bind('ajax:success', function() { - var name = $(this).html() - map.Active.Map.set('name', name) - map.Active.Map.trigger('saved') - // mobile menu - $('#header_content').html(name) - $('.maptoExport').removeClass('mapRequestTitle') - document.title = `${name} | Metamaps` - window.history.replaceState('', `${name} | Metamaps`, window.location.pathname) - }) + if (self.isOpen) self.close() + else self.open() - $('.mapInfoDesc .best_in_place_desc').unbind('ajax:success').bind('ajax:success', function() { - var desc = $(this).html() - map.Active.Map.set('desc', desc) - map.Active.Map.trigger('saved') - }) - - $('.mapInfoDesc .best_in_place_desc, .mapInfoName .best_in_place_name').unbind('keypress').keypress(function(e) { - const ENTER = 13 - if (e.which === ENTER) { - $(this).data('bestInPlaceEditor').update() - } - }) - - $('.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 .maptoExport - $('.maptoExport.yourMap').unbind('.yourMap').bind('click.yourMap', self.hidePermissionSelect) - - $('.yourMap .mapInfoDelete').unbind().click(self.deleteActiveMap) - $('.mapInfoThumbnail').unbind().click(self.updateThumbnail) - - $('.mapContributors span, #mapContribs').unbind().click(function(event) { - $('.mapContributors .tip').toggle() event.stopPropagation() - }) - $('.mapContributors .tip').unbind().click(function(event) { - event.stopPropagation() - }) + }, + open: function() { + var self = toExport + $('.mapInfoIcon div').addClass('hide') + $('.maptoExport').fadeIn(200, function() { + self.isOpen = true + }) + }, + close: function() { + var self = toExport + $('.mapInfoIcon div').removeClass('hide') + $('.mapInfoBox').fadeOut(200, function() { + self.isOpen = false + self.hidePermissionSelect() + $('.mapContributors .tip').hide() + }) + }, + load: function() { + var self = toExport - $('.maptoExport').unbind('.hideTip').bind('click.hideTip', function() { - $('.mapContributors .tip').hide() - }) + var m = map.Active.Map - self.addTypeahead() - }, - addTypeahead: function() { - var self = toExport + var obj = m.pick('permission', 'topic_count', 'synapse_count') - if (!map.Active.Map) return + var isCreator = m.authorizePermissionChange(map.Active.Mapper) + var canEdit = m.authorizeToEdit(map.Active.Mapper) + var relevantPeople = m.get('permission') === 'commons' ? map.DataModel.Mappers : map.DataModel.Collaborators + var shareable = m.get('permission') !== 'private' - // for autocomplete - var collaborators = { - name: 'collaborators', - limit: 9999, - display: function(s) { return s.label }, - templates: { - notFound: function(s) { - return Hogan.compile($('#collaboratorSearchTemplate').html()).render({ - value: 'No results', - label: 'No results', - rtype: 'noresult', - profile: self.userImageUrl - }) - }, - suggestion: function(s) { - return Hogan.compile($('#collaboratorSearchTemplate').html()).render(s) + obj['name'] = canEdit ? Hogan.compile(self.nameHTML).render({id: m.id, name: m.get('name')}) : m.get('name') + obj['desc'] = canEdit ? Hogan.compile(self.descHTML).render({id: m.id, desc: m.get('desc')}) : m.get('desc') + obj['map_creator_tip'] = isCreator ? self.changePermissionText : '' + + obj['contributor_count'] = relevantPeople.length + obj['contributors_class'] = relevantPeople.length > 1 ? 'multiple' : '' + obj['contributors_class'] += relevantPeople.length === 2 ? ' mTwo' : '' + obj['contributor_image'] = relevantPeople.length > 0 ? relevantPeople.models[0].get('image') : self.userImageUrl + obj['contributor_list'] = self.createContributorList() + + obj['user_name'] = isCreator ? 'You' : m.get('user_name') + obj['created_at'] = m.get('created_at_clean') + obj['updated_at'] = m.get('updated_at_clean') + + self.generateBoxHTML = self.generateBoxHTML || Hogan.compile($('#mapInfoBoxTemplate').html()) + self.html = self.generateBoxHTML.render(obj) + ReactApp.render() + self.attachEventListeners() + }, + attachEventListeners: function() { + var self = toExport + + $('.maptoExport.canEdit .best_in_place').best_in_place() + + // because anyone who can edit the map can change the map title + var bipName = $('.maptoExport .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('
') + + var callback = function(data) { + $('.nameCounter.forMap').html(data.all + '/140') } - }, - source: new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), - queryTokenizer: Bloodhound.tokenizers.whitespace, - remote: { - url: '/search/mappers?term=%QUERY', - wildcard: '%QUERY' + 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() + map.Active.Map.set('name', name) + map.Active.Map.trigger('saved') + // mobile menu + $('#header_content').html(name) + $('.maptoExport').removeClass('mapRequestTitle') + document.title = `${name} | Metamaps` + window.history.replaceState('', `${name} | Metamaps`, window.location.pathname) + }) + + $('.mapInfoDesc .best_in_place_desc').unbind('ajax:success').bind('ajax:success', function() { + var desc = $(this).html() + map.Active.Map.set('desc', desc) + map.Active.Map.trigger('saved') + }) + + $('.mapInfoDesc .best_in_place_desc, .mapInfoName .best_in_place_name').unbind('keypress').keypress(function(e) { + const ENTER = 13 + if (e.which === ENTER) { + $(this).data('bestInPlaceEditor').update() } }) - } - // for adding map collaborators, who will have edit rights - if (map.Active.Mapper && map.Active.Mapper.id === map.Active.Map.get('user_id')) { - $('.collaboratorSearchField').typeahead( - { - highlight: false - }, - [collaborators] - ) - $('.collaboratorSearchField').bind('typeahead:select', self.handleResultClick) - $('.mapContributors .removeCollaborator').click(function() { - self.removeCollaborator(parseInt($(this).data('id'))) + $('.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 .maptoExport + $('.maptoExport.yourMap').unbind('.yourMap').bind('click.yourMap', self.hidePermissionSelect) + + $('.yourMap .mapInfoDelete').unbind().click(self.deleteActiveMap) + $('.mapInfoThumbnail').unbind().click(self.updateThumbnail) + + $('.mapContributors span, #mapContribs').unbind().click(function(event) { + $('.mapContributors .tip').toggle() + event.stopPropagation() + }) + $('.mapContributors .tip').unbind().click(function(event) { + event.stopPropagation() }) - } - }, - removeCollaborator: function(collaboratorId) { - var self = toExport - map.DataModel.Collaborators.remove(map.DataModel.Collaborators.get(collaboratorId)) - var mapperIds = map.DataModel.Collaborators.models.map(function(mapper) { return mapper.id }) - $.post('/maps/' + map.Active.Map.id + '/access', { access: mapperIds }) - self.updateNumbers() - }, - addCollaborator: function(newCollaboratorId) { - var self = toExport - if (map.DataModel.Collaborators.get(newCollaboratorId)) { - GlobalUI.notifyUser('That user already has access') - return - } + $('.maptoExport').unbind('.hideTip').bind('click.hideTip', function() { + $('.mapContributors .tip').hide() + }) - function callback(mapper) { - map.DataModel.Collaborators.add(mapper) + self.addTypeahead() + }, + addTypeahead: function() { + var self = toExport + + if (!map.Active.Map) return + + // for autocomplete + var collaborators = { + name: 'collaborators', + limit: 9999, + display: function(s) { return s.label }, + templates: { + notFound: function(s) { + return Hogan.compile($('#collaboratorSearchTemplate').html()).render({ + value: 'No results', + label: 'No results', + rtype: 'noresult', + profile: self.userImageUrl + }) + }, + suggestion: function(s) { + return Hogan.compile($('#collaboratorSearchTemplate').html()).render(s) + } + }, + source: new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: { + url: '/search/mappers?term=%QUERY', + wildcard: '%QUERY' + } + }) + } + + // for adding map collaborators, who will have edit rights + if (map.Active.Mapper && map.Active.Mapper.id === map.Active.Map.get('user_id')) { + $('.collaboratorSearchField').typeahead( + { + highlight: false + }, + [collaborators] + ) + $('.collaboratorSearchField').bind('typeahead:select', self.handleResultClick) + $('.mapContributors .removeCollaborator').click(function() { + self.removeCollaborator(parseInt($(this).data('id'))) + }) + } + }, + removeCollaborator: function(collaboratorId) { + var self = toExport + map.DataModel.Collaborators.remove(map.DataModel.Collaborators.get(collaboratorId)) var mapperIds = map.DataModel.Collaborators.models.map(function(mapper) { return mapper.id }) $.post('/maps/' + map.Active.Map.id + '/access', { access: mapperIds }) - var name = map.DataModel.Collaborators.get(newCollaboratorId).get('name') - GlobalUI.notifyUser(name + ' will be notified') self.updateNumbers() - } + }, + addCollaborator: function(newCollaboratorId) { + var self = toExport - $.getJSON('/users/' + newCollaboratorId + '.json', callback) - }, - handleResultClick: function(event, item) { - var self = toExport - - self.addCollaborator(item.id) - $('.collaboratorSearchField').typeahead('val', '') - }, - updateNameDescPerm: function(name, desc, perm) { - $('.maptoExport').removeClass('mapRequestTitle') - $('.mapInfoName .best_in_place_name').html(name) - $('.mapInfoDesc .best_in_place_desc').html(desc) - $('.maptoExport .mapPermission').removeClass('commons public private').addClass(perm) - }, - createContributorList: function() { - var relevantPeople = map.Active.Map.get('permission') === 'commons' ? map.DataModel.Mappers : map.DataModel.Collaborators - var activeMapperIsCreator = map.Active.Mapper && map.Active.Mapper.id === map.Active.Map.get('user_id') - var string = '' - string += '' - - if (activeMapperIsCreator) { - string += '
' - } - return string - }, - updateNumbers: function() { - if (!map.Active.Map) return - - const self = toExport - - var relevantPeople = map.Active.Map.get('permission') === 'commons' ? map.DataModel.Mappers : map.DataModel.Collaborators - - let contributorsClass = '' - if (relevantPeople.length === 2) { - contributorsClass = 'multiple mTwo' - } else if (relevantPeople.length > 2) { - contributorsClass = 'multiple' - } - - let contributorsImage = self.userImageUrl - if (relevantPeople.length > 0) { - // get the first contributor and use their image - contributorsImage = relevantPeople.models[0].get('image') - } - $('.mapContributors img').attr('src', contributorsImage).removeClass('multiple mTwo').addClass(contributorsClass) - $('.mapContributors span').text(relevantPeople.length) - $('.mapContributors .tip').html(self.createContributorList()) - self.addTypeahead() - $('.mapContributors .tip').unbind().click(function(event) { - event.stopPropagation() - }) - $('.mapTopics').text(map.DataModel.Topics.length) - $('.mapSynapses').text(map.DataModel.Synapses.length) - - $('.mapEditedAt').html('Last edited: ' + Util.nowDateFormatted()) - }, - onPermissionClick: function(event) { - var self = toExport - - if (!self.selectingPermission) { - self.selectingPermission = true - $(this).addClass('minimize') // this line flips the drop down arrow to a pull up arrow - if ($(this).hasClass('commons')) { - $(this).append('') - } else if ($(this).hasClass('public')) { - $(this).append('') - } else if ($(this).hasClass('private')) { - $(this).append('') + if (map.DataModel.Collaborators.get(newCollaboratorId)) { + GlobalUI.notifyUser('That user already has access') + return } - $('.mapPermission .permissionSelect li').click(self.selectPermission) + + function callback(mapper) { + map.DataModel.Collaborators.add(mapper) + var mapperIds = map.DataModel.Collaborators.models.map(function(mapper) { return mapper.id }) + $.post('/maps/' + map.Active.Map.id + '/access', { access: mapperIds }) + var name = map.DataModel.Collaborators.get(newCollaboratorId).get('name') + GlobalUI.notifyUser(name + ' will be notified') + self.updateNumbers() + } + + $.getJSON('/users/' + newCollaboratorId + '.json', callback) + }, + handleResultClick: function(event, item) { + var self = toExport + + self.addCollaborator(item.id) + $('.collaboratorSearchField').typeahead('val', '') + }, + updateNameDescPerm: function(name, desc, perm) { + $('.maptoExport').removeClass('mapRequestTitle') + $('.mapInfoName .best_in_place_name').html(name) + $('.mapInfoDesc .best_in_place_desc').html(desc) + $('.maptoExport .mapPermission').removeClass('commons public private').addClass(perm) + }, + createContributorList: function() { + var relevantPeople = map.Active.Map.get('permission') === 'commons' ? map.DataModel.Mappers : map.DataModel.Collaborators + var activeMapperIsCreator = map.Active.Mapper && map.Active.Mapper.id === map.Active.Map.get('user_id') + var string = '' + string += '' + + if (activeMapperIsCreator) { + string += '
' + } + return string + }, + updateNumbers: function() { + if (!map.Active.Map) return + + const self = toExport + + var relevantPeople = map.Active.Map.get('permission') === 'commons' ? map.DataModel.Mappers : map.DataModel.Collaborators + + let contributorsClass = '' + if (relevantPeople.length === 2) { + contributorsClass = 'multiple mTwo' + } else if (relevantPeople.length > 2) { + contributorsClass = 'multiple' + } + + let contributorsImage = self.userImageUrl + if (relevantPeople.length > 0) { + // get the first contributor and use their image + contributorsImage = relevantPeople.models[0].get('image') + } + $('.mapContributors img').attr('src', contributorsImage).removeClass('multiple mTwo').addClass(contributorsClass) + $('.mapContributors span').text(relevantPeople.length) + $('.mapContributors .tip').html(self.createContributorList()) + self.addTypeahead() + $('.mapContributors .tip').unbind().click(function(event) { + event.stopPropagation() + }) + $('.mapTopics').text(map.DataModel.Topics.length) + $('.mapSynapses').text(map.DataModel.Synapses.length) + + $('.mapEditedAt').html('Last edited: ' + Util.nowDateFormatted()) + }, + onPermissionClick: function(event) { + var self = toExport + + if (!self.selectingPermission) { + self.selectingPermission = true + $(this).addClass('minimize') // this line flips the drop down arrow to a pull up arrow + if ($(this).hasClass('commons')) { + $(this).append('') + } else if ($(this).hasClass('public')) { + $(this).append('') + } else if ($(this).hasClass('private')) { + $(this).append('') + } + $('.mapPermission .permissionSelect li').click(self.selectPermission) + event.stopPropagation() + } + }, + hidePermissionSelect: function() { + var self = toExport + + 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 = toExport + + self.selectingPermission = false + var permission = $(this).attr('class') + map.Active.Map.save({ + permission: permission + }) + map.Active.Map.updateMapWrapper() + const shareable = permission === 'private' ? '' : 'shareable' + $('.mapPermission').removeClass('commons public private minimize').addClass(permission) + $('.mapPermission .permissionSelect').remove() + $('.maptoExport').removeClass('shareable').addClass(shareable) event.stopPropagation() - } - }, - hidePermissionSelect: function() { - var self = toExport + }, + 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.' - 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 = toExport + var doIt = window.confirm(confirmString) + var m = map.Active.Map + var mapper = map.Active.Mapper + var authorized = map.authorizePermissionChange(mapper) - self.selectingPermission = false - var permission = $(this).attr('class') - map.Active.Map.save({ - permission: permission - }) - map.Active.Map.updateMapWrapper() - const shareable = permission === 'private' ? '' : 'shareable' - $('.mapPermission').removeClass('commons public private minimize').addClass(permission) - $('.mapPermission .permissionSelect').remove() - $('.maptoExport').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 = window.confirm(confirmString) - var map = map.Active.Map - var mapper = map.Active.Mapper - var authorized = map.authorizePermissionChange(mapper) - - if (doIt && authorized) { - toExport.close() - DataModel.Maps.map.Active.remove(map) - DataModel.Maps.Featured.remove(map) - DataModel.Maps.Mine.remove(map) - DataModel.Maps.Shared.remove(map) - map.destroy() - browserHistory.push('/') - GlobalUI.notifyUser('Map eliminated') - } else if (!authorized) { - window.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?") + if (doIt && authorized) { + toExport.close() + DataModel.Maps.map.Active.remove(m) + DataModel.Maps.Featured.remove(m) + DataModel.Maps.Mine.remove(m) + DataModel.Maps.Shared.remove(m) + m.destroy() + browserHistory.push('/') + GlobalUI.notifyUser('Map eliminated') + } else if (!authorized) { + window.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?") + } } } -} -return toExport + return toExport } export default InfoBox diff --git a/frontend/src/Metamaps/Map/JIT.js b/frontend/src/Metamaps/Map/JIT.js index 6d2448c9..b1eea1d2 100644 --- a/frontend/src/Metamaps/Map/JIT.js +++ b/frontend/src/Metamaps/Map/JIT.js @@ -17,1951 +17,1950 @@ import Util from '../Util' let panningInt const JIT = (map) => { + const toExport = { + tempInit: false, + tempNode: null, + tempNode2: null, + mouseDownPix: {}, + dragFlag: 0, + dragTolerance: 0, + virtualPointer: {}, + vizData: [], // contains the visualization-compatible graph + /** + * convert our topic JSON into something JIT can use + */ + convertModelsToJIT: function(topics, synapses) { + const jitReady = [] -return { - tempInit: false, - tempNode: null, - tempNode2: null, - mouseDownPix: {}, - dragFlag: 0, - dragTolerance: 0, - virtualPointer: {}, + const synapsesToRemove = [] + let mapping + let node + const nodes = {} + let existingEdge + let edge + const edges = [] - events: { - topicDrag: 'Metamaps:JIT:events:topicDrag', - pan: 'Metamaps:JIT:events:pan', - zoom: 'Metamaps:JIT:events:zoom', - animationDone: 'Metamaps:JIT:events:animationDone' - }, - vizData: [], // contains the visualization-compatible graph - /** - * convert our topic JSON into something JIT can use - */ - convertModelsToJIT: function(topics, synapses) { - const jitReady = [] + topics.each(function(t) { + node = t.createNode() + nodes[node.id] = node + }) + synapses.each(function(s) { + edge = s.createEdge() - const synapsesToRemove = [] - let mapping - let node - const nodes = {} - let existingEdge - let edge - const edges = [] + if (topics.get(s.get('topic1_id')) === undefined || topics.get(s.get('topic2_id')) === undefined) { + // this means it's an invalid synapse + synapsesToRemove.push(s) + } else if (nodes[edge.nodeFrom] && nodes[edge.nodeTo]) { + existingEdge = _.find(edges, { + nodeFrom: edge.nodeFrom, + nodeTo: edge.nodeTo + }) || + _.find(edges, { + nodeFrom: edge.nodeTo, + nodeTo: edge.nodeFrom + }) - topics.each(function(t) { - node = t.createNode() - nodes[node.id] = node - }) - synapses.each(function(s) { - edge = s.createEdge() - - if (topics.get(s.get('topic1_id')) === undefined || topics.get(s.get('topic2_id')) === undefined) { - // this means it's an invalid synapse - synapsesToRemove.push(s) - } else if (nodes[edge.nodeFrom] && nodes[edge.nodeTo]) { - existingEdge = _.find(edges, { - nodeFrom: edge.nodeFrom, - nodeTo: edge.nodeTo - }) || - _.find(edges, { - nodeFrom: edge.nodeTo, - nodeTo: edge.nodeFrom - }) - - if (existingEdge) { - // for when you're dealing with multiple relationships between the same two topics - if (map.Active.Map) { - mapping = s.getMapping() - existingEdge.data['$mappingIDs'].push(mapping.id) + if (existingEdge) { + // for when you're dealing with multiple relationships between the same two topics + if (map.Active.Map) { + mapping = s.getMapping() + existingEdge.data['$mappingIDs'].push(mapping.id) + } + existingEdge.data['$synapseIDs'].push(s.id) + } else { + // for when you're dealing with a topic that has relationships to many different nodes + nodes[edge.nodeFrom].adjacencies.push(edge) + edges.push(edge) } - existingEdge.data['$synapseIDs'].push(s.id) - } else { - // for when you're dealing with a topic that has relationships to many different nodes - nodes[edge.nodeFrom].adjacencies.push(edge) - edges.push(edge) } + }) + + _.each(nodes, function(node) { + jitReady.push(node) + }) + + return [jitReady, synapsesToRemove] + }, + prepareVizData: function() { + const self = toExport + let mapping + self.vizData = [] + map.Visualize.loadLater = false + const results = self.convertModelsToJIT(map.DataModel.Topics, map.DataModel.Synapses) + self.vizData = results[0] + // clean up the synapses array in case of any faulty data + _.each(results[1], function(synapse) { + mapping = synapse.getMapping() + map.DataModel.Synapses.remove(synapse) + if (map.DataModel.Mappings) map.DataModel.Mappings.remove(mapping) + }) + if (self.vizData.length === 0) { + map.Map.setHasLearnedTopicCreation(false) + map.Visualize.loadLater = true + } else { + map.Map.setHasLearnedTopicCreation(true) } - }) + map.Visualize.render() + }, // prepareVizData + edgeRender: function(adj, canvas) { + // get nodes cartesian coordinates + const pos = adj.nodeFrom.pos.getc(true) + const posChild = adj.nodeTo.pos.getc(true) - _.each(nodes, function(node) { - jitReady.push(node) - }) - - return [jitReady, synapsesToRemove] - }, - prepareVizData: function() { - const self = JIT - let mapping - self.vizData = [] - map.Visualize.loadLater = false - const results = self.convertModelsToJIT(map.DataModel.Topics, map.DataModel.Synapses) - self.vizData = results[0] - // clean up the synapses array in case of any faulty data - _.each(results[1], function(synapse) { - mapping = synapse.getMapping() - map.DataModel.Synapses.remove(synapse) - if (map.DataModel.Mappings) map.DataModel.Mappings.remove(mapping) - }) - if (self.vizData.length === 0) { - Map.setHasLearnedTopicCreation(false) - map.Visualize.loadLater = true - } else { - Map.setHasLearnedTopicCreation(true) - } - map.Visualize.render() - }, // prepareVizData - edgeRender: function(adj, canvas) { - // get nodes cartesian coordinates - const pos = adj.nodeFrom.pos.getc(true) - const posChild = adj.nodeTo.pos.getc(true) - - let synapse - if (adj.getData('displayIndex')) { - synapse = adj.getData('synapses')[adj.getData('displayIndex')] - if (!synapse) { - delete adj.data.$displayIndex + let synapse + if (adj.getData('displayIndex')) { + synapse = adj.getData('synapses')[adj.getData('displayIndex')] + if (!synapse) { + delete adj.data.$displayIndex + synapse = adj.getData('synapses')[0] + } + } else { synapse = adj.getData('synapses')[0] } - } else { - synapse = adj.getData('synapses')[0] - } - if (!synapse) return // this means there are no corresponding synapses for - // this edge, don't render it + if (!synapse) return // this means there are no corresponding synapses for + // this edge, don't render it - // label placement on edges - if (canvas.denySelected) { - const color = Settings.colors.synapses.normal - canvas.getCtx().fillStyle = canvas.getCtx().strokeStyle = color - } - map.JIT.renderEdgeArrows($jit.Graph.Plot.edgeHelper, adj, synapse, canvas) - - // check for edge label in data - let desc = synapse.get('desc') - - const showDesc = adj.getData('showDesc') - - const drawSynapseCount = function(context, x, y, count) { - /* - circle size: 16x16px - positioning: overlay and center on top right corner of synapse label - 8px left and 8px down - color: #dab539 - border color: #424242 - border size: 1.5px - font: DIN medium - font-size: 14pt - font-color: #424242 - */ - context.beginPath() - context.arc(x, y, 8, 0, 2 * Math.PI, false) - context.fillStyle = '#DAB539' - context.strokeStyle = '#424242' - context.lineWidth = 1.5 - context.closePath() - context.fill() - context.stroke() - - // add the synapse count - context.fillStyle = '#424242' - context.textAlign = 'center' - context.font = '14px din-medium' - - context.fillText(count, x, y + 5) - } - - if (!canvas.denySelected && desc !== '' && showDesc) { - // '&' to '&' - desc = Util.decodeEntities(desc) - - // now adjust the label placement - const ctx = canvas.getCtx() - ctx.font = 'bold 14px arial' - ctx.fillStyle = '#FFF' - ctx.textBaseline = 'alphabetic' - - const arrayOfLabelLines = Util.splitLine(desc, 25).split('\n') - let lineWidths = [] - for (let index = 0; index < arrayOfLabelLines.length; ++index) { - lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) + // label placement on edges + if (canvas.denySelected) { + const color = Settings.colors.synapses.normal + canvas.getCtx().fillStyle = canvas.getCtx().strokeStyle = color } - const width = Math.max.apply(null, lineWidths) + 16 - const height = (16 * arrayOfLabelLines.length) + 8 + map.JIT.renderEdgeArrows($jit.Graph.Plot.edgeHelper, adj, synapse, canvas) - const x = (pos.x + posChild.x - width) / 2 - const y = ((pos.y + posChild.y) / 2) - height / 2 + // check for edge label in data + let desc = synapse.get('desc') - const radius = 5 + const showDesc = adj.getData('showDesc') - // render background - ctx.beginPath() - ctx.moveTo(x + radius, y) - ctx.lineTo(x + width - radius, y) - ctx.quadraticCurveTo(x + width, y, x + width, y + radius) - ctx.lineTo(x + width, y + height - radius) - ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height) - ctx.lineTo(x + radius, y + height) - ctx.quadraticCurveTo(x, y + height, x, y + height - radius) - ctx.lineTo(x, y + radius) - ctx.quadraticCurveTo(x, y, x + radius, y) - ctx.closePath() - ctx.fill() + const drawSynapseCount = function(context, x, y, count) { + /* + circle size: 16x16px + positioning: overlay and center on top right corner of synapse label - 8px left and 8px down + color: #dab539 + border color: #424242 + border size: 1.5px + font: DIN medium + font-size: 14pt + font-color: #424242 + */ + context.beginPath() + context.arc(x, y, 8, 0, 2 * Math.PI, false) + context.fillStyle = '#DAB539' + context.strokeStyle = '#424242' + context.lineWidth = 1.5 + context.closePath() + context.fill() + context.stroke() - // get number of synapses - const synapseNum = adj.getData('synapses').length + // add the synapse count + context.fillStyle = '#424242' + context.textAlign = 'center' + context.font = '14px din-medium' - // render text - ctx.fillStyle = '#424242' - ctx.textAlign = 'center' - for (let index = 0; index < arrayOfLabelLines.length; ++index) { - ctx.fillText(arrayOfLabelLines[index], x + (width / 2), y + 18 + (16 * index)) + context.fillText(count, x, y + 5) } - if (synapseNum > 1) { - drawSynapseCount(ctx, x + width, y, synapseNum) - } - } else if (!canvas.denySelected && showDesc) { - // get number of synapses - const synapseNum = adj.getData('synapses').length + if (!canvas.denySelected && desc !== '' && showDesc) { + // '&' to '&' + desc = Util.decodeEntities(desc) - if (synapseNum > 1) { + // now adjust the label placement const ctx = canvas.getCtx() - const x = (pos.x + posChild.x) / 2 - const y = (pos.y + posChild.y) / 2 - drawSynapseCount(ctx, x, y, synapseNum) - } - } - }, // edgeRender - ForceDirected: { - animateSavedLayout: { - modes: ['linear'], - // TODO fix tests so we don't need _.get - transition: _.get($jit, 'Trans.Quad.easeInOut'), - duration: 800, - onComplete: function() { - map.Visualize.mGraph.busy = false - $(document).trigger(map.JIT.events.animationDone) - } - }, - animateFDLayout: { - modes: ['linear'], - // TODO fix tests so we don't need _.get - transition: _.get($jit, 'Trans.Elastic.easeOut'), - duration: 800, - onComplete: function() { - map.Visualize.mGraph.busy = false - } - }, - graphSettings: { - // id of the visualization container - injectInto: 'infovis', - // Enable zooming and panning - // by scrolling and DnD - Navigation: { - enable: true, - // Enable panning events only if we're dragging the empty - // canvas (and not a node). - panning: 'avoid nodes', - zooming: 28 // zoom speed. higher is more sensible - }, - // Change node and edge styles such as - // color and width. - // These properties are also set per node - // with dollar prefixed data-properties in the - // JSON structure. - Node: { - overridable: true, - color: '#2D6A5D', - type: 'customNode', - dim: 25 - }, - Edge: { - overridable: true, - color: Settings.colors.synapses.normal, - type: 'customEdge', - lineWidth: 2, - alpha: 1 - }, - // Native canvas text styling - Label: { - type: 'Native', // Native or HTML - size: 20, - family: 'arial', - textBaseline: 'alphabetic', - color: Settings.colors.labels.text - }, - // Add Tips - Tips: { - enable: false, - onShow: function(tip, node) {} - }, - // Add node events - Events: { - enable: true, - enableForEdges: true, - onMouseMove: function(node, eventInfo, e) { - map.JIT.onMouseMoveHandler(node, eventInfo, e) - // console.log('called mouse move handler') - }, - // Update node positions when dragged - onDragMove: function(node, eventInfo, e) { - map.JIT.onDragMoveTopicHandler(node, eventInfo, e) - // console.log('called drag move handler') - }, - onDragEnd: function(node, eventInfo, e) { - map.JIT.onDragEndTopicHandler(node, eventInfo, e, false) - // console.log('called drag end handler') - }, - onDragCancel: function(node, eventInfo, e) { - map.JIT.onDragCancelHandler(node, eventInfo, e, false) - }, - // Implement the same handler for touchscreens - onTouchStart: function(node, eventInfo, e) {}, - // Implement the same handler for touchscreens - onTouchMove: function(node, eventInfo, e) { - map.JIT.onDragMoveTopicHandler(node, eventInfo, e) - }, - // Implement the same handler for touchscreens - onTouchEnd: function(node, eventInfo, e) {}, - // Implement the same handler for touchscreens - onTouchCancel: function(node, eventInfo, e) {}, - // Add also a click handler to nodes - onClick: function(node, eventInfo, e) { - // remove the rightclickmenu - $('.rightclickmenu').remove() + ctx.font = 'bold 14px arial' + ctx.fillStyle = '#FFF' + ctx.textBaseline = 'alphabetic' - if (map.Mouse.boxStartCoordinates) { - if (e.ctrlKey) { - map.Visualize.mGraph.busy = false - map.Mouse.boxEndCoordinates = eventInfo.getPos() + const arrayOfLabelLines = Util.splitLine(desc, 25).split('\n') + let lineWidths = [] + for (let index = 0; index < arrayOfLabelLines.length; ++index) { + lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) + } + const width = Math.max.apply(null, lineWidths) + 16 + const height = (16 * arrayOfLabelLines.length) + 8 + + const x = (pos.x + posChild.x - width) / 2 + const y = ((pos.y + posChild.y) / 2) - height / 2 + + const radius = 5 + + // render background + ctx.beginPath() + ctx.moveTo(x + radius, y) + ctx.lineTo(x + width - radius, y) + ctx.quadraticCurveTo(x + width, y, x + width, y + radius) + ctx.lineTo(x + width, y + height - radius) + ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height) + ctx.lineTo(x + radius, y + height) + ctx.quadraticCurveTo(x, y + height, x, y + height - radius) + ctx.lineTo(x, y + radius) + ctx.quadraticCurveTo(x, y, x + radius, y) + ctx.closePath() + ctx.fill() + + // get number of synapses + const synapseNum = adj.getData('synapses').length + + // render text + ctx.fillStyle = '#424242' + ctx.textAlign = 'center' + for (let index = 0; index < arrayOfLabelLines.length; ++index) { + ctx.fillText(arrayOfLabelLines[index], x + (width / 2), y + 18 + (16 * index)) + } + + if (synapseNum > 1) { + drawSynapseCount(ctx, x + width, y, synapseNum) + } + } else if (!canvas.denySelected && showDesc) { + // get number of synapses + const synapseNum = adj.getData('synapses').length + + if (synapseNum > 1) { + const ctx = canvas.getCtx() + const x = (pos.x + posChild.x) / 2 + const y = (pos.y + posChild.y) / 2 + drawSynapseCount(ctx, x, y, synapseNum) + } + } + }, // edgeRender + ForceDirected: { + animateSavedLayout: { + modes: ['linear'], + // TODO fix tests so we don't need _.get + transition: _.get($jit, 'Trans.Quad.easeInOut'), + duration: 800, + onComplete: function() { + map.Visualize.mGraph.busy = false + $(document).trigger(JIT.events.animationDone) + } + }, + animateFDLayout: { + modes: ['linear'], + // TODO fix tests so we don't need _.get + transition: _.get($jit, 'Trans.Elastic.easeOut'), + duration: 800, + onComplete: function() { + map.Visualize.mGraph.busy = false + } + }, + graphSettings: { + // id of the visualization container + injectInto: 'infovis', + // Enable zooming and panning + // by scrolling and DnD + Navigation: { + enable: true, + // Enable panning events only if we're dragging the empty + // canvas (and not a node). + panning: 'avoid nodes', + zooming: 28 // zoom speed. higher is more sensible + }, + // Change node and edge styles such as + // color and width. + // These properties are also set per node + // with dollar prefixed data-properties in the + // JSON structure. + Node: { + overridable: true, + color: '#2D6A5D', + type: 'customNode', + dim: 25 + }, + Edge: { + overridable: true, + color: Settings.colors.synapses.normal, + type: 'customEdge', + lineWidth: 2, + alpha: 1 + }, + // Native canvas text styling + Label: { + type: 'Native', // Native or HTML + size: 20, + family: 'arial', + textBaseline: 'alphabetic', + color: Settings.colors.labels.text + }, + // Add Tips + Tips: { + enable: false, + onShow: function(tip, node) {} + }, + // Add node events + Events: { + enable: true, + enableForEdges: true, + onMouseMove: function(node, eventInfo, e) { + map.JIT.onMouseMoveHandler(node, eventInfo, e) + // console.log('called mouse move handler') + }, + // Update node positions when dragged + onDragMove: function(node, eventInfo, e) { + map.JIT.onDragMoveTopicHandler(node, eventInfo, e) + // console.log('called drag move handler') + }, + onDragEnd: function(node, eventInfo, e) { + map.JIT.onDragEndTopicHandler(node, eventInfo, e, false) + // console.log('called drag end handler') + }, + onDragCancel: function(node, eventInfo, e) { + map.JIT.onDragCancelHandler(node, eventInfo, e, false) + }, + // Implement the same handler for touchscreens + onTouchStart: function(node, eventInfo, e) {}, + // Implement the same handler for touchscreens + onTouchMove: function(node, eventInfo, e) { + map.JIT.onDragMoveTopicHandler(node, eventInfo, e) + }, + // Implement the same handler for touchscreens + onTouchEnd: function(node, eventInfo, e) {}, + // Implement the same handler for touchscreens + onTouchCancel: function(node, eventInfo, e) {}, + // Add also a click handler to nodes + onClick: function(node, eventInfo, e) { + // remove the rightclickmenu + $('.rightclickmenu').remove() + + if (map.Mouse.boxStartCoordinates) { + if (e.ctrlKey) { + map.Visualize.mGraph.busy = false + map.Mouse.boxEndCoordinates = eventInfo.getPos() + + const bS = map.Mouse.boxStartCoordinates + const bE = map.Mouse.boxEndCoordinates + if (Math.abs(bS.x - bE.x) > 20 && Math.abs(bS.y - bE.y) > 20) { + map.JIT.zoomToBox(e) + return + } else { + map.Mouse.boxStartCoordinates = null + map.Mouse.boxEndCoordinates = null + } + } + + if (e.shiftKey) { + map.Visualize.mGraph.busy = false + map.Mouse.boxEndCoordinates = eventInfo.getPos() + map.JIT.selectWithBox(e) - const bS = map.Mouse.boxStartCoordinates - const bE = map.Mouse.boxEndCoordinates - if (Math.abs(bS.x - bE.x) > 20 && Math.abs(bS.y - bE.y) > 20) { - map.JIT.zoomToBox(e) return - } else { - map.Mouse.boxStartCoordinates = null - map.Mouse.boxEndCoordinates = null } } - if (e.shiftKey) { + if (e.target.id !== 'infovis-canvas') return false + + // clicking on a edge, node, or clicking on blank part of canvas? + if (node.nodeFrom) { + map.JIT.selectEdgeOnClickHandler(node, e) + } else if (node && !node.nodeFrom) { + map.JIT.selectNodeOnClickHandler(node, e) + } else { + map.JIT.canvasClickHandler(eventInfo.getPos(), e) + } // if + }, + // Add also a click handler to nodes + onRightClick: function(node, eventInfo, e) { + // remove the rightclickmenu + $('.rightclickmenu').remove() + + if (map.Mouse.boxStartCoordinates) { + map.Create.newSynapse.hide() + map.Create.newTopic.hide() map.Visualize.mGraph.busy = false map.Mouse.boxEndCoordinates = eventInfo.getPos() map.JIT.selectWithBox(e) - return } + + if (e.target.id !== 'infovis-canvas') return false + + // clicking on a edge, node, or clicking on blank part of canvas? + if (node.nodeFrom) { + map.JIT.selectEdgeOnRightClickHandler(node, e) + } else if (node && !node.nodeFrom) { + map.JIT.selectNodeOnRightClickHandler(node, e) + } else { + // right click open space + map.Create.newSynapse.hide() + map.Create.newTopic.hide() + } } - - if (e.target.id !== 'infovis-canvas') return false - - // clicking on a edge, node, or clicking on blank part of canvas? - if (node.nodeFrom) { - map.JIT.selectEdgeOnClickHandler(node, e) - } else if (node && !node.nodeFrom) { - map.JIT.selectNodeOnClickHandler(node, e) - } else { - map.JIT.canvasClickHandler(eventInfo.getPos(), e) - } // if }, - // Add also a click handler to nodes - onRightClick: function(node, eventInfo, e) { - // remove the rightclickmenu - $('.rightclickmenu').remove() + // Number of iterations for the FD algorithm + iterations: 200, + // Edge length + levelDistance: 200 + }, + nodeSettings: { + 'customNode': { + 'render': function(node, canvas) { + const pos = node.pos.getc(true) + const dim = node.getData('dim') + const topic = node.getData('topic') + const metacode = topic ? topic.getMetacode() : false + const ctx = canvas.getCtx() - if (map.Mouse.boxStartCoordinates) { - map.Create.newSynapse.hide() - map.Create.newTopic.hide() - map.Visualize.mGraph.busy = false - map.Mouse.boxEndCoordinates = eventInfo.getPos() - map.JIT.selectWithBox(e) - return - } + // if the topic is selected draw a circle around it + if (!canvas.denySelected && node.selected) { + ctx.beginPath() + ctx.arc(pos.x, pos.y, dim + 3, 0, 2 * Math.PI, false) + ctx.strokeStyle = Settings.colors.topics.selected + ctx.lineWidth = 2 + ctx.stroke() + } - if (e.target.id !== 'infovis-canvas') return false + if (!metacode || + !metacode.get('image') || + !metacode.get('image').complete || + (typeof metacode.get('image').naturalWidth !== 'undefined' && + metacode.get('image').naturalWidth === 0)) { + ctx.beginPath() + ctx.arc(pos.x, pos.y, dim, 0, 2 * Math.PI, false) + ctx.fillStyle = '#B6B2FD' + ctx.fill() + } else { + ctx.drawImage(metacode.get('image'), pos.x - dim, pos.y - dim, dim * 2, dim * 2) + } - // clicking on a edge, node, or clicking on blank part of canvas? - if (node.nodeFrom) { - map.JIT.selectEdgeOnRightClickHandler(node, e) - } else if (node && !node.nodeFrom) { - map.JIT.selectNodeOnRightClickHandler(node, e) - } else { - // right click open space - map.Create.newSynapse.hide() - map.Create.newTopic.hide() + // if the topic has a link, draw a small image to indicate that + const hasLink = topic && topic.get('link') !== '' && topic.get('link') !== null + const linkImage = JIT.topicLinkImage + const linkImageLoaded = linkImage.complete || + (typeof linkImage.naturalWidth !== 'undefined' && + linkImage.naturalWidth !== 0) + if (hasLink && linkImageLoaded) { + ctx.drawImage(linkImage, pos.x - dim - 8, pos.y - dim - 8, 16, 16) + } + + // if the topic has a desc, draw a small image to indicate that + const hasDesc = topic && topic.get('desc') !== '' && topic.get('desc') !== null + const descImage = JIT.topicDescImage + const descImageLoaded = descImage.complete || + (typeof descImage.naturalWidth !== 'undefined' && + descImage.naturalWidth !== 0) + if (hasDesc && descImageLoaded) { + ctx.drawImage(descImage, pos.x + dim - 8, pos.y - dim - 8, 16, 16) + } + }, + 'contains': function(node, pos) { + const npos = node.pos.getc(true) + const dim = node.getData('dim') + const arrayOfLabelLines = Util.splitLine(node.name, 25).split('\n') + const ctx = map.Visualize.mGraph.canvas.getCtx() + + const height = 25 * arrayOfLabelLines.length + + let lineWidths = [] + for (let index = 0; index < arrayOfLabelLines.length; ++index) { + lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) + } + const width = Math.max.apply(null, lineWidths) + 8 + const labely = npos.y + node.getData('height') + 5 + height / 2 + + const overLabel = this.nodeHelper.rectangle.contains({ + x: npos.x, + y: labely + }, pos, width, height) + + return this.nodeHelper.circle.contains(npos, pos, dim) || overLabel } } }, - // Number of iterations for the FD algorithm - iterations: 200, - // Edge length + edgeSettings: { + 'customEdge': { + 'render': function(adj, canvas) { + map.JIT.edgeRender(adj, canvas) + }, + 'contains': function(adj, pos) { + const from = adj.nodeFrom.pos.getc() + const to = adj.nodeTo.pos.getc() + + // this fixes an issue where when edges are perfectly horizontal or perfectly vertical + // it becomes incredibly difficult to hover over them + if (-1 < pos.x && pos.x < 1) pos.x = 0 + if (-1 < pos.y && pos.y < 1) pos.y = 0 + + return $jit.Graph.Plot.edgeHelper.line.contains(from, to, pos, adj.Edge.epsilon + 5) + } + } + } + }, // ForceDirected + ForceDirected3D: { + animate: { + modes: ['linear'], + // TODO fix tests so we don't need _.get + transition: _.get($jit, 'Trans.Elastic.easeOut'), + duration: 2500, + onComplete: function() { + map.Visualize.mGraph.busy = false + } + }, + graphSettings: { + // id of the visualization container + injectInto: 'infovis', + type: '3D', + Scene: { + Lighting: { + enable: false, + ambient: [0.5, 0.5, 0.5], + directional: { + direction: { + x: 1, + y: 0, + z: -1 + }, + color: [0.9, 0.9, 0.9] + } + } + }, + // Enable zooming and panning + // by scrolling and DnD + Navigation: { + enable: false, + // Enable panning events only if we're dragging the empty + // canvas (and not a node). + panning: 'avoid nodes', + zooming: 10 // zoom speed. higher is more sensible + }, + // Change node and edge styles such as + // color and width. + // These properties are also set per node + // with dollar prefixed data-properties in the + // JSON structure. + Node: { + overridable: true, + type: 'sphere', + dim: 15, + color: '#ffffff' + }, + Edge: { + overridable: false, + type: 'tube', + color: '#111', + lineWidth: 3 + }, + // Native canvas text styling + Label: { + type: 'HTML', // Native or HTML + size: 10, + style: 'bold' + }, + // Add node events + Events: { + enable: true, + type: 'Native', + i: 0, + onMouseMove: function(node, eventInfo, e) { + // if(this.i++ % 3) return + const pos = eventInfo.getPos() + map.Visualize.cameraPosition.x += (pos.x - map.Visualize.cameraPosition.x) * 0.5 + map.Visualize.cameraPosition.y += (-pos.y - map.Visualize.cameraPosition.y) * 0.5 + map.Visualize.mGraph.plot() + }, + onMouseWheel: function(delta) { + map.Visualize.cameraPosition.z += -delta * 20 + map.Visualize.mGraph.plot() + }, + onClick: function() {} + }, + // Number of iterations for the FD algorithm + iterations: 200, + // Edge length + levelDistance: 100 + }, + nodeSettings: { + + }, + edgeSettings: { + + } + }, // ForceDirected3D + RGraph: { + animate: { + modes: ['polar'], + duration: 800, + onComplete: function() { + map.Visualize.mGraph.busy = false + } + }, + // this will just be used to patch the ForceDirected graphsettings with the few things which actually differ + background: { + levelDistance: 200, + numberOfCircles: 4, + CanvasStyles: { + strokeStyle: '#333', + lineWidth: 1.5 + } + }, levelDistance: 200 }, - nodeSettings: { - 'customNode': { - 'render': function(node, canvas) { - const pos = node.pos.getc(true) - const dim = node.getData('dim') - const topic = node.getData('topic') - const metacode = topic ? topic.getMetacode() : false - const ctx = canvas.getCtx() + onMouseEnter: function(edge) { + const filtered = edge.getData('alpha') === 0 - // if the topic is selected draw a circle around it - if (!canvas.denySelected && node.selected) { - ctx.beginPath() - ctx.arc(pos.x, pos.y, dim + 3, 0, 2 * Math.PI, false) - ctx.strokeStyle = Settings.colors.topics.selected - ctx.lineWidth = 2 - ctx.stroke() - } + // don't do anything if the edge is filtered + // or if the canvas is animating + if (filtered || map.Visualize.mGraph.busy) return - if (!metacode || - !metacode.get('image') || - !metacode.get('image').complete || - (typeof metacode.get('image').naturalWidth !== 'undefined' && - metacode.get('image').naturalWidth === 0)) { - ctx.beginPath() - ctx.arc(pos.x, pos.y, dim, 0, 2 * Math.PI, false) - ctx.fillStyle = '#B6B2FD' - ctx.fill() - } else { - ctx.drawImage(metacode.get('image'), pos.x - dim, pos.y - dim, dim * 2, dim * 2) - } - - // if the topic has a link, draw a small image to indicate that - const hasLink = topic && topic.get('link') !== '' && topic.get('link') !== null - const linkImage = map.JIT.topicLinkImage - const linkImageLoaded = linkImage.complete || - (typeof linkImage.naturalWidth !== 'undefined' && - linkImage.naturalWidth !== 0) - if (hasLink && linkImageLoaded) { - ctx.drawImage(linkImage, pos.x - dim - 8, pos.y - dim - 8, 16, 16) - } - - // if the topic has a desc, draw a small image to indicate that - const hasDesc = topic && topic.get('desc') !== '' && topic.get('desc') !== null - const descImage = map.JIT.topicDescImage - const descImageLoaded = descImage.complete || - (typeof descImage.naturalWidth !== 'undefined' && - descImage.naturalWidth !== 0) - if (hasDesc && descImageLoaded) { - ctx.drawImage(descImage, pos.x + dim - 8, pos.y - dim - 8, 16, 16) - } - }, - 'contains': function(node, pos) { - const npos = node.pos.getc(true) - const dim = node.getData('dim') - const arrayOfLabelLines = Util.splitLine(node.name, 25).split('\n') - const ctx = map.Visualize.mGraph.canvas.getCtx() - - const height = 25 * arrayOfLabelLines.length - - let lineWidths = [] - for (let index = 0; index < arrayOfLabelLines.length; ++index) { - lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) - } - const width = Math.max.apply(null, lineWidths) + 8 - const labely = npos.y + node.getData('height') + 5 + height / 2 - - const overLabel = this.nodeHelper.rectangle.contains({ - x: npos.x, - y: labely - }, pos, width, height) - - return this.nodeHelper.circle.contains(npos, pos, dim) || overLabel - } - } - }, - edgeSettings: { - 'customEdge': { - 'render': function(adj, canvas) { - map.JIT.edgeRender(adj, canvas) - }, - 'contains': function(adj, pos) { - const from = adj.nodeFrom.pos.getc() - const to = adj.nodeTo.pos.getc() - - // this fixes an issue where when edges are perfectly horizontal or perfectly vertical - // it becomes incredibly difficult to hover over them - if (-1 < pos.x && pos.x < 1) pos.x = 0 - if (-1 < pos.y && pos.y < 1) pos.y = 0 - - return $jit.Graph.Plot.edgeHelper.line.contains(from, to, pos, adj.Edge.epsilon + 5) - } - } - } - }, // ForceDirected - ForceDirected3D: { - animate: { - modes: ['linear'], - // TODO fix tests so we don't need _.get - transition: _.get($jit, 'Trans.Elastic.easeOut'), - duration: 2500, - onComplete: function() { - map.Visualize.mGraph.busy = false - } - }, - graphSettings: { - // id of the visualization container - injectInto: 'infovis', - type: '3D', - Scene: { - Lighting: { - enable: false, - ambient: [0.5, 0.5, 0.5], - directional: { - direction: { - x: 1, - y: 0, - z: -1 - }, - color: [0.9, 0.9, 0.9] - } - } - }, - // Enable zooming and panning - // by scrolling and DnD - Navigation: { - enable: false, - // Enable panning events only if we're dragging the empty - // canvas (and not a node). - panning: 'avoid nodes', - zooming: 10 // zoom speed. higher is more sensible - }, - // Change node and edge styles such as - // color and width. - // These properties are also set per node - // with dollar prefixed data-properties in the - // JSON structure. - Node: { - overridable: true, - type: 'sphere', - dim: 15, - color: '#ffffff' - }, - Edge: { - overridable: false, - type: 'tube', - color: '#111', - lineWidth: 3 - }, - // Native canvas text styling - Label: { - type: 'HTML', // Native or HTML - size: 10, - style: 'bold' - }, - // Add node events - Events: { - enable: true, - type: 'Native', - i: 0, - onMouseMove: function(node, eventInfo, e) { - // if(this.i++ % 3) return - const pos = eventInfo.getPos() - map.Visualize.cameraPosition.x += (pos.x - map.Visualize.cameraPosition.x) * 0.5 - map.Visualize.cameraPosition.y += (-pos.y - map.Visualize.cameraPosition.y) * 0.5 - map.Visualize.mGraph.plot() - }, - onMouseWheel: function(delta) { - map.Visualize.cameraPosition.z += -delta * 20 - map.Visualize.mGraph.plot() - }, - onClick: function() {} - }, - // Number of iterations for the FD algorithm - iterations: 200, - // Edge length - levelDistance: 100 - }, - nodeSettings: { - - }, - edgeSettings: { - - } - }, // ForceDirected3D - RGraph: { - animate: { - modes: ['polar'], - duration: 800, - onComplete: function() { - map.Visualize.mGraph.busy = false - } - }, - // this will just be used to patch the ForceDirected graphsettings with the few things which actually differ - background: { - levelDistance: 200, - numberOfCircles: 4, - CanvasStyles: { - strokeStyle: '#333', - lineWidth: 1.5 - } - }, - levelDistance: 200 - }, - onMouseEnter: function(edge) { - const filtered = edge.getData('alpha') === 0 - - // don't do anything if the edge is filtered - // or if the canvas is animating - if (filtered || map.Visualize.mGraph.busy) return - - $('canvas').css('cursor', 'pointer') - const edgeIsSelected = map.Selected.Edges.indexOf(edge) - // following if statement only executes if the edge being hovered over is not selected - if (edgeIsSelected === -1) { - edge.setData('showDesc', true, 'current') - } - - edge.setDataset('end', { - lineWidth: 4 - }) - map.Visualize.mGraph.fx.animate({ - modes: ['edge-property:lineWidth'], - duration: 100 - }) - map.Visualize.mGraph.plot() - }, // onMouseEnter - onMouseLeave: function(edge) { - if (edge.getData('alpha') === 0) return // don't do anything if the edge is filtered - $('canvas').css('cursor', 'default') - const edgeIsSelected = map.Selected.Edges.indexOf(edge) - // following if statement only executes if the edge being hovered over is not selected - if (edgeIsSelected === -1) { - edge.setData('showDesc', false, 'current') - } - - edge.setDataset('end', { - lineWidth: 2 - }) - map.Visualize.mGraph.fx.animate({ - modes: ['edge-property:lineWidth'], - duration: 100 - }) - map.Visualize.mGraph.plot() - }, // onMouseLeave - onMouseMoveHandler: function(_node, eventInfo, e) { - const self = JIT - - if (map.Visualize.mGraph.busy) return - - const node = eventInfo.getNode() - const edge = eventInfo.getEdge() - - // if we're on top of a node object, act like there aren't edges under it - if (node !== false) { - if (map.Mouse.edgeHoveringOver) { - self.onMouseLeave(map.Mouse.edgeHoveringOver) - } $('canvas').css('cursor', 'pointer') - return - } + const edgeIsSelected = map.Selected.Edges.indexOf(edge) + // following if statement only executes if the edge being hovered over is not selected + if (edgeIsSelected === -1) { + edge.setData('showDesc', true, 'current') + } - if (edge === false && map.Mouse.edgeHoveringOver !== false) { - // mouse not on an edge, but we were on an edge previously - self.onMouseLeave(map.Mouse.edgeHoveringOver) - } else if (edge !== false && map.Mouse.edgeHoveringOver === false) { - // mouse is on an edge, but there isn't a stored edge - self.onMouseEnter(edge) - } else if (edge !== false && map.Mouse.edgeHoveringOver !== edge) { - // mouse is on an edge, but a different edge is stored - self.onMouseLeave(map.Mouse.edgeHoveringOver) - self.onMouseEnter(edge) - } - - // could be false - map.Mouse.edgeHoveringOver = edge - - if (!node && !edge) { + edge.setDataset('end', { + lineWidth: 4 + }) + map.Visualize.mGraph.fx.animate({ + modes: ['edge-property:lineWidth'], + duration: 100 + }) + map.Visualize.mGraph.plot() + }, // onMouseEnter + onMouseLeave: function(edge) { + if (edge.getData('alpha') === 0) return // don't do anything if the edge is filtered $('canvas').css('cursor', 'default') - } - }, // onMouseMoveHandler - enterKeyHandler: function() { - const creatingMap = GlobalUI.lightbox - if (creatingMap === 'newmap' || creatingMap === 'forkmap') { - GlobalUI.CreateMap.submit() - } else if (map.Create.newTopic.beingCreated) { - Topic.createTopicLocally() - } else if (map.Create.newSynapse.beingCreated) { - map.Synapse.createSynapseLocally() - } - }, // enterKeyHandler - escKeyHandler: function() { - map.Control.deselectAllEdges() - map.Control.deselectAllNodes() - }, // escKeyHandler - onDragMoveTopicHandler: function(node, eventInfo, e) { - var self = JIT + const edgeIsSelected = map.Selected.Edges.indexOf(edge) + // following if statement only executes if the edge being hovered over is not selected + if (edgeIsSelected === -1) { + edge.setData('showDesc', false, 'current') + } - var authorized = map.Active.Map && map.Active.Map.authorizeToEdit(map.Active.Mapper) + edge.setDataset('end', { + lineWidth: 2 + }) + map.Visualize.mGraph.fx.animate({ + modes: ['edge-property:lineWidth'], + duration: 100 + }) + map.Visualize.mGraph.plot() + }, // onMouseLeave + onMouseMoveHandler: function(_node, eventInfo, e) { + const self = toExport - if (node && !node.nodeFrom) { - self.handleSelectionBeforeDragging(node, e) + if (map.Visualize.mGraph.busy) return - const pos = eventInfo.getPos() - const EDGE_THICKNESS = 30 - const SHIFT = 2 / map.Visualize.mGraph.canvas.scaleOffsetX - const PERIOD = 5 + const node = eventInfo.getNode() + const edge = eventInfo.getEdge() - // self.virtualPointer = pos; - - // if it's a left click, or a touch, move the node - if (e.touches || (e.button === 0 && !e.altKey && !e.ctrlKey && (e.buttons === 0 || e.buttons === 1 || e.buttons === undefined))) { - const width = map.Visualize.mGraph.canvas.getSize().width - const height = map.Visualize.mGraph.canvas.getSize().height - const xPix = Util.coordsToPixels(map.Visualize.mGraph, pos).x - const yPix = Util.coordsToPixels(map.Visualize.mGraph, pos).y - - if (self.dragFlag === 0) { - self.mouseDownPix = Util.coordsToPixels(map.Visualize.mGraph, eventInfo.getPos()) - self.dragFlag = 1 + // if we're on top of a node object, act like there aren't edges under it + if (node !== false) { + if (map.Mouse.edgeHoveringOver) { + self.onMouseLeave(map.Mouse.edgeHoveringOver) } + $('canvas').css('cursor', 'pointer') + return + } - if (Util.getDistance(Util.coordsToPixels(map.Visualize.mGraph, pos), self.mouseDownPix) > 2 && !self.dragTolerance) { - self.dragTolerance = 1 - } + if (edge === false && map.Mouse.edgeHoveringOver !== false) { + // mouse not on an edge, but we were on an edge previously + self.onMouseLeave(map.Mouse.edgeHoveringOver) + } else if (edge !== false && map.Mouse.edgeHoveringOver === false) { + // mouse is on an edge, but there isn't a stored edge + self.onMouseEnter(edge) + } else if (edge !== false && map.Mouse.edgeHoveringOver !== edge) { + // mouse is on an edge, but a different edge is stored + self.onMouseLeave(map.Mouse.edgeHoveringOver) + self.onMouseEnter(edge) + } - if (xPix < EDGE_THICKNESS && self.dragTolerance) { - clearInterval(self.dragLeftEdge) - clearInterval(self.dragRightEdge) - clearInterval(self.dragTopEdge) - clearInterval(self.dragBottomEdge) - self.virtualPointer = { x: Util.pixelsToCoords(map.Visualize.mGraph, { x: EDGE_THICKNESS, y: yPix }).x - SHIFT, y: pos.y } - map.Visualize.mGraph.canvas.translate(SHIFT, 0) - self.updateTopicPositions(node, self.virtualPointer) - map.Visualize.mGraph.plot() + // could be false + map.Mouse.edgeHoveringOver = edge - self.dragLeftEdge = setInterval(function() { + if (!node && !edge) { + $('canvas').css('cursor', 'default') + } + }, // onMouseMoveHandler + enterKeyHandler: function() { + const creatingMap = GlobalUI.lightbox + if (creatingMap === 'newmap' || creatingMap === 'forkmap') { + GlobalUI.CreateMap.submit() + } else if (map.Create.newTopic.beingCreated) { + Topic.createTopicLocally() + } else if (map.Create.newSynapse.beingCreated) { + map.Synapse.createSynapseLocally() + } + }, // enterKeyHandler + escKeyHandler: function() { + map.Control.deselectAllEdges() + map.Control.deselectAllNodes() + }, // escKeyHandler + onDragMoveTopicHandler: function(node, eventInfo, e) { + var self = toExport + + var authorized = map.Active.Map && map.Active.Map.authorizeToEdit(map.Active.Mapper) + + if (node && !node.nodeFrom) { + self.handleSelectionBeforeDragging(node, e) + + const pos = eventInfo.getPos() + const EDGE_THICKNESS = 30 + const SHIFT = 2 / map.Visualize.mGraph.canvas.scaleOffsetX + const PERIOD = 5 + + // self.virtualPointer = pos; + + // if it's a left click, or a touch, move the node + if (e.touches || (e.button === 0 && !e.altKey && !e.ctrlKey && (e.buttons === 0 || e.buttons === 1 || e.buttons === undefined))) { + const width = map.Visualize.mGraph.canvas.getSize().width + const height = map.Visualize.mGraph.canvas.getSize().height + const xPix = Util.coordsToPixels(map.Visualize.mGraph, pos).x + const yPix = Util.coordsToPixels(map.Visualize.mGraph, pos).y + + if (self.dragFlag === 0) { + self.mouseDownPix = Util.coordsToPixels(map.Visualize.mGraph, eventInfo.getPos()) + self.dragFlag = 1 + } + + if (Util.getDistance(Util.coordsToPixels(map.Visualize.mGraph, pos), self.mouseDownPix) > 2 && !self.dragTolerance) { + self.dragTolerance = 1 + } + + if (xPix < EDGE_THICKNESS && self.dragTolerance) { + clearInterval(self.dragLeftEdge) + clearInterval(self.dragRightEdge) + clearInterval(self.dragTopEdge) + clearInterval(self.dragBottomEdge) self.virtualPointer = { x: Util.pixelsToCoords(map.Visualize.mGraph, { x: EDGE_THICKNESS, y: yPix }).x - SHIFT, y: pos.y } map.Visualize.mGraph.canvas.translate(SHIFT, 0) self.updateTopicPositions(node, self.virtualPointer) map.Visualize.mGraph.plot() - }, PERIOD) - } - if (width - xPix < EDGE_THICKNESS && self.dragTolerance) { - clearInterval(self.dragLeftEdge) - clearInterval(self.dragRightEdge) - clearInterval(self.dragTopEdge) - clearInterval(self.dragBottomEdge) - self.virtualPointer = { x: Util.pixelsToCoords(map.Visualize.mGraph, { x: width - EDGE_THICKNESS, y: yPix }).x + SHIFT, y: pos.y } - map.Visualize.mGraph.canvas.translate(-SHIFT, 0) - self.updateTopicPositions(node, self.virtualPointer) - map.Visualize.mGraph.plot() - self.dragRightEdge = setInterval(function() { + self.dragLeftEdge = setInterval(function() { + self.virtualPointer = { x: Util.pixelsToCoords(map.Visualize.mGraph, { x: EDGE_THICKNESS, y: yPix }).x - SHIFT, y: pos.y } + map.Visualize.mGraph.canvas.translate(SHIFT, 0) + self.updateTopicPositions(node, self.virtualPointer) + map.Visualize.mGraph.plot() + }, PERIOD) + } + if (width - xPix < EDGE_THICKNESS && self.dragTolerance) { + clearInterval(self.dragLeftEdge) + clearInterval(self.dragRightEdge) + clearInterval(self.dragTopEdge) + clearInterval(self.dragBottomEdge) self.virtualPointer = { x: Util.pixelsToCoords(map.Visualize.mGraph, { x: width - EDGE_THICKNESS, y: yPix }).x + SHIFT, y: pos.y } map.Visualize.mGraph.canvas.translate(-SHIFT, 0) self.updateTopicPositions(node, self.virtualPointer) map.Visualize.mGraph.plot() - }, PERIOD) - } - if (yPix < EDGE_THICKNESS && self.dragTolerance) { - clearInterval(self.dragLeftEdge) - clearInterval(self.dragRightEdge) - clearInterval(self.dragTopEdge) - clearInterval(self.dragBottomEdge) - self.virtualPointer = { x: pos.x, y: Util.pixelsToCoords(map.Visualize.mGraph, { x: xPix, y: EDGE_THICKNESS }).y - SHIFT } - map.Visualize.mGraph.canvas.translate(0, SHIFT) - self.updateTopicPositions(node, self.virtualPointer) - map.Visualize.mGraph.plot() - self.dragTopEdge = setInterval(function() { + self.dragRightEdge = setInterval(function() { + self.virtualPointer = { x: Util.pixelsToCoords(map.Visualize.mGraph, { x: width - EDGE_THICKNESS, y: yPix }).x + SHIFT, y: pos.y } + map.Visualize.mGraph.canvas.translate(-SHIFT, 0) + self.updateTopicPositions(node, self.virtualPointer) + map.Visualize.mGraph.plot() + }, PERIOD) + } + if (yPix < EDGE_THICKNESS && self.dragTolerance) { + clearInterval(self.dragLeftEdge) + clearInterval(self.dragRightEdge) + clearInterval(self.dragTopEdge) + clearInterval(self.dragBottomEdge) self.virtualPointer = { x: pos.x, y: Util.pixelsToCoords(map.Visualize.mGraph, { x: xPix, y: EDGE_THICKNESS }).y - SHIFT } map.Visualize.mGraph.canvas.translate(0, SHIFT) self.updateTopicPositions(node, self.virtualPointer) map.Visualize.mGraph.plot() - }, PERIOD) - } - if (height - yPix < EDGE_THICKNESS && self.dragTolerance) { - clearInterval(self.dragLeftEdge) - clearInterval(self.dragRightEdge) - clearInterval(self.dragTopEdge) - clearInterval(self.dragBottomEdge) - self.virtualPointer = { x: pos.x, y: Util.pixelsToCoords(map.Visualize.mGraph, { x: xPix, y: height - EDGE_THICKNESS }).y + SHIFT } - map.Visualize.mGraph.canvas.translate(0, -SHIFT) - self.updateTopicPositions(node, self.virtualPointer) - map.Visualize.mGraph.plot() - self.dragBottomEdge = setInterval(function() { + self.dragTopEdge = setInterval(function() { + self.virtualPointer = { x: pos.x, y: Util.pixelsToCoords(map.Visualize.mGraph, { x: xPix, y: EDGE_THICKNESS }).y - SHIFT } + map.Visualize.mGraph.canvas.translate(0, SHIFT) + self.updateTopicPositions(node, self.virtualPointer) + map.Visualize.mGraph.plot() + }, PERIOD) + } + if (height - yPix < EDGE_THICKNESS && self.dragTolerance) { + clearInterval(self.dragLeftEdge) + clearInterval(self.dragRightEdge) + clearInterval(self.dragTopEdge) + clearInterval(self.dragBottomEdge) self.virtualPointer = { x: pos.x, y: Util.pixelsToCoords(map.Visualize.mGraph, { x: xPix, y: height - EDGE_THICKNESS }).y + SHIFT } map.Visualize.mGraph.canvas.translate(0, -SHIFT) self.updateTopicPositions(node, self.virtualPointer) map.Visualize.mGraph.plot() - }, PERIOD) - } - if (xPix >= EDGE_THICKNESS && width - xPix >= EDGE_THICKNESS && yPix >= EDGE_THICKNESS && height - yPix >= EDGE_THICKNESS) { - clearInterval(self.dragLeftEdge) - clearInterval(self.dragRightEdge) - clearInterval(self.dragTopEdge) - clearInterval(self.dragBottomEdge) + self.dragBottomEdge = setInterval(function() { + self.virtualPointer = { x: pos.x, y: Util.pixelsToCoords(map.Visualize.mGraph, { x: xPix, y: height - EDGE_THICKNESS }).y + SHIFT } + map.Visualize.mGraph.canvas.translate(0, -SHIFT) + self.updateTopicPositions(node, self.virtualPointer) + map.Visualize.mGraph.plot() + }, PERIOD) + } - self.updateTopicPositions(node, pos) - map.Visualize.mGraph.plot() - } - } else if ((e.button === 2 || (e.button === 0 && e.altKey) || e.buttons === 2) && authorized) { - // if it's a right click or holding down alt, start synapse creation ->third option is for firefox - if (map.JIT.tempInit === false) { - map.JIT.tempNode = node - map.JIT.tempInit = true + if (xPix >= EDGE_THICKNESS && width - xPix >= EDGE_THICKNESS && yPix >= EDGE_THICKNESS && height - yPix >= EDGE_THICKNESS) { + clearInterval(self.dragLeftEdge) + clearInterval(self.dragRightEdge) + clearInterval(self.dragTopEdge) + clearInterval(self.dragBottomEdge) - map.Create.newTopic.hide() - map.Create.newSynapse.hide() - // set the draw synapse start positions - var l = map.Selected.Nodes.length - if (l > 0) { - for (let i = l - 1; i >= 0; i -= 1) { - const n = map.Selected.Nodes[i] - map.Mouse.synapseStartCoordinates.push({ - x: n.pos.getc().x, - y: n.pos.getc().y - }) + self.updateTopicPositions(node, pos) + map.Visualize.mGraph.plot() + } + } else if ((e.button === 2 || (e.button === 0 && e.altKey) || e.buttons === 2) && authorized) { + // if it's a right click or holding down alt, start synapse creation ->third option is for firefox + if (map.JIT.tempInit === false) { + map.JIT.tempNode = node + map.JIT.tempInit = true + + map.Create.newTopic.hide() + map.Create.newSynapse.hide() + // set the draw synapse start positions + var l = map.Selected.Nodes.length + if (l > 0) { + for (let i = l - 1; i >= 0; i -= 1) { + const n = map.Selected.Nodes[i] + map.Mouse.synapseStartCoordinates.push({ + x: n.pos.getc().x, + y: n.pos.getc().y + }) + } + } else { + map.Mouse.synapseStartCoordinates = [{ + x: map.JIT.tempNode.pos.getc().x, + y: map.JIT.tempNode.pos.getc().y + }] + } + map.Mouse.synapseEndCoordinates = { + x: pos.x, + y: pos.y } - } else { - map.Mouse.synapseStartCoordinates = [{ - x: map.JIT.tempNode.pos.getc().x, - y: map.JIT.tempNode.pos.getc().y - }] } - map.Mouse.synapseEndCoordinates = { - x: pos.x, - y: pos.y + // + let temp = eventInfo.getNode() + if (temp !== false && temp.id !== node.id && map.Selected.Nodes.indexOf(temp) === -1) { // this means a Node has been returned + map.JIT.tempNode2 = temp + + map.Mouse.synapseEndCoordinates = { + x: map.JIT.tempNode2.pos.getc().x, + y: map.JIT.tempNode2.pos.getc().y + } + + // before making the highlighted one bigger, make sure all the others are regular size + map.Visualize.mGraph.graph.eachNode(function(n) { + n.setData('dim', 25, 'current') + }) + temp.setData('dim', 35, 'current') + map.Visualize.mGraph.plot() + } else if (!temp) { + map.JIT.tempNode2 = null + map.Visualize.mGraph.graph.eachNode(function(n) { + n.setData('dim', 25, 'current') + }) + // pop up node creation :) + var myX = e.clientX - 110 + var myY = e.clientY - 30 + $('#new_topic').css('left', myX + 'px') + $('#new_topic').css('top', myY + 'px') + map.Create.newTopic.x = eventInfo.getPos().x + map.Create.newTopic.y = eventInfo.getPos().y + map.Visualize.mGraph.plot() + + map.Mouse.synapseEndCoordinates = { + x: pos.x, + y: pos.y + } } + } else if ((e.button === 2 || (e.button === 0 && e.altKey) || e.buttons === 2) && map.Active.Topic) { + GlobalUI.notifyUser('Cannot create in Topic view.') + } else if ((e.button === 2 || (e.button === 0 && e.altKey) || e.buttons === 2) && !authorized) { + GlobalUI.notifyUser('Cannot edit this map.') } - // - let temp = eventInfo.getNode() - if (temp !== false && temp.id !== node.id && map.Selected.Nodes.indexOf(temp) === -1) { // this means a Node has been returned - map.JIT.tempNode2 = temp - - map.Mouse.synapseEndCoordinates = { - x: map.JIT.tempNode2.pos.getc().x, - y: map.JIT.tempNode2.pos.getc().y - } - - // before making the highlighted one bigger, make sure all the others are regular size - map.Visualize.mGraph.graph.eachNode(function(n) { - n.setData('dim', 25, 'current') - }) - temp.setData('dim', 35, 'current') - map.Visualize.mGraph.plot() - } else if (!temp) { - map.JIT.tempNode2 = null - map.Visualize.mGraph.graph.eachNode(function(n) { - n.setData('dim', 25, 'current') - }) - // pop up node creation :) - var myX = e.clientX - 110 - var myY = e.clientY - 30 - $('#new_topic').css('left', myX + 'px') - $('#new_topic').css('top', myY + 'px') - map.Create.newTopic.x = eventInfo.getPos().x - map.Create.newTopic.y = eventInfo.getPos().y - map.Visualize.mGraph.plot() - - map.Mouse.synapseEndCoordinates = { - x: pos.x, - y: pos.y - } - } - } else if ((e.button === 2 || (e.button === 0 && e.altKey) || e.buttons === 2) && map.Active.Topic) { - GlobalUI.notifyUser('Cannot create in Topic view.') - } else if ((e.button === 2 || (e.button === 0 && e.altKey) || e.buttons === 2) && !authorized) { - GlobalUI.notifyUser('Cannot edit this map.') } - } - }, // onDragMoveTopicHandler - onDragCancelHandler: function(node, eventInfo, e) { - map.JIT.tempNode = null - if (map.JIT.tempNode2) map.JIT.tempNode2.setData('dim', 25, 'current') - map.JIT.tempNode2 = null - map.JIT.tempInit = false - // reset the draw synapse positions to false - map.Mouse.synapseStartCoordinates = [] - map.Mouse.synapseEndCoordinates = null - map.Visualize.mGraph.plot() - }, // onDragCancelHandler - onDragEndTopicHandler: function(node, eventInfo, e) { - const self = JIT - const midpoint = {} - let pixelPos - let mapping - - clearInterval(self.dragLeftEdge) - clearInterval(self.dragRightEdge) - clearInterval(self.dragTopEdge) - clearInterval(self.dragBottomEdge) - - delete self.dragLeftEdge - delete self.dragRightEdge - delete self.dragTopEdge - delete self.dragBottomEdge - - self.dragFlag = 0 - self.dragTolerance = 0 - - if (map.JIT.tempInit && map.JIT.tempNode2 === null) { - // this means you want to add a new topic, and then a synapse - map.Create.newTopic.addSynapse = true - map.Create.newTopic.open() - } else if (map.JIT.tempInit && map.JIT.tempNode2 !== null) { - // this means you want to create a synapse between two existing topics - map.Create.newTopic.addSynapse = false - map.Create.newSynapse.topic1id = map.JIT.tempNode.getData('topic').id - map.Create.newSynapse.topic2id = map.JIT.tempNode2.getData('topic').id - map.JIT.tempNode2.setData('dim', 25, 'current') - map.Visualize.mGraph.plot() - midpoint.x = map.JIT.tempNode.pos.getc().x + (map.JIT.tempNode2.pos.getc().x - map.JIT.tempNode.pos.getc().x) / 2 - midpoint.y = map.JIT.tempNode.pos.getc().y + (map.JIT.tempNode2.pos.getc().y - map.JIT.tempNode.pos.getc().y) / 2 - pixelPos = Util.coordsToPixels(map.Visualize.mGraph, midpoint) - $('#new_synapse').css('left', pixelPos.x + 'px') - $('#new_synapse').css('top', pixelPos.y + 'px') - map.Create.newSynapse.open() + }, // onDragMoveTopicHandler + onDragCancelHandler: function(node, eventInfo, e) { map.JIT.tempNode = null + if (map.JIT.tempNode2) map.JIT.tempNode2.setData('dim', 25, 'current') map.JIT.tempNode2 = null map.JIT.tempInit = false - } else if (!map.JIT.tempInit && node && !node.nodeFrom) { - // this means you dragged an existing node, autosave that to the database - - // check whether to save mappings - const checkWhetherToSave = function() { - const map = map.Active.Map - if (!map) return false - return map.authorizeToEdit(map.Active.Mapper) - } - - if (checkWhetherToSave()) { - if (map.Active.Mapper.get('follow_map_on_contributed')) { - map.Active.Mapper.followMap(map.Active.Map.id) - } - mapping = node.getData('mapping') - mapping.save({ - xloc: node.getPos().x, - yloc: node.getPos().y - }) - // also save any other selected nodes that also got dragged along - const l = map.Selected.Nodes.length - for (var i = l - 1; i >= 0; i -= 1) { - const n = map.Selected.Nodes[i] - if (n !== node) { - mapping = n.getData('mapping') - mapping.save({ - xloc: n.getPos().x, - yloc: n.getPos().y - }) - } - } - } - } - }, // onDragEndTopicHandler - canvasClickHandler: function(canvasLoc, e) { - // grab the location and timestamp of the click - const storedTime = map.Mouse.lastCanvasClick - const now = Date.now() // not compatible with IE8 FYI - map.Mouse.lastCanvasClick = now - - const authorized = map.Active.Map && map.Active.Map.authorizeToEdit(map.Active.Mapper) - - if (now - storedTime < map.Mouse.DOUBLE_CLICK_TOLERANCE && !map.Mouse.didPan) { - if (map.Active.Map && !authorized) { - GlobalUI.notifyUser('Cannot edit Public map.') - return - } else if (map.Active.Topic) { - GlobalUI.notifyUser('Cannot create in Topic view.') - return - } - // DOUBLE CLICK - // pop up node creation :) - map.Create.newTopic.addSynapse = false - map.Create.newTopic.x = canvasLoc.x - map.Create.newTopic.y = canvasLoc.y - $('#new_topic').css('left', e.clientX + 'px') - $('#new_topic').css('top', e.clientY + 'px') - map.Create.newTopic.open() - } else if (!map.Mouse.didPan) { - // SINGLE CLICK, no pan - map.TopicCard.hideCard() - map.SynapseCard.hideCard() - map.Create.newTopic.hide() - $('.rightclickmenu').remove() // reset the draw synapse positions to false map.Mouse.synapseStartCoordinates = [] map.Mouse.synapseEndCoordinates = null - map.JIT.tempInit = false - map.JIT.tempNode = null - map.JIT.tempNode2 = null - if (!e.ctrlKey && !e.shiftKey) { - map.Control.deselectAllEdges() - map.Control.deselectAllNodes() + map.Visualize.mGraph.plot() + }, // onDragCancelHandler + onDragEndTopicHandler: function(node, eventInfo, e) { + const self = toExport + const midpoint = {} + let pixelPos + let mapping + + clearInterval(self.dragLeftEdge) + clearInterval(self.dragRightEdge) + clearInterval(self.dragTopEdge) + clearInterval(self.dragBottomEdge) + + delete self.dragLeftEdge + delete self.dragRightEdge + delete self.dragTopEdge + delete self.dragBottomEdge + + self.dragFlag = 0 + self.dragTolerance = 0 + + if (map.JIT.tempInit && map.JIT.tempNode2 === null) { + // this means you want to add a new topic, and then a synapse + map.Create.newTopic.addSynapse = true + map.Create.newTopic.open() + } else if (map.JIT.tempInit && map.JIT.tempNode2 !== null) { + // this means you want to create a synapse between two existing topics + map.Create.newTopic.addSynapse = false + map.Create.newSynapse.topic1id = map.JIT.tempNode.getData('topic').id + map.Create.newSynapse.topic2id = map.JIT.tempNode2.getData('topic').id + map.JIT.tempNode2.setData('dim', 25, 'current') + map.Visualize.mGraph.plot() + midpoint.x = map.JIT.tempNode.pos.getc().x + (map.JIT.tempNode2.pos.getc().x - map.JIT.tempNode.pos.getc().x) / 2 + midpoint.y = map.JIT.tempNode.pos.getc().y + (map.JIT.tempNode2.pos.getc().y - map.JIT.tempNode.pos.getc().y) / 2 + pixelPos = Util.coordsToPixels(map.Visualize.mGraph, midpoint) + $('#new_synapse').css('left', pixelPos.x + 'px') + $('#new_synapse').css('top', pixelPos.y + 'px') + map.Create.newSynapse.open() + map.JIT.tempNode = null + map.JIT.tempNode2 = null + map.JIT.tempInit = false + } else if (!map.JIT.tempInit && node && !node.nodeFrom) { + // this means you dragged an existing node, autosave that to the database + + // check whether to save mappings + const checkWhetherToSave = function() { + const map = map.Active.Map + if (!map) return false + return map.authorizeToEdit(map.Active.Mapper) + } + + if (checkWhetherToSave()) { + if (map.Active.Mapper.get('follow_map_on_contributed')) { + map.Active.Mapper.followMap(map.Active.Map.id) + } + mapping = node.getData('mapping') + mapping.save({ + xloc: node.getPos().x, + yloc: node.getPos().y + }) + // also save any other selected nodes that also got dragged along + const l = map.Selected.Nodes.length + for (var i = l - 1; i >= 0; i -= 1) { + const n = map.Selected.Nodes[i] + if (n !== node) { + mapping = n.getData('mapping') + mapping.save({ + xloc: n.getPos().x, + yloc: n.getPos().y + }) + } + } + } } - } else { - // SINGLE CLICK, resulting from pan - map.Create.newTopic.hide() - } - }, // canvasClickHandler - updateTopicPositions: function(node, pos) { - const len = map.Selected.Nodes.length - // this is used to send nodes that are moving to - // other realtime collaborators on the same map - const positionsToSend = {} + }, // onDragEndTopicHandler + canvasClickHandler: function(canvasLoc, e) { + // grab the location and timestamp of the click + const storedTime = map.Mouse.lastCanvasClick + const now = Date.now() // not compatible with IE8 FYI + map.Mouse.lastCanvasClick = now - // first define offset for each node - var xOffset = [] - var yOffset = [] - for (let i = 0; i < len; i += 1) { - const n = map.Selected.Nodes[i] - xOffset[i] = n.pos.getc().x - node.pos.getc().x - yOffset[i] = n.pos.getc().y - node.pos.getc().y - } // for + const authorized = map.Active.Map && map.Active.Map.authorizeToEdit(map.Active.Mapper) - for (let i = 0; i < len; i += 1) { - const n = map.Selected.Nodes[i] - const x = pos.x + xOffset[i] - const y = pos.y + yOffset[i] - if (n.pos.rho || n.pos.rho === 0) { - // this means we're in topic view - const rho = Math.sqrt(x * x + y * y) - const theta = Math.atan2(y, x) - n.pos.setp(theta, rho) + if (now - storedTime < map.Mouse.DOUBLE_CLICK_TOLERANCE && !map.Mouse.didPan) { + if (map.Active.Map && !authorized) { + GlobalUI.notifyUser('Cannot edit Public map.') + return + } else if (map.Active.Topic) { + GlobalUI.notifyUser('Cannot create in Topic view.') + return + } + // DOUBLE CLICK + // pop up node creation :) + map.Create.newTopic.addSynapse = false + map.Create.newTopic.x = canvasLoc.x + map.Create.newTopic.y = canvasLoc.y + $('#new_topic').css('left', e.clientX + 'px') + $('#new_topic').css('top', e.clientY + 'px') + map.Create.newTopic.open() + } else if (!map.Mouse.didPan) { + // SINGLE CLICK, no pan + map.TopicCard.hideCard() + map.SynapseCard.hideCard() + map.Create.newTopic.hide() + $('.rightclickmenu').remove() + // reset the draw synapse positions to false + map.Mouse.synapseStartCoordinates = [] + map.Mouse.synapseEndCoordinates = null + map.JIT.tempInit = false + map.JIT.tempNode = null + map.JIT.tempNode2 = null + if (!e.ctrlKey && !e.shiftKey) { + map.Control.deselectAllEdges() + map.Control.deselectAllNodes() + } } else { - n.pos.setc(x, y) + // SINGLE CLICK, resulting from pan + map.Create.newTopic.hide() } + }, // canvasClickHandler + updateTopicPositions: function(node, pos) { + const len = map.Selected.Nodes.length + // this is used to send nodes that are moving to + // other realtime collaborators on the same map + const positionsToSend = {} + + // first define offset for each node + var xOffset = [] + var yOffset = [] + for (let i = 0; i < len; i += 1) { + const n = map.Selected.Nodes[i] + xOffset[i] = n.pos.getc().x - node.pos.getc().x + yOffset[i] = n.pos.getc().y - node.pos.getc().y + } // for + + for (let i = 0; i < len; i += 1) { + const n = map.Selected.Nodes[i] + const x = pos.x + xOffset[i] + const y = pos.y + yOffset[i] + if (n.pos.rho || n.pos.rho === 0) { + // this means we're in topic view + const rho = Math.sqrt(x * x + y * y) + const theta = Math.atan2(y, x) + n.pos.setp(theta, rho) + } else { + n.pos.setc(x, y) + } + + if (map.Active.Map) { + const topic = n.getData('topic') + // we use the topic ID not the node id + // because we can't depend on the node id + // to be the same as on other collaborators + // maps + positionsToSend[topic.id] = n.pos + } + } // for if (map.Active.Map) { - const topic = n.getData('topic') - // we use the topic ID not the node id - // because we can't depend on the node id - // to be the same as on other collaborators - // maps - positionsToSend[topic.id] = n.pos + $(document).trigger(JIT.events.topicDrag, [positionsToSend]) } - } // for + }, - if (map.Active.Map) { - $(document).trigger(map.JIT.events.topicDrag, [positionsToSend]) - } - }, + nodeDoubleClickHandler: function(node, e) { + map.TopicCard.showCard(node) + }, // nodeDoubleClickHandler + edgeDoubleClickHandler: function(adj, e) { + map.SynapseCard.showCard(adj, e) + }, // nodeDoubleClickHandler + nodeWasDoubleClicked: function() { + // grab the timestamp of the click + const storedTime = map.Mouse.lastNodeClick + const now = Date.now() // not compatible with IE8 FYI + map.Mouse.lastNodeClick = now - nodeDoubleClickHandler: function(node, e) { - map.TopicCard.showCard(node) - }, // nodeDoubleClickHandler - edgeDoubleClickHandler: function(adj, e) { - map.SynapseCard.showCard(adj, e) - }, // nodeDoubleClickHandler - nodeWasDoubleClicked: function() { - // grab the timestamp of the click - const storedTime = map.Mouse.lastNodeClick - const now = Date.now() // not compatible with IE8 FYI - map.Mouse.lastNodeClick = now - - if (now - storedTime < map.Mouse.DOUBLE_CLICK_TOLERANCE) { - return true - } else { - return false - } - }, // nodeWasDoubleClicked - handleSelectionBeforeDragging: function(node, e) { - if (map.Selected.Nodes.length === 0) { - map.Control.selectNode(node, e) - } - if (map.Selected.Nodes.indexOf(node) === -1) { - if (e.shiftKey) { - map.Control.selectNode(node, e) + if (now - storedTime < map.Mouse.DOUBLE_CLICK_TOLERANCE) { + return true } else { - map.Control.deselectAllEdges() - map.Control.deselectAllNodes() + return false + } + }, // nodeWasDoubleClicked + handleSelectionBeforeDragging: function(node, e) { + if (map.Selected.Nodes.length === 0) { map.Control.selectNode(node, e) } - } - }, // handleSelectionBeforeDragging - getNodeXY: function(node) { - if (typeof node.pos.x === 'number' && typeof node.pos.y === 'number') { - return node.pos - } else if (typeof node.pos.theta === 'number' && typeof node.pos.rho === 'number') { - return new $jit.Polar(node.pos.theta, node.pos.rho).getc(true) - } else { - console.error('getNodeXY: unrecognized node pos format') - return {} - } - }, - selectWithBox: function(e) { - const self = this - let sX = map.Mouse.boxStartCoordinates.x - let sY = map.Mouse.boxStartCoordinates.y - let eX = map.Mouse.boxEndCoordinates.x - let eY = map.Mouse.boxEndCoordinates.y - - if (!e.shiftKey) { - map.Control.deselectAllNodes() - map.Control.deselectAllEdges() - } - - // select all nodes that are within the box - map.Visualize.mGraph.graph.eachNode(function(n) { - const pos = self.getNodeXY(n) - const x = pos.x - const y = pos.y - - // depending on which way the person dragged the box, check that - // x and y are between the start and end values of the box - if ((sX < x && x < eX && sY < y && y < eY) || - (sX > x && x > eX && sY > y && y > eY) || - (sX > x && x > eX && sY < y && y < eY) || - (sX < x && x < eX && sY > y && y > eY)) { + if (map.Selected.Nodes.indexOf(node) === -1) { if (e.shiftKey) { - if (n.selected) { - map.Control.deselectNode(n) + map.Control.selectNode(node, e) + } else { + map.Control.deselectAllEdges() + map.Control.deselectAllNodes() + map.Control.selectNode(node, e) + } + } + }, // handleSelectionBeforeDragging + getNodeXY: function(node) { + if (typeof node.pos.x === 'number' && typeof node.pos.y === 'number') { + return node.pos + } else if (typeof node.pos.theta === 'number' && typeof node.pos.rho === 'number') { + return new $jit.Polar(node.pos.theta, node.pos.rho).getc(true) + } else { + console.error('getNodeXY: unrecognized node pos format') + return {} + } + }, + selectWithBox: function(e) { + const self = this + let sX = map.Mouse.boxStartCoordinates.x + let sY = map.Mouse.boxStartCoordinates.y + let eX = map.Mouse.boxEndCoordinates.x + let eY = map.Mouse.boxEndCoordinates.y + + if (!e.shiftKey) { + map.Control.deselectAllNodes() + map.Control.deselectAllEdges() + } + + // select all nodes that are within the box + map.Visualize.mGraph.graph.eachNode(function(n) { + const pos = self.getNodeXY(n) + const x = pos.x + const y = pos.y + + // depending on which way the person dragged the box, check that + // x and y are between the start and end values of the box + if ((sX < x && x < eX && sY < y && y < eY) || + (sX > x && x > eX && sY > y && y > eY) || + (sX > x && x > eX && sY < y && y < eY) || + (sX < x && x < eX && sY > y && y > eY)) { + if (e.shiftKey) { + if (n.selected) { + map.Control.deselectNode(n) + } else { + map.Control.selectNode(n, e) + } } else { map.Control.selectNode(n, e) } - } else { - map.Control.selectNode(n, e) } - } - }) - - // Convert selection box coordinates to traditional coordinates (+,+) in upper right - sY = -1 * sY - eY = -1 * eY - - const edgesToToggle = [] - map.DataModel.Synapses.each(function(synapse) { - const e = synapse.get('edge') - if (edgesToToggle.indexOf(e) === -1) { - edgesToToggle.push(e) - } - }) - edgesToToggle.forEach(function(edge) { - const fromNodePos = self.getNodeXY(edge.nodeFrom) - const fromNodeX = fromNodePos.x - const fromNodeY = -1 * fromNodePos.y - const toNodePos = self.getNodeXY(edge.nodeTo) - const toNodeX = toNodePos.x - const toNodeY = -1 * toNodePos.y - - let maxX = fromNodeX - let maxY = fromNodeY - let minX = fromNodeX - let minY = fromNodeY - - // Correct maxX, MaxY values - ;(toNodeX > maxX) ? (maxX = toNodeX) : (minX = toNodeX) - ;(toNodeY > maxY) ? (maxY = toNodeY) : (minY = toNodeY) - - let maxBoxX = sX - let maxBoxY = sY - let minBoxX = sX - let minBoxY = sY - - // Correct maxBoxX, maxBoxY values - ;(eX > maxBoxX) ? (maxBoxX = eX) : (minBoxX = eX) - ;(eY > maxBoxY) ? (maxBoxY = eY) : (minBoxY = eY) - - // Find the slopes from the synapse fromNode to the 4 corners of the selection box - const slopes = [] - slopes.push((sY - fromNodeY) / (sX - fromNodeX)) - slopes.push((sY - fromNodeY) / (eX - fromNodeX)) - slopes.push((eY - fromNodeY) / (eX - fromNodeX)) - slopes.push((eY - fromNodeY) / (sX - fromNodeX)) - - let minSlope = slopes[0] - let maxSlope = slopes[0] - slopes.forEach(function(entry) { - if (entry > maxSlope) maxSlope = entry - if (entry < minSlope) minSlope = entry }) - // Find synapse-in-question's slope - const synSlope = (toNodeY - fromNodeY) / (toNodeX - fromNodeX) - const b = fromNodeY - synSlope * fromNodeX + // Convert selection box coordinates to traditional coordinates (+,+) in upper right + sY = -1 * sY + eY = -1 * eY - // Use the selection box edges as test cases for synapse intersection - let testX = sX - let testY = synSlope * testX + b + const edgesToToggle = [] + map.DataModel.Synapses.each(function(synapse) { + const e = synapse.get('edge') + if (edgesToToggle.indexOf(e) === -1) { + edgesToToggle.push(e) + } + }) + edgesToToggle.forEach(function(edge) { + const fromNodePos = self.getNodeXY(edge.nodeFrom) + const fromNodeX = fromNodePos.x + const fromNodeY = -1 * fromNodePos.y + const toNodePos = self.getNodeXY(edge.nodeTo) + const toNodeX = toNodePos.x + const toNodeY = -1 * toNodePos.y - let selectTest + let maxX = fromNodeX + let maxY = fromNodeY + let minX = fromNodeX + let minY = fromNodeY - if (testX >= minX && testX <= maxX && testY >= minY && testY <= maxY && testY >= minBoxY && testY <= maxBoxY) { - selectTest = true - } + // Correct maxX, MaxY values + ;(toNodeX > maxX) ? (maxX = toNodeX) : (minX = toNodeX) + ;(toNodeY > maxY) ? (maxY = toNodeY) : (minY = toNodeY) - testX = eX - testY = synSlope * testX + b + let maxBoxX = sX + let maxBoxY = sY + let minBoxX = sX + let minBoxY = sY - if (testX >= minX && testX <= maxX && testY >= minY && testY <= maxY && testY >= minBoxY && testY <= maxBoxY) { - selectTest = true - } + // Correct maxBoxX, maxBoxY values + ;(eX > maxBoxX) ? (maxBoxX = eX) : (minBoxX = eX) + ;(eY > maxBoxY) ? (maxBoxY = eY) : (minBoxY = eY) - testY = sY - testX = (testY - b) / synSlope + // Find the slopes from the synapse fromNode to the 4 corners of the selection box + const slopes = [] + slopes.push((sY - fromNodeY) / (sX - fromNodeX)) + slopes.push((sY - fromNodeY) / (eX - fromNodeX)) + slopes.push((eY - fromNodeY) / (eX - fromNodeX)) + slopes.push((eY - fromNodeY) / (sX - fromNodeX)) - if (testX >= minX && testX <= maxX && testY >= minY && testY <= maxY && testX >= minBoxX && testX <= maxBoxX) { - selectTest = true - } + let minSlope = slopes[0] + let maxSlope = slopes[0] + slopes.forEach(function(entry) { + if (entry > maxSlope) maxSlope = entry + if (entry < minSlope) minSlope = entry + }) - testY = eY - testX = (testY - b) / synSlope + // Find synapse-in-question's slope + const synSlope = (toNodeY - fromNodeY) / (toNodeX - fromNodeX) + const b = fromNodeY - synSlope * fromNodeX - if (testX >= minX && testX <= maxX && testY >= minY && testY <= maxY && testX >= minBoxX && testX <= maxBoxX) { - selectTest = true - } + // Use the selection box edges as test cases for synapse intersection + let testX = sX + let testY = synSlope * testX + b - // Case where the synapse is wholly enclosed in the seldction box - if (fromNodeX >= minBoxX && fromNodeX <= maxBoxX && fromNodeY >= minBoxY && fromNodeY <= maxBoxY && toNodeX >= minBoxX && toNodeX <= maxBoxX && toNodeY >= minBoxY && toNodeY <= maxBoxY) { - selectTest = true - } + let selectTest - // The test synapse was selected! + if (testX >= minX && testX <= maxX && testY >= minY && testY <= maxY && testY >= minBoxY && testY <= maxBoxY) { + selectTest = true + } - if (selectTest) { - // shiftKey = toggleSelect, otherwise - if (e.shiftKey) { - if (map.Selected.Edges.indexOf(edge) !== -1) { - map.Control.deselectEdge(edge) + testX = eX + testY = synSlope * testX + b + + if (testX >= minX && testX <= maxX && testY >= minY && testY <= maxY && testY >= minBoxY && testY <= maxBoxY) { + selectTest = true + } + + testY = sY + testX = (testY - b) / synSlope + + if (testX >= minX && testX <= maxX && testY >= minY && testY <= maxY && testX >= minBoxX && testX <= maxBoxX) { + selectTest = true + } + + testY = eY + testX = (testY - b) / synSlope + + if (testX >= minX && testX <= maxX && testY >= minY && testY <= maxY && testX >= minBoxX && testX <= maxBoxX) { + selectTest = true + } + + // Case where the synapse is wholly enclosed in the seldction box + if (fromNodeX >= minBoxX && fromNodeX <= maxBoxX && fromNodeY >= minBoxY && fromNodeY <= maxBoxY && toNodeX >= minBoxX && toNodeX <= maxBoxX && toNodeY >= minBoxY && toNodeY <= maxBoxY) { + selectTest = true + } + + // The test synapse was selected! + + if (selectTest) { + // shiftKey = toggleSelect, otherwise + if (e.shiftKey) { + if (map.Selected.Edges.indexOf(edge) !== -1) { + map.Control.deselectEdge(edge) + } else { + map.Control.selectEdge(edge) + } } else { map.Control.selectEdge(edge) } - } else { - map.Control.selectEdge(edge) } + }) + map.Mouse.boxStartCoordinates = false + map.Mouse.boxEndCoordinates = false + map.Visualize.mGraph.plot() + }, // selectWithBox + drawSelectBox: function(eventInfo, e) { + const ctx = map.Visualize.mGraph.canvas.getCtx() + + const startX = map.Mouse.boxStartCoordinates.x + const startY = map.Mouse.boxStartCoordinates.y + const currX = eventInfo.getPos().x + const currY = eventInfo.getPos().y + + map.Visualize.mGraph.canvas.clear() + map.Visualize.mGraph.plot() + + ctx.beginPath() + ctx.moveTo(startX, startY) + ctx.lineTo(startX, currY) + ctx.lineTo(currX, currY) + ctx.lineTo(currX, startY) + ctx.lineTo(startX, startY) + ctx.strokeStyle = 'black' + ctx.stroke() + }, // drawSelectBox + selectNodeOnClickHandler: function(node, e) { + if (map.Visualize.mGraph.busy) return + + const self = toExport + + // Copy topic title to clipboard + if (e.button === 1 && e.ctrlKey) clipboard.copy(node.name) + + // catch right click on mac, which is often like ctrl+click + if (navigator.platform.indexOf('Mac') !== -1 && e.ctrlKey) { + self.selectNodeOnRightClickHandler(node, e) + return } - }) - map.Mouse.boxStartCoordinates = false - map.Mouse.boxEndCoordinates = false - map.Visualize.mGraph.plot() - }, // selectWithBox - drawSelectBox: function(eventInfo, e) { - const ctx = map.Visualize.mGraph.canvas.getCtx() - const startX = map.Mouse.boxStartCoordinates.x - const startY = map.Mouse.boxStartCoordinates.y - const currX = eventInfo.getPos().x - const currY = eventInfo.getPos().y + // if on a topic page, let alt+click center you on a new topic + if (map.Active.Topic && e.altKey) { + map.JIT.RGraph.centerOn(node.id) + return + } - map.Visualize.mGraph.canvas.clear() - map.Visualize.mGraph.plot() + const check = self.nodeWasDoubleClicked() + if (check) { + self.nodeDoubleClickHandler(node, e) + return + } else { + // wait a certain length of time, then check again, then run this code + setTimeout(function() { + if (!map.JIT.nodeWasDoubleClicked()) { + var nodeAlreadySelected = node.selected - ctx.beginPath() - ctx.moveTo(startX, startY) - ctx.lineTo(startX, currY) - ctx.lineTo(currX, currY) - ctx.lineTo(currX, startY) - ctx.lineTo(startX, startY) - ctx.strokeStyle = 'black' - ctx.stroke() - }, // drawSelectBox - selectNodeOnClickHandler: function(node, e) { - if (map.Visualize.mGraph.busy) return + if (e.button !== 1) { + if (!e.shiftKey) { + map.Control.deselectAllNodes() + map.Control.deselectAllEdges() + } - const self = JIT + if (nodeAlreadySelected) { + map.Control.deselectNode(node) + } else { + map.Control.selectNode(node, e) + } - // Copy topic title to clipboard - if (e.button === 1 && e.ctrlKey) clipboard.copy(node.name) + // trigger animation to final styles + map.Visualize.mGraph.fx.animate({ + modes: ['edge-property:lineWidth:color:alpha'], + duration: 500 + }) + map.Visualize.mGraph.plot() + } else { + if (!e.ctrlKey) { + var len = map.Selected.Nodes.length - // catch right click on mac, which is often like ctrl+click - if (navigator.platform.indexOf('Mac') !== -1 && e.ctrlKey) { - self.selectNodeOnRightClickHandler(node, e) - return - } + for (let i = 0; i < len; i += 1) { + let n = map.Selected.Nodes[i] + let result = Util.openLink(map.DataModel.Topics.get(n.id).attributes.link) - // if on a topic page, let alt+click center you on a new topic - if (map.Active.Topic && e.altKey) { - map.JIT.RGraph.centerOn(node.id) - return - } + if (!result) { // if link failed to open + break + } + } - const check = self.nodeWasDoubleClicked() - if (check) { - self.nodeDoubleClickHandler(node, e) - return - } else { - // wait a certain length of time, then check again, then run this code - setTimeout(function() { - if (!map.JIT.nodeWasDoubleClicked()) { - var nodeAlreadySelected = node.selected + if (!node.selected) { + Util.openLink(map.DataModel.Topics.get(node.id).attributes.link) + } + } + } + } + }, map.Mouse.DOUBLE_CLICK_TOLERANCE) + } + }, // selectNodeOnClickHandler + selectNodeOnRightClickHandler: function(node, e) { + // the 'node' variable is a JIT node, the one that was clicked on + // the 'e' variable is the click event + + e.preventDefault() + e.stopPropagation() + + if (map.Visualize.mGraph.busy) return + + // select the node + map.Control.selectNode(node, e) + + // delete old right click menu + $('.rightclickmenu').remove() + // create new menu for clicked on node + const rightclickmenu = document.createElement('div') + rightclickmenu.className = 'rightclickmenu' + // prevent the custom context menu from immediately opening the default context menu as well + rightclickmenu.setAttribute('oncontextmenu', 'return false') + + // add the proper options to the menu + let menustring = '' + rightclickmenu.innerHTML = menustring + + // position the menu where the click happened + const position = {} + const RIGHTCLICK_WIDTH = 300 + const RIGHTCLICK_HEIGHT = 144 // this does vary somewhat, but we can use static + const SUBMENUS_WIDTH = 256 + const MAX_SUBMENU_HEIGHT = 270 + const windowWidth = $(window).width() + const windowHeight = $(window).height() + + if (windowWidth - e.clientX < SUBMENUS_WIDTH) { + position.right = windowWidth - e.clientX + $(rightclickmenu).addClass('moveMenusToLeft') + } else if (windowWidth - e.clientX < RIGHTCLICK_WIDTH) { + position.right = windowWidth - e.clientX + } else if (windowWidth - e.clientX < RIGHTCLICK_WIDTH + SUBMENUS_WIDTH) { + position.left = e.clientX + $(rightclickmenu).addClass('moveMenusToLeft') + } else { + position.left = e.clientX + } + + if (windowHeight - e.clientY < MAX_SUBMENU_HEIGHT) { + position.bottom = windowHeight - e.clientY + $(rightclickmenu).addClass('moveMenusUp') + } else if (windowHeight - e.clientY < RIGHTCLICK_HEIGHT + MAX_SUBMENU_HEIGHT) { + position.top = e.clientY + $(rightclickmenu).addClass('moveMenusUp') + } else { + position.top = e.clientY + } + + $(rightclickmenu).css(position) + // add the menu to the page + $('#wrapper').append(rightclickmenu) + + ReactDOM.render( + React.createElement(MetacodeSelect, { + onMetacodeSelect: metacodeId => { + if (map.Selected.Nodes.length > 1) { + // batch update multiple topics + map.Control.updateSelectedMetacodes(metacodeId) + } else { + const topic = map.DataModel.Topics.get(node.id) + topic.save({ + metacode_id: metacodeId + }) + } + $(rightclickmenu).remove() + }, + metacodeSets: ReactApp.metacodeSets + }), + document.getElementById('metacodeOptionsWrapper') + ) + + // attach events to clicks on the list items + + // delete the selected things from the database + if (authorized) { + $('.rc-delete').click(function() { + $('.rightclickmenu').remove() + map.Control.deleteSelected() + }) + } + + // remove the selected things from the map + if (map.Active.Topic || authorized) { + $('.rc-remove').click(function() { + $('.rightclickmenu').remove() + map.Control.removeSelectedEdges() + map.Control.removeSelectedNodes() + }) + } + + // hide selected nodes and synapses until refresh + $('.rc-hide').click(function() { + $('.rightclickmenu').remove() + map.Control.hideSelectedEdges() + map.Control.hideSelectedNodes() + }) + + // when in radial, center on the topic you picked + $('.rc-center').click(function() { + $('.rightclickmenu').remove() + map.Topic.centerOn(node.id) + }) + + // open the entity in a new tab + $('.rc-popout').click(function() { + $('.rightclickmenu').remove() + const win = window.open('/topics/' + node.id, '_blank') + win.focus() + }) + + // change the permission of all the selected nodes and synapses that you were the originator of + $('.rc-permission li').click(function() { + $('.rightclickmenu').remove() + // $(this).text() will be 'commons' 'public' or 'private' + map.Control.updateSelectedPermissions($(this).text()) + }) + + // fetch relatives + let fetchSent = false + $('.rc-siblings').hover(function() { + if (!fetchSent) { + map.JIT.populateRightClickSiblings(node) + fetchSent = true + } + }) + $('.rc-siblings .fetchAll').click(function() { + $('.rightclickmenu').remove() + // data-id is a metacode id + map.Topic.fetchRelatives(node) + }) + }, // selectNodeOnRightClickHandler, + populateRightClickSiblings: function(node) { + // depending on how many topics are selected, do different things + const topic = node.getData('topic') + + // add a loading icon for now + const loader = new CanvasLoader('loadingSiblings') + loader.setColor('#4FC059') // default is '#000000' + loader.setDiameter(15) // default is 40 + loader.setDensity(41) // default is 40 + loader.setRange(0.9) // default is 1.3 + loader.show() // Hidden by default + + const topics = map.DataModel.Topics.map(function(t) { return t.id }) + const topicsString = topics.join() + + const successCallback = function(data) { + $('#loadingSiblings').remove() + + for (var key in data) { + const string = `${DataModel.Metacodes.get(key).get('name')} (${data[key]})` + $('#fetchSiblingList').append(`
  • ${string}
  • `) + } + + $('.rc-siblings .getSiblings').click(function() { + $('.rightclickmenu').remove() + // data-id is a metacode id + map.Topic.fetchRelatives(node, $(this).attr('data-id')) + }) + } + + $.ajax({ + type: 'GET', + url: '/topics/' + topic.id + '/relative_numbers.json?network=' + topicsString, + success: successCallback, + error: function() {} + }) + }, + selectEdgeOnClickHandler: function(adj, e) { + if (map.Visualize.mGraph.busy) return + + const self = toExport + var synapseText = adj.data.$synapses[0].attributes.desc + // Copy synapse label to clipboard + if (e.button === 1 && e.ctrlKey && synapseText !== '') clipboard.copy(synapseText) + + // catch right click on mac, which is often like ctrl+click + if (navigator.platform.indexOf('Mac') !== -1 && e.ctrlKey) { + self.selectEdgeOnRightClickHandler(adj, e) + return + } + + const check = self.nodeWasDoubleClicked() + if (check) { + self.edgeDoubleClickHandler(adj, e) + return + } else { + // wait a certain length of time, then check again, then run this code + setTimeout(function() { + if (!map.JIT.nodeWasDoubleClicked()) { + const edgeAlreadySelected = map.Selected.Edges.indexOf(adj) !== -1 - if (e.button !== 1) { if (!e.shiftKey) { map.Control.deselectAllNodes() map.Control.deselectAllEdges() } - if (nodeAlreadySelected) { - map.Control.deselectNode(node) + if (edgeAlreadySelected) { + map.Control.deselectEdge(adj) } else { - map.Control.selectNode(node, e) + map.Control.selectEdge(adj) } - // trigger animation to final styles - map.Visualize.mGraph.fx.animate({ - modes: ['edge-property:lineWidth:color:alpha'], - duration: 500 - }) map.Visualize.mGraph.plot() - } else { - if (!e.ctrlKey) { - var len = map.Selected.Nodes.length - - for (let i = 0; i < len; i += 1) { - let n = map.Selected.Nodes[i] - let result = Util.openLink(map.DataModel.Topics.get(n.id).attributes.link) - - if (!result) { // if link failed to open - break - } - } - - if (!node.selected) { - Util.openLink(map.DataModel.Topics.get(node.id).attributes.link) - } - } } - } - }, map.Mouse.DOUBLE_CLICK_TOLERANCE) - } - }, // selectNodeOnClickHandler - selectNodeOnRightClickHandler: function(node, e) { - // the 'node' variable is a JIT node, the one that was clicked on - // the 'e' variable is the click event + }, map.Mouse.DOUBLE_CLICK_TOLERANCE) + } + }, // selectEdgeOnClickHandler + selectEdgeOnRightClickHandler: function(adj, e) { + // the 'node' variable is a JIT node, the one that was clicked on + // the 'e' variable is the click event - e.preventDefault() - e.stopPropagation() + if (adj.getData('alpha') === 0) return // don't do anything if the edge is filtered - if (map.Visualize.mGraph.busy) return + e.preventDefault() + e.stopPropagation() - // select the node - map.Control.selectNode(node, e) + if (map.Visualize.mGraph.busy) return - // delete old right click menu - $('.rightclickmenu').remove() - // create new menu for clicked on node - const rightclickmenu = document.createElement('div') - rightclickmenu.className = 'rightclickmenu' - // prevent the custom context menu from immediately opening the default context menu as well - rightclickmenu.setAttribute('oncontextmenu', 'return false') + map.Control.selectEdge(adj) - // add the proper options to the menu - let menustring = '' - rightclickmenu.innerHTML = menustring + // position the menu where the click happened + const position = {} + const RIGHTCLICK_WIDTH = 300 + const RIGHTCLICK_HEIGHT = 144 // this does vary somewhat, but we can use static + const SUBMENUS_WIDTH = 256 + const MAX_SUBMENU_HEIGHT = 270 + const windowWidth = $(window).width() + const windowHeight = $(window).height() - // position the menu where the click happened - const position = {} - const RIGHTCLICK_WIDTH = 300 - const RIGHTCLICK_HEIGHT = 144 // this does vary somewhat, but we can use static - const SUBMENUS_WIDTH = 256 - const MAX_SUBMENU_HEIGHT = 270 - const windowWidth = $(window).width() - const windowHeight = $(window).height() + if (windowWidth - e.clientX < SUBMENUS_WIDTH) { + position.right = windowWidth - e.clientX + $(rightclickmenu).addClass('moveMenusToLeft') + } else if (windowWidth - e.clientX < RIGHTCLICK_WIDTH) { + position.right = windowWidth - e.clientX + } else position.left = e.clientX - if (windowWidth - e.clientX < SUBMENUS_WIDTH) { - position.right = windowWidth - e.clientX - $(rightclickmenu).addClass('moveMenusToLeft') - } else if (windowWidth - e.clientX < RIGHTCLICK_WIDTH) { - position.right = windowWidth - e.clientX - } else if (windowWidth - e.clientX < RIGHTCLICK_WIDTH + SUBMENUS_WIDTH) { - position.left = e.clientX - $(rightclickmenu).addClass('moveMenusToLeft') - } else { - position.left = e.clientX - } + if (windowHeight - e.clientY < MAX_SUBMENU_HEIGHT) { + position.bottom = windowHeight - e.clientY + $(rightclickmenu).addClass('moveMenusUp') + } else if (windowHeight - e.clientY < RIGHTCLICK_HEIGHT + MAX_SUBMENU_HEIGHT) { + position.top = e.clientY + $(rightclickmenu).addClass('moveMenusUp') + } else position.top = e.clientY - if (windowHeight - e.clientY < MAX_SUBMENU_HEIGHT) { - position.bottom = windowHeight - e.clientY - $(rightclickmenu).addClass('moveMenusUp') - } else if (windowHeight - e.clientY < RIGHTCLICK_HEIGHT + MAX_SUBMENU_HEIGHT) { - position.top = e.clientY - $(rightclickmenu).addClass('moveMenusUp') - } else { - position.top = e.clientY - } + $(rightclickmenu).css(position) - $(rightclickmenu).css(position) - // add the menu to the page - $('#wrapper').append(rightclickmenu) + // add the menu to the page + $('#wrapper').append(rightclickmenu) - ReactDOM.render( - React.createElement(MetacodeSelect, { - onMetacodeSelect: metacodeId => { - if (map.Selected.Nodes.length > 1) { - // batch update multiple topics - map.Control.updateSelectedMetacodes(metacodeId) - } else { - const topic = map.DataModel.Topics.get(node.id) - topic.save({ - metacode_id: metacodeId - }) - } - $(rightclickmenu).remove() - }, - metacodeSets: ReactApp.metacodeSets - }), - document.getElementById('metacodeOptionsWrapper') - ) + // attach events to clicks on the list items - // attach events to clicks on the list items - - // delete the selected things from the database - if (authorized) { - $('.rc-delete').click(function() { - $('.rightclickmenu').remove() - map.Control.deleteSelected() - }) - } - - // remove the selected things from the map - if (map.Active.Topic || authorized) { - $('.rc-remove').click(function() { - $('.rightclickmenu').remove() - map.Control.removeSelectedEdges() - map.Control.removeSelectedNodes() - }) - } - - // hide selected nodes and synapses until refresh - $('.rc-hide').click(function() { - $('.rightclickmenu').remove() - map.Control.hideSelectedEdges() - map.Control.hideSelectedNodes() - }) - - // when in radial, center on the topic you picked - $('.rc-center').click(function() { - $('.rightclickmenu').remove() - map.Topic.centerOn(node.id) - }) - - // open the entity in a new tab - $('.rc-popout').click(function() { - $('.rightclickmenu').remove() - const win = window.open('/topics/' + node.id, '_blank') - win.focus() - }) - - // change the permission of all the selected nodes and synapses that you were the originator of - $('.rc-permission li').click(function() { - $('.rightclickmenu').remove() - // $(this).text() will be 'commons' 'public' or 'private' - map.Control.updateSelectedPermissions($(this).text()) - }) - - // fetch relatives - let fetchSent = false - $('.rc-siblings').hover(function() { - if (!fetchSent) { - map.JIT.populateRightClickSiblings(node) - fetchSent = true - } - }) - $('.rc-siblings .fetchAll').click(function() { - $('.rightclickmenu').remove() - // data-id is a metacode id - map.Topic.fetchRelatives(node) - }) - }, // selectNodeOnRightClickHandler, - populateRightClickSiblings: function(node) { - // depending on how many topics are selected, do different things - const topic = node.getData('topic') - - // add a loading icon for now - const loader = new CanvasLoader('loadingSiblings') - loader.setColor('#4FC059') // default is '#000000' - loader.setDiameter(15) // default is 40 - loader.setDensity(41) // default is 40 - loader.setRange(0.9) // default is 1.3 - loader.show() // Hidden by default - - const topics = map.DataModel.Topics.map(function(t) { return t.id }) - const topicsString = topics.join() - - const successCallback = function(data) { - $('#loadingSiblings').remove() - - for (var key in data) { - const string = `${DataModel.Metacodes.get(key).get('name')} (${data[key]})` - $('#fetchSiblingList').append(`
  • ${string}
  • `) + // delete the selected things from the database + if (authorized) { + $('.rc-delete').click(function() { + $('.rightclickmenu').remove() + map.Control.deleteSelected() + }) } - $('.rc-siblings .getSiblings').click(function() { + // remove the selected things from the map + if (authorized) { + $('.rc-remove').click(function() { + $('.rightclickmenu').remove() + map.Control.removeSelectedEdges() + map.Control.removeSelectedNodes() + }) + } + + // hide selected nodes and synapses until refresh + $('.rc-hide').click(function() { $('.rightclickmenu').remove() - // data-id is a metacode id - map.Topic.fetchRelatives(node, $(this).attr('data-id')) + map.Control.hideSelectedEdges() + map.Control.hideSelectedNodes() }) - } - $.ajax({ - type: 'GET', - url: '/topics/' + topic.id + '/relative_numbers.json?network=' + topicsString, - success: successCallback, - error: function() {} - }) - }, - selectEdgeOnClickHandler: function(adj, e) { - if (map.Visualize.mGraph.busy) return - - const self = JIT - var synapseText = adj.data.$synapses[0].attributes.desc - // Copy synapse label to clipboard - if (e.button === 1 && e.ctrlKey && synapseText !== '') clipboard.copy(synapseText) - - // catch right click on mac, which is often like ctrl+click - if (navigator.platform.indexOf('Mac') !== -1 && e.ctrlKey) { - self.selectEdgeOnRightClickHandler(adj, e) - return - } - - const check = self.nodeWasDoubleClicked() - if (check) { - self.edgeDoubleClickHandler(adj, e) - return - } else { - // wait a certain length of time, then check again, then run this code - setTimeout(function() { - if (!map.JIT.nodeWasDoubleClicked()) { - const edgeAlreadySelected = map.Selected.Edges.indexOf(adj) !== -1 - - if (!e.shiftKey) { - map.Control.deselectAllNodes() - map.Control.deselectAllEdges() - } - - if (edgeAlreadySelected) { - map.Control.deselectEdge(adj) - } else { - map.Control.selectEdge(adj) - } - - map.Visualize.mGraph.plot() - } - }, map.Mouse.DOUBLE_CLICK_TOLERANCE) - } - }, // selectEdgeOnClickHandler - selectEdgeOnRightClickHandler: function(adj, e) { - // the 'node' variable is a JIT node, the one that was clicked on - // the 'e' variable is the click event - - if (adj.getData('alpha') === 0) return // don't do anything if the edge is filtered - - e.preventDefault() - e.stopPropagation() - - if (map.Visualize.mGraph.busy) return - - map.Control.selectEdge(adj) - - // delete old right click menu - $('.rightclickmenu').remove() - // create new menu for clicked on node - const rightclickmenu = document.createElement('div') - rightclickmenu.className = 'rightclickmenu' - // prevent the custom context menu from immediately opening the default context menu as well - rightclickmenu.setAttribute('oncontextmenu', 'return false') - - // add the proper options to the menu - let menustring = '' - rightclickmenu.innerHTML = menustring - - // position the menu where the click happened - const position = {} - const RIGHTCLICK_WIDTH = 300 - const RIGHTCLICK_HEIGHT = 144 // this does vary somewhat, but we can use static - const SUBMENUS_WIDTH = 256 - const MAX_SUBMENU_HEIGHT = 270 - const windowWidth = $(window).width() - const windowHeight = $(window).height() - - if (windowWidth - e.clientX < SUBMENUS_WIDTH) { - position.right = windowWidth - e.clientX - $(rightclickmenu).addClass('moveMenusToLeft') - } else if (windowWidth - e.clientX < RIGHTCLICK_WIDTH) { - position.right = windowWidth - e.clientX - } else position.left = e.clientX - - if (windowHeight - e.clientY < MAX_SUBMENU_HEIGHT) { - position.bottom = windowHeight - e.clientY - $(rightclickmenu).addClass('moveMenusUp') - } else if (windowHeight - e.clientY < RIGHTCLICK_HEIGHT + MAX_SUBMENU_HEIGHT) { - position.top = e.clientY - $(rightclickmenu).addClass('moveMenusUp') - } else position.top = e.clientY - - $(rightclickmenu).css(position) - - // add the menu to the page - $('#wrapper').append(rightclickmenu) - - // attach events to clicks on the list items - - // delete the selected things from the database - if (authorized) { - $('.rc-delete').click(function() { + // change the permission of all the selected nodes and synapses that you were the originator of + $('.rc-permission li').click(function() { $('.rightclickmenu').remove() - map.Control.deleteSelected() + // $(this).text() will be 'commons' 'public' or 'private' + map.Control.updateSelectedPermissions($(this).text()) }) - } + }, // selectEdgeOnRightClickHandler + SmoothPanning: function() { + const sx = map.Visualize.mGraph.canvas.scaleOffsetX + const sy = map.Visualize.mGraph.canvas.scaleOffsetY + const yVelocity = map.Mouse.changeInY // initial y velocity + const xVelocity = map.Mouse.changeInX // initial x velocity + let easing = 1 // frictional value - // remove the selected things from the map - if (authorized) { - $('.rc-remove').click(function() { - $('.rightclickmenu').remove() - map.Control.removeSelectedEdges() - map.Control.removeSelectedNodes() - }) - } + window.clearInterval(panningInt) + panningInt = setInterval(function() { + myTimer() + }, 1) - // hide selected nodes and synapses until refresh - $('.rc-hide').click(function() { - $('.rightclickmenu').remove() - map.Control.hideSelectedEdges() - map.Control.hideSelectedNodes() - }) + function myTimer() { + map.Visualize.mGraph.canvas.translate(xVelocity * easing * 1 / sx, yVelocity * easing * 1 / sy) + $(document).trigger(JIT.events.pan) + easing = easing * 0.75 - // change the permission of all the selected nodes and synapses that you were the originator of - $('.rc-permission li').click(function() { - $('.rightclickmenu').remove() - // $(this).text() will be 'commons' 'public' or 'private' - map.Control.updateSelectedPermissions($(this).text()) - }) - }, // selectEdgeOnRightClickHandler - SmoothPanning: function() { - const sx = map.Visualize.mGraph.canvas.scaleOffsetX - const sy = map.Visualize.mGraph.canvas.scaleOffsetY - const yVelocity = map.Mouse.changeInY // initial y velocity - const xVelocity = map.Mouse.changeInX // initial x velocity - let easing = 1 // frictional value + if (easing < 0.1) window.clearInterval(panningInt) + } + }, // SmoothPanning + renderMidArrow: function(from, to, dim, swap, canvas, placement, newSynapse) { + const ctx = canvas.getCtx() + // invert edge direction + if (swap) { + const tmp = from + from = to + to = tmp + } + // vect represents a line from tip to tail of the arrow + const vect = new $jit.Complex(to.x - from.x, to.y - from.y) + // scale it + vect.$scale(dim / vect.norm()) + // compute the midpoint of the edge line + const newX = (to.x - from.x) * placement + from.x + const newY = (to.y - from.y) * placement + from.y + const midPoint = new $jit.Complex(newX, newY) - window.clearInterval(panningInt) - panningInt = setInterval(function() { - myTimer() - }, 1) + // move midpoint by half the "length" of the arrow so the arrow is centered on the midpoint + const arrowPoint = new $jit.Complex((vect.x / 0.7) + midPoint.x, (vect.y / 0.7) + midPoint.y) + // compute the tail intersection point with the edge line + const intermediatePoint = new $jit.Complex(arrowPoint.x - vect.x, arrowPoint.y - vect.y) + // vector perpendicular to vect + const normal = new $jit.Complex(-vect.y / 2, vect.x / 2) + const v1 = intermediatePoint.add(normal) + const v2 = intermediatePoint.$add(normal.$scale(-1)) - function myTimer() { - map.Visualize.mGraph.canvas.translate(xVelocity * easing * 1 / sx, yVelocity * easing * 1 / sy) - $(document).trigger(map.JIT.events.pan) - easing = easing * 0.75 + if (newSynapse) { + ctx.strokeStyle = '#4fc059' + ctx.lineWidth = 2 + ctx.globalAlpha = 1 + } + ctx.beginPath() + ctx.moveTo(from.x, from.y) + ctx.lineTo(to.x, to.y) + ctx.stroke() + ctx.beginPath() + ctx.moveTo(v1.x, v1.y) + ctx.lineTo(arrowPoint.x, arrowPoint.y) + ctx.lineTo(v2.x, v2.y) + ctx.stroke() + }, // renderMidArrow + renderEdgeArrows: function(edgeHelper, adj, synapse, canvas) { + const self = toExport - if (easing < 0.1) window.clearInterval(panningInt) - } - }, // SmoothPanning - renderMidArrow: function(from, to, dim, swap, canvas, placement, newSynapse) { - const ctx = canvas.getCtx() - // invert edge direction - if (swap) { - const tmp = from - from = to - to = tmp - } - // vect represents a line from tip to tail of the arrow - const vect = new $jit.Complex(to.x - from.x, to.y - from.y) - // scale it - vect.$scale(dim / vect.norm()) - // compute the midpoint of the edge line - const newX = (to.x - from.x) * placement + from.x - const newY = (to.y - from.y) * placement + from.y - const midPoint = new $jit.Complex(newX, newY) + const directionCat = synapse.get('category') + const direction = synapse.getDirection() - // move midpoint by half the "length" of the arrow so the arrow is centered on the midpoint - const arrowPoint = new $jit.Complex((vect.x / 0.7) + midPoint.x, (vect.y / 0.7) + midPoint.y) - // compute the tail intersection point with the edge line - const intermediatePoint = new $jit.Complex(arrowPoint.x - vect.x, arrowPoint.y - vect.y) - // vector perpendicular to vect - const normal = new $jit.Complex(-vect.y / 2, vect.x / 2) - const v1 = intermediatePoint.add(normal) - const v2 = intermediatePoint.$add(normal.$scale(-1)) + const pos = adj.nodeFrom.pos.getc(true) + const posChild = adj.nodeTo.pos.getc(true) - if (newSynapse) { - ctx.strokeStyle = '#4fc059' - ctx.lineWidth = 2 - ctx.globalAlpha = 1 - } - ctx.beginPath() - ctx.moveTo(from.x, from.y) - ctx.lineTo(to.x, to.y) - ctx.stroke() - ctx.beginPath() - ctx.moveTo(v1.x, v1.y) - ctx.lineTo(arrowPoint.x, arrowPoint.y) - ctx.lineTo(v2.x, v2.y) - ctx.stroke() - }, // renderMidArrow - renderEdgeArrows: function(edgeHelper, adj, synapse, canvas) { - const self = JIT + // plot arrow edge + if (!direction) { + // render nothing for this arrow if the direction couldn't be retrieved + } else if (directionCat === 'none') { + edgeHelper.line.render({ + x: pos.x, + y: pos.y + }, { + x: posChild.x, + y: posChild.y + }, canvas) + } else if (directionCat === 'both') { + self.renderMidArrow({ + x: pos.x, + y: pos.y + }, { + x: posChild.x, + y: posChild.y + }, 13, true, canvas, 0.7) + self.renderMidArrow({ + x: pos.x, + y: pos.y + }, { + x: posChild.x, + y: posChild.y + }, 13, false, canvas, 0.7) + } else if (directionCat === 'from-to') { + const inv = (direction[0] !== adj.nodeFrom.id) + self.renderMidArrow({ + x: pos.x, + y: pos.y + }, { + x: posChild.x, + y: posChild.y + }, 13, inv, canvas, 0.7) + self.renderMidArrow({ + x: pos.x, + y: pos.y + }, { + x: posChild.x, + y: posChild.y + }, 13, inv, canvas, 0.3) + } + }, // renderEdgeArrows + zoomIn: function(event) { + map.Visualize.mGraph.canvas.scale(1.25, 1.25) + $(document).trigger(JIT.events.zoom, [event]) + }, + zoomOut: function(event) { + map.Visualize.mGraph.canvas.scale(0.8, 0.8) + $(document).trigger(JIT.events.zoom, [event]) + }, + centerMap: function(canvas) { + const offsetScale = canvas.scaleOffsetX - const directionCat = synapse.get('category') - const direction = synapse.getDirection() + canvas.scale(1 / offsetScale, 1 / offsetScale) - const pos = adj.nodeFrom.pos.getc(true) - const posChild = adj.nodeTo.pos.getc(true) + const offsetX = canvas.translateOffsetX + const offsetY = canvas.translateOffsetY - // plot arrow edge - if (!direction) { - // render nothing for this arrow if the direction couldn't be retrieved - } else if (directionCat === 'none') { - edgeHelper.line.render({ - x: pos.x, - y: pos.y - }, { - x: posChild.x, - y: posChild.y - }, canvas) - } else if (directionCat === 'both') { - self.renderMidArrow({ - x: pos.x, - y: pos.y - }, { - x: posChild.x, - y: posChild.y - }, 13, true, canvas, 0.7) - self.renderMidArrow({ - x: pos.x, - y: pos.y - }, { - x: posChild.x, - y: posChild.y - }, 13, false, canvas, 0.7) - } else if (directionCat === 'from-to') { - const inv = (direction[0] !== adj.nodeFrom.id) - self.renderMidArrow({ - x: pos.x, - y: pos.y - }, { - x: posChild.x, - y: posChild.y - }, 13, inv, canvas, 0.7) - self.renderMidArrow({ - x: pos.x, - y: pos.y - }, { - x: posChild.x, - y: posChild.y - }, 13, inv, canvas, 0.3) - } - }, // renderEdgeArrows - zoomIn: function(event) { - map.Visualize.mGraph.canvas.scale(1.25, 1.25) - $(document).trigger(map.JIT.events.zoom, [event]) - }, - zoomOut: function(event) { - map.Visualize.mGraph.canvas.scale(0.8, 0.8) - $(document).trigger(map.JIT.events.zoom, [event]) - }, - centerMap: function(canvas) { - const offsetScale = canvas.scaleOffsetX + canvas.translate(-1 * offsetX, -1 * offsetY) + }, + zoomToBox: function(event) { + const sX = map.Mouse.boxStartCoordinates.x + const sY = map.Mouse.boxStartCoordinates.y + const eX = map.Mouse.boxEndCoordinates.x + const eY = map.Mouse.boxEndCoordinates.y - canvas.scale(1 / offsetScale, 1 / offsetScale) + let canvas = map.Visualize.mGraph.canvas + map.JIT.centerMap(canvas) - const offsetX = canvas.translateOffsetX - const offsetY = canvas.translateOffsetY + let height = $(document).height() + let width = $(document).width() - canvas.translate(-1 * offsetX, -1 * offsetY) - }, - zoomToBox: function(event) { - const sX = map.Mouse.boxStartCoordinates.x - const sY = map.Mouse.boxStartCoordinates.y - const eX = map.Mouse.boxEndCoordinates.x - const eY = map.Mouse.boxEndCoordinates.y + let spanX = Math.abs(sX - eX) + let spanY = Math.abs(sY - eY) + let ratioX = width / spanX + let ratioY = height / spanY - let canvas = map.Visualize.mGraph.canvas - map.JIT.centerMap(canvas) + let newRatio = Math.min(ratioX, ratioY) - let height = $(document).height() - let width = $(document).width() + if (canvas.scaleOffsetX * newRatio <= 5 && canvas.scaleOffsetX * newRatio >= 0.2) { + canvas.scale(newRatio, newRatio) + } else if (canvas.scaleOffsetX * newRatio > 5) { + newRatio = 5 / canvas.scaleOffsetX + canvas.scale(newRatio, newRatio) + } else { + newRatio = 0.2 / canvas.scaleOffsetX + canvas.scale(newRatio, newRatio) + } - let spanX = Math.abs(sX - eX) - let spanY = Math.abs(sY - eY) - let ratioX = width / spanX - let ratioY = height / spanY - - let newRatio = Math.min(ratioX, ratioY) - - if (canvas.scaleOffsetX * newRatio <= 5 && canvas.scaleOffsetX * newRatio >= 0.2) { - canvas.scale(newRatio, newRatio) - } else if (canvas.scaleOffsetX * newRatio > 5) { - newRatio = 5 / canvas.scaleOffsetX - canvas.scale(newRatio, newRatio) - } else { - newRatio = 0.2 / canvas.scaleOffsetX - canvas.scale(newRatio, newRatio) - } - - const cogX = (sX + eX) / 2 - const cogY = (sY + eY) / 2 - - canvas.translate(-1 * cogX, -1 * cogY) - $(document).trigger(map.JIT.events.zoom, [event]) - - map.Mouse.boxStartCoordinates = false - map.Mouse.boxEndCoordinates = false - map.Visualize.mGraph.plot() - }, - zoomExtents: function(event, canvas, denySelected) { - map.JIT.centerMap(canvas) - let height = canvas.getSize().height - let width = canvas.getSize().width - let maxX - let maxY - let minX - let minY - let counter = 0 - - let nodes - if (!denySelected && map.Selected.Nodes.length > 0) { - nodes = map.Selected.Nodes - } else { - nodes = _.values(map.Visualize.mGraph.graph.nodes) - } - - if (nodes.length > 1) { - nodes.forEach(function(n) { - let x = n.pos.x - let y = n.pos.y - - if (counter === 0 && n.getData('alpha') === 1) { - maxX = x - minX = x - maxY = y - minY = y - } - - let arrayOfLabelLines = Util.splitLine(n.name, 25).split('\n') - let dim = n.getData('dim') - let ctx = canvas.getCtx() - - let height = 25 * arrayOfLabelLines.length - - let lineWidths = [] - for (let index = 0; index < arrayOfLabelLines.length; ++index) { - lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) - } - let width = Math.max.apply(null, lineWidths) + 8 - - // only adjust these values if the node is not filtered - if (n.getData('alpha') === 1) { - maxX = Math.max(x + width / 2, maxX) - maxY = Math.max(y + n.getData('height') + 5 + height, maxY) - minX = Math.min(x - width / 2, minX) - minY = Math.min(y - dim, minY) - - counter++ - } - }) - - let spanX = maxX - minX - let spanY = maxY - minY - let ratioX = spanX / width - let ratioY = spanY / height - - let cogX = (maxX + minX) / 2 - let cogY = (maxY + minY) / 2 + const cogX = (sX + eX) / 2 + const cogY = (sY + eY) / 2 canvas.translate(-1 * cogX, -1 * cogY) + $(document).trigger(JIT.events.zoom, [event]) - let newRatio = Math.max(ratioX, ratioY) - let scaleMultiplier = 1 / newRatio * 0.9 + map.Mouse.boxStartCoordinates = false + map.Mouse.boxEndCoordinates = false + map.Visualize.mGraph.plot() + }, + zoomExtents: function(event, canvas, denySelected) { + map.JIT.centerMap(canvas) + let height = canvas.getSize().height + let width = canvas.getSize().width + let maxX + let maxY + let minX + let minY + let counter = 0 - if (canvas.scaleOffsetX * scaleMultiplier <= 3 && canvas.scaleOffsetX * scaleMultiplier >= 0.2) { - canvas.scale(scaleMultiplier, scaleMultiplier) - } else if (canvas.scaleOffsetX * scaleMultiplier > 3) { - scaleMultiplier = 3 / canvas.scaleOffsetX - canvas.scale(scaleMultiplier, scaleMultiplier) + let nodes + if (!denySelected && map.Selected.Nodes.length > 0) { + nodes = map.Selected.Nodes } else { - scaleMultiplier = 0.2 / canvas.scaleOffsetX - canvas.scale(scaleMultiplier, scaleMultiplier) + nodes = _.values(map.Visualize.mGraph.graph.nodes) } - $(document).trigger(map.JIT.events.zoom, [event]) - } else if (nodes.length === 1) { - nodes.forEach(function(n) { - const x = n.pos.x - const y = n.pos.y + if (nodes.length > 1) { + nodes.forEach(function(n) { + let x = n.pos.x + let y = n.pos.y - canvas.translate(-1 * x, -1 * y) - $(document).trigger(map.JIT.events.zoom, [event]) - }) + if (counter === 0 && n.getData('alpha') === 1) { + maxX = x + minX = x + maxY = y + minY = y + } + + let arrayOfLabelLines = Util.splitLine(n.name, 25).split('\n') + let dim = n.getData('dim') + let ctx = canvas.getCtx() + + let height = 25 * arrayOfLabelLines.length + + let lineWidths = [] + for (let index = 0; index < arrayOfLabelLines.length; ++index) { + lineWidths.push(ctx.measureText(arrayOfLabelLines[index]).width) + } + let width = Math.max.apply(null, lineWidths) + 8 + + // only adjust these values if the node is not filtered + if (n.getData('alpha') === 1) { + maxX = Math.max(x + width / 2, maxX) + maxY = Math.max(y + n.getData('height') + 5 + height, maxY) + minX = Math.min(x - width / 2, minX) + minY = Math.min(y - dim, minY) + + counter++ + } + }) + + let spanX = maxX - minX + let spanY = maxY - minY + let ratioX = spanX / width + let ratioY = spanY / height + + let cogX = (maxX + minX) / 2 + let cogY = (maxY + minY) / 2 + + canvas.translate(-1 * cogX, -1 * cogY) + + let newRatio = Math.max(ratioX, ratioY) + let scaleMultiplier = 1 / newRatio * 0.9 + + if (canvas.scaleOffsetX * scaleMultiplier <= 3 && canvas.scaleOffsetX * scaleMultiplier >= 0.2) { + canvas.scale(scaleMultiplier, scaleMultiplier) + } else if (canvas.scaleOffsetX * scaleMultiplier > 3) { + scaleMultiplier = 3 / canvas.scaleOffsetX + canvas.scale(scaleMultiplier, scaleMultiplier) + } else { + scaleMultiplier = 0.2 / canvas.scaleOffsetX + canvas.scale(scaleMultiplier, scaleMultiplier) + } + + $(document).trigger(JIT.events.zoom, [event]) + } else if (nodes.length === 1) { + nodes.forEach(function(n) { + const x = n.pos.x + const y = n.pos.y + + canvas.translate(-1 * x, -1 * y) + $(document).trigger(JIT.events.zoom, [event]) + }) + } } } + return toExport } +JIT.events = { + topicDrag: 'Metamaps:JIT:events:topicDrag', + pan: 'Metamaps:JIT:events:pan', + zoom: 'Metamaps:JIT:events:zoom', + animationDone: 'Metamaps:JIT:events:animationDone' } -map.JIT.init = function(serverData) { - map.JIT.topicDescImage = new Image() - map.JIT.topicDescImage.src = serverData['topic_description_signifier.png'] +JIT.init = function(serverData) { + JIT.topicDescImage = new Image() + JIT.topicDescImage.src = serverData['topic_description_signifier.png'] - map.JIT.topicLinkImage = new Image() - map.JIT.topicLinkImage.src = serverData['topic_link_signifier.png'] + JIT.topicLinkImage = new Image() + JIT.topicLinkImage.src = serverData['topic_link_signifier.png'] } export default JIT diff --git a/frontend/src/Metamaps/Map/Realtime/index.js b/frontend/src/Metamaps/Map/Realtime/index.js index 32735939..8331e5db 100644 --- a/frontend/src/Metamaps/Map/Realtime/index.js +++ b/frontend/src/Metamaps/Map/Realtime/index.js @@ -3,8 +3,12 @@ import SimpleWebRTC from 'simplewebrtc' import SocketIoConnection from 'simplewebrtc/socketioconnection' +import MessageCollection from '../../DataModel/MessageCollection' import Util from '../../Util' import Views from '../Views' +import ChatView from '../ChatView' +import { ReactApp } from '../../GlobalUI' +import JIT from '../JIT' import { JUNTO_UPDATED, @@ -25,7 +29,6 @@ import { } from './events' import { - juntoUpdated, invitedToCall, invitedToJoin, callAccepted, @@ -59,49 +62,63 @@ import { } from './sendable' const Realtime = (map) => { -const toExport = { - videoId: 'video-wrapper', - socket: null, - webrtc: null, - readyToCall: false, - mappersOnMap: {}, - disconnected: false, - chatOpen: false, - soundId: null, - broadcastingStatus: false, - inConversation: false, - localVideo: null, - 'junto_spinner_darkgrey.gif': '', - init: function(serverData) { - var self = toExport - - self.addJuntoListeners() - - self.socket = new SocketIoConnection({ - url: serverData['REALTIME_SERVER'], - socketio: { - // don't poll forever if in development - reconnectionAttempts: serverData.RAILS_ENV === 'development' ? 5 : Infinity - } - }) - self['junto_spinner_darkgrey.gif'] = serverData['junto_spinner_darkgrey.gif'] - - self.socket.on('connect', function() { - console.log('connected') - if (map.Active.Map && map.Active.Mapper && map.Active.Map.authorizeToEdit(map.Active.Mapper)) { - self.checkForCall() - self.joinMap() - } - subscribeToEvents(self, self.socket) - self.disconnected = false - }) - self.socket.on('disconnect', function() { - self.disconnected = true - }) - - if (map.Active.Mapper) { + const toExport = { + videoId: 'video-wrapper', + webrtc: null, + readyToCall: false, + mappersOnMap: {}, + chatOpen: false, + soundId: null, + broadcastingStatus: false, + inConversation: false, + localVideo: null, + onSocketConnect: () => {}, + startActiveMap: function() { + var self = toExport + if (map.Active.Map.authorizeToEdit(map.Active.Mapper)) { + self.addJuntoListeners() + if (Realtime.socket && !Realtime.socket.disconnected) self.onSocketConnect = self._onSocketConnect + else self._onSocketConnect() + } + if (map.Active.Mapper) { + self.setupChat() // chat can happen on public maps too + map.Cable.subscribeToMap(map.Active.Map.id) // people with viewing rights can still see live updates + } + }, + endActiveMap: function() { + var self = toExport + $(document).off('.map') + // leave the appropriate rooms to leave + if (self.inConversation) self.leaveCall() + self.leaveMap() + $('.collabCompass').remove() + if (self.room) self.room.leave() + if (!Realtime.socket.disconnected) self.unsubscribeFromEvents() + map.Cable.unsubscribeFromMap() + }, + _onSocketConnect: function() { + console.log('testing') + const self = toExport + const sendables = [ + ['joinMap', joinMap], + ['leaveMap', leaveMap], + ['checkForCall', checkForCall], + ['acceptCall', acceptCall], + ['denyCall', denyCall], + ['denyInvite', denyInvite], + ['inviteToJoin', inviteToJoin], + ['inviteACall', inviteACall], + ['joinCall', joinCall], + ['leaveCall', leaveCall], + ['sendMapperInfo', sendMapperInfo], + ['sendCoords', sendCoords], + ['dragTopic', dragTopic] + ] + sendables.forEach(sendable => { + toExport[sendable[0]] = sendable[1](Realtime.socket, toExport, map) + }) self.webrtc = new SimpleWebRTC({ - connection: self.socket, + connection: Realtime.socket, localVideoEl: self.videoId, remoteVideosEl: '', debug: true, @@ -127,7 +144,6 @@ const toExport = { console.log('remote ice failure', peer) // remote ice failure }) - var $video = $('').attr('id', self.videoId) self.localVideo = { $video: $video, @@ -136,291 +152,291 @@ const toExport = { avatar: map.Active.Mapper ? map.Active.Mapper.get('image') : '' }) } - self.room = new Views.Room({ webrtc: self.webrtc, - socket: self.socket, - room: 'global', + room: 'map-' + map.Active.Map.id, $video: self.localVideo.$video, myVideoView: self.localVideo.view, config: { DOUBLE_CLICK_TOLERANCE: 200 } }) self.room.videoAdded(self.handleVideoAdded) - } // if map.Active.Mapper - }, - addJuntoListeners: function() { - var self = toExport - - $(document).on(ChatView.events.openTray, function() { - $('.main').addClass('compressed') - self.chatOpen = true - self.positionPeerIcons() - }) - $(document).on(ChatView.events.closeTray, function() { - $('.main').removeClass('compressed') - self.chatOpen = false - self.positionPeerIcons() - }) - $(document).on(ChatView.events.videosOn, function() { - $('#wrapper').removeClass('hideVideos') - }) - $(document).on(ChatView.events.videosOff, function() { - $('#wrapper').addClass('hideVideos') - }) - $(document).on(ChatView.events.cursorsOn, function() { - $('#wrapper').removeClass('hideCursors') - }) - $(document).on(ChatView.events.cursorsOff, function() { - $('#wrapper').addClass('hideCursors') - }) - }, - startActiveMap: function() { - var self = toExport - if (map.Active.Map && map.Active.Mapper) { - if (map.Active.Map.authorizeToEdit(map.Active.Mapper)) { - self.turnOn() - self.checkForCall() - self.joinMap() - } - self.setupChat() // chat can happen on public maps too - map.Cable.subscribeToMap(map.Active.Map.id) // people with edit rights can still see live updates - } - }, - endActiveMap: function() { - var self = toExport - $(document).off('.map') - // leave the appropriate rooms to leave - if (self.inConversation) self.leaveCall() - self.leaveMap() - $('.collabCompass').remove() - if (self.room) self.room.leave() - map.Cable.unsubscribeFromMap() - }, - turnOn: function(notify) { - var self = toExport - $('.collabCompass').show() - self.room.room = 'map-' + map.Active.Map.id - self.activeMapper = { - id: map.Active.Mapper.id, - name: map.Active.Mapper.get('name'), - username: map.Active.Mapper.get('name'), - image: map.Active.Mapper.get('image'), - color: Util.getPastelColor(), - self: true - } - self.localVideo.view.$container.find('.video-cutoff').css({ - border: '4px solid ' + self.activeMapper.color - }) - self.setupLocalEvents() - }, - setupChat: function() { - const self = toExport - map.ChatView.setNewMap() - map.ChatView.addParticipant(self.activeMapper) - map.ChatView.addMessages(new DataModel.MessageCollection(map.DataModel.Messages), true) - }, - setupLocalEvents: function() { - var self = toExport - // local event listeners that trigger events - $(document).on(map.JIT.events.zoom + '.map', self.positionPeerIcons) - $(document).on(map.JIT.events.pan + '.map', self.positionPeerIcons) - $(document).on('mousemove.map', function(event) { - var pixels = { - x: event.pageX, - y: event.pageY - } - var coords = Util.pixelsToCoords(map.Visualize.mGraph, pixels) - self.sendCoords(coords) - }) - $(document).on(map.JIT.events.topicDrag + '.map', function(event, positions) { - self.dragTopic(positions) - }) - }, - countOthersInConversation: function() { - var self = toExport - var count = 0 - for (var key in self.mappersOnMap) { - if (self.mappersOnMap[key].inConversation) count++ - } - return count - }, - handleVideoAdded: function(v, id) { - var self = toExport - 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 = toExport - 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 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 = toExport.localVideo.view - if (!myVideo.manuallyPositioned) { - myVideo.$container.css({ - top: yFormula() + 'px', - left: xFormula() + 'px' + self.subscribeToEvents() + self.turnOn() + self.checkForCall() + self.joinMap() + }, + addJuntoListeners: function() { + var self = toExport + $(document).on(ChatView.events.openTray, function() { + $('.main').addClass('compressed') + self.chatOpen = true + self.positionPeerIcons() }) - } - videoIds.forEach(function(id) { - var video = self.room.videos[id] - if (!video.manuallyPositioned) { - video.$container.css({ + $(document).on(ChatView.events.closeTray, function() { + $('.main').removeClass('compressed') + self.chatOpen = false + self.positionPeerIcons() + }) + $(document).on(ChatView.events.videosOn, function() { + $('#wrapper').removeClass('hideVideos') + }) + $(document).on(ChatView.events.videosOff, function() { + $('#wrapper').addClass('hideVideos') + }) + $(document).on(ChatView.events.cursorsOn, function() { + $('#wrapper').removeClass('hideCursors') + }) + $(document).on(ChatView.events.cursorsOff, function() { + $('#wrapper').addClass('hideCursors') + }) + }, + turnOn: function(notify) { + var self = toExport + $('.collabCompass').show() + self.activeMapper = { + id: map.Active.Mapper.id, + name: map.Active.Mapper.get('name'), + username: map.Active.Mapper.get('name'), + image: map.Active.Mapper.get('image'), + color: Util.getPastelColor(), + self: true + } + self.localVideo.view.$container.find('.video-cutoff').css({ + border: '4px solid ' + self.activeMapper.color + }) + self.setupLocalEvents() + }, + setupChat: function() { + const self = toExport + map.ChatView.setNewMap() + map.ChatView.addParticipant(self.activeMapper) + map.ChatView.addMessages(new MessageCollection(map.DataModel.Messages), true) + }, + setupLocalEvents: function() { + var self = toExport + // local event listeners that trigger events + $(document).on(JIT.events.zoom + '.map', self.positionPeerIcons) + $(document).on(JIT.events.pan + '.map', self.positionPeerIcons) + $(document).on('mousemove.map', function(event) { + var pixels = { + x: event.pageX, + y: event.pageY + } + var coords = Util.pixelsToCoords(map.Visualize.mGraph, pixels) + self.sendCoords(coords) + }) + $(document).on(JIT.events.topicDrag + '.map', function(event, positions) { + self.dragTopic(positions) + }) + }, + subscribeToEvents: function() { + // todo scope these event listeners to map + const socket = Realtime.socket + socket.on(INVITED_TO_CALL, invitedToCall(toExport, map)) + socket.on(INVITED_TO_JOIN, invitedToJoin(toExport, map)) + socket.on(CALL_ACCEPTED, callAccepted(toExport, map)) + socket.on(CALL_DENIED, callDenied(toExport, map)) + socket.on(INVITE_DENIED, inviteDenied(toExport, map)) + socket.on(CALL_IN_PROGRESS, callInProgress(toExport, map)) + socket.on(CALL_STARTED, callStarted(toExport, map)) + socket.on(MAPPER_LIST_UPDATED, mapperListUpdated(toExport, map)) + socket.on(MAPPER_JOINED_CALL, mapperJoinedCall(toExport, map)) + socket.on(MAPPER_LEFT_CALL, mapperLeftCall(toExport, map)) + socket.on(PEER_COORDS_UPDATED, peerCoordsUpdated(toExport, map)) + socket.on(NEW_MAPPER, newMapper(toExport, map)) + socket.on(LOST_MAPPER, lostMapper(toExport, map)) + socket.on(TOPIC_DRAGGED, topicDragged(toExport, map)) + }, + unsubscribeFromEvents: function() { + // todo scope these event listeners to map + const socket = Realtime.socket + socket.off(INVITED_TO_JOIN) + socket.off(CALL_ACCEPTED) + socket.off(INVITED_TO_CALL) + socket.off(CALL_DENIED) + socket.off(INVITE_DENIED) + socket.off(CALL_IN_PROGRESS) + socket.off(CALL_STARTED) + socket.off(MAPPER_LIST_UPDATED) + socket.off(MAPPER_JOINED_CALL) + socket.off(MAPPER_LEFT_CALL) + socket.off(PEER_COORDS_UPDATED) + socket.off(NEW_MAPPER) + socket.off(LOST_MAPPER) + socket.off(TOPIC_DRAGGED) + }, + countOthersInConversation: function() { + var self = toExport + var count = 0 + for (var key in self.mappersOnMap) { + if (self.mappersOnMap[key].inConversation) count++ + } + return count + }, + handleVideoAdded: function(v, id) { + var self = toExport + 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 = toExport + 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 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 = toExport.localVideo.view + if (!myVideo.manuallyPositioned) { + myVideo.$container.css({ top: yFormula() + 'px', left: xFormula() + 'px' }) } - }) - }, - callEnded: function() { - var self = toExport - - map.ChatView.conversationEnded() - self.room.leaveVideoOnly() - self.inConversation = false - self.localVideo.view.$container.hide().css({ - top: '72px', - left: '30px' - }) - self.localVideo.view.audioOn() - self.localVideo.view.videoOn() - }, - createCompass: function(name, id, image, color) { - var str = '

    ' + name + '

    ' - str += '
    ' - $('#compass' + id).remove() - $('
    ', { - id: 'compass' + id, - class: 'collabCompass' - }).html(str).appendTo('#wrapper') - $('#compass' + id + ' img').css({ - 'border': '2px solid ' + color - }) - $('#compass' + id + ' p').css({ - 'background-color': color - }) - }, - positionPeerIcons: function() { - var self = toExport - for (var key in self.mappersOnMap) { - self.positionPeerIcon(key) - } - }, - positionPeerIcon: function(id) { - var self = toExport - var mapper = self.mappersOnMap[id] - - var origPixels = Util.coordsToPixels(map.Visualize.mGraph, 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 angle = Math.atan2(dy, dx) - - $('#compassArrow' + id).show().css({ - transform: 'rotate(' + angle + 'rad)', - '-webkit-transform': 'rotate(' + angle + 'rad)' + videoIds.forEach(function(id) { + var video = self.room.videos[id] + if (!video.manuallyPositioned) { + video.$container.css({ + top: yFormula() + 'px', + left: xFormula() + 'px' + }) + } }) + }, + callEnded: function() { + var self = toExport - if (dx > 0) { - $('#compass' + id).addClass('labelLeft') + map.ChatView.conversationEnded() + self.room.leaveVideoOnly() + self.inConversation = false + self.localVideo.view.$container.hide().css({ + top: '72px', + left: '30px' + }) + self.localVideo.view.audioOn() + self.localVideo.view.videoOn() + }, + createCompass: function(name, id, image, color) { + var str = '

    ' + name + '

    ' + str += '
    ' + $('#compass' + id).remove() + $('
    ', { + id: 'compass' + id, + class: 'collabCompass' + }).html(str).appendTo('#wrapper') + $('#compass' + id + ' img').css({ + 'border': '2px solid ' + color + }) + $('#compass' + id + ' p').css({ + 'background-color': color + }) + }, + positionPeerIcons: function() { + var self = toExport + for (var key in self.mappersOnMap) { + self.positionPeerIcon(key) } - } else { - $('#compassArrow' + id).hide() - $('#compass' + id).removeClass('labelLeft') + }, + positionPeerIcon: function(id) { + var self = toExport + var mapper = self.mappersOnMap[id] + + var origPixels = Util.coordsToPixels(map.Visualize.mGraph, 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 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 = toExport + + 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} } - }, - limitPixelsToScreen: function(pixels) { - var self = toExport - - 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} } + return toExport } -const sendables = [ - ['joinMap', joinMap], - ['leaveMap', leaveMap], - ['checkForCall', checkForCall], - ['acceptCall', acceptCall], - ['denyCall', denyCall], - ['denyInvite', denyInvite], - ['inviteToJoin', inviteToJoin], - ['inviteACall', inviteACall], - ['joinCall', joinCall], - ['leaveCall', leaveCall], - ['sendMapperInfo', sendMapperInfo], - ['sendCoords', sendCoords], - ['dragTopic', dragTopic] -] -sendables.forEach(sendable => { - toExport[sendable[0]] = sendable[1](toExport, map) -}) - -const subscribeToEvents = (toExport, socket) => { - socket.on(JUNTO_UPDATED, juntoUpdated(toExport, map)) - socket.on(INVITED_TO_CALL, invitedToCall(toExport, map)) - socket.on(INVITED_TO_JOIN, invitedToJoin(toExport, map)) - socket.on(CALL_ACCEPTED, callAccepted(toExport, map)) - socket.on(CALL_DENIED, callDenied(toExport, map)) - socket.on(INVITE_DENIED, inviteDenied(toExport, map)) - socket.on(CALL_IN_PROGRESS, callInProgress(toExport, map)) - socket.on(CALL_STARTED, callStarted(toExport, map)) - socket.on(MAPPER_LIST_UPDATED, mapperListUpdated(toExport, map)) - socket.on(MAPPER_JOINED_CALL, mapperJoinedCall(toExport, map)) - socket.on(MAPPER_LEFT_CALL, mapperLeftCall(toExport, map)) - socket.on(PEER_COORDS_UPDATED, peerCoordsUpdated(toExport, map)) - socket.on(NEW_MAPPER, newMapper(toExport, map)) - socket.on(LOST_MAPPER, lostMapper(toExport, map)) - socket.on(TOPIC_DRAGGED, topicDragged(toExport, map)) -} -return toExport +Realtime.init = function(serverData) { + var self = Realtime + self.socket = new SocketIoConnection({ + url: serverData['REALTIME_SERVER'], + socketio: { + // don't poll forever if in development + reconnectionAttempts: serverData.RAILS_ENV === 'development' ? 5 : Infinity + } + }) + self['junto_spinner_darkgrey.gif'] = serverData['junto_spinner_darkgrey.gif'] + self.socket.on('connect', function() { + console.log('connected') + self.socket.on(JUNTO_UPDATED, (state) => { + ReactApp.juntoState = state + ReactApp.render() + }) + self.disconnected = false + ReactApp.openMap && ReactApp.openMap.Realtime.onSocketConnect() + }) + self.socket.on('disconnect', function() { + self.disconnected = true + }) } diff --git a/frontend/src/Metamaps/Map/Realtime/receivable.js b/frontend/src/Metamaps/Map/Realtime/receivable.js index 7fbe4075..b68f4c91 100644 --- a/frontend/src/Metamaps/Map/Realtime/receivable.js +++ b/frontend/src/Metamaps/Map/Realtime/receivable.js @@ -4,16 +4,9 @@ everthing in this file happens as a result of websocket events */ -import { JUNTO_UPDATED } from './events' - -import GlobalUI, { ReactApp } from '../../GlobalUI' +import GlobalUI from '../../GlobalUI' import Util from '../../Util' -export const juntoUpdated = (self, map) => state => { - ReactApp.juntoState = state - $(document).trigger(JUNTO_UPDATED) -} - /* All the following events are received through the nodejs realtime server and are done this way because they are transient data, not persisted to the server */ export const topicDragged = (self, map) => positions => { diff --git a/frontend/src/Metamaps/Map/Realtime/sendable.js b/frontend/src/Metamaps/Map/Realtime/sendable.js index bafd353c..f8322bed 100644 --- a/frontend/src/Metamaps/Map/Realtime/sendable.js +++ b/frontend/src/Metamaps/Map/Realtime/sendable.js @@ -18,8 +18,8 @@ import { DRAG_TOPIC } from './events' -export const joinMap = (self, map) => () => { - self.socket.emit(JOIN_MAP, { +export const joinMap = (socket, self, map) => () => { + socket.emit(JOIN_MAP, { userid: map.Active.Mapper.id, username: map.Active.Mapper.get('name'), avatar: map.Active.Mapper.get('image'), @@ -28,15 +28,15 @@ export const joinMap = (self, map) => () => { }) } -export const leaveMap = (self, map) => () => { - self.socket.emit(LEAVE_MAP) +export const leaveMap = (socket, self, map) => () => { + socket.emit(LEAVE_MAP) } -export const checkForCall = (self, map) => () => { - self.socket.emit(CHECK_FOR_CALL, { room: self.room.room, mapid: map.Active.Map.id }) +export const checkForCall = (socket, self, map) => () => { + socket.emit(CHECK_FOR_CALL, { room: self.room.room, mapid: map.Active.Map.id }) } -export const sendMapperInfo = (self, map) => userid => { +export const sendMapperInfo = (socket, self, map) => userid => { // send this new mapper back your details, and the awareness that you've loaded the map var update = { userToNotify: userid, @@ -46,10 +46,10 @@ export const sendMapperInfo = (self, map) => userid => { userinconversation: self.inConversation, mapid: map.Active.Map.id } - self.socket.emit(SEND_MAPPER_INFO, update) + socket.emit(SEND_MAPPER_INFO, update) } -export const joinCall = (self, map) => () => { +export const joinCall = (socket, self, map) => () => { self.webrtc.off('readyToCall') self.webrtc.once('readyToCall', function() { self.videoInitialized = true @@ -64,7 +64,7 @@ export const joinCall = (self, map) => () => { map.ChatView.conversationInProgress(true) }) self.inConversation = true - self.socket.emit(JOIN_CALL, { + socket.emit(JOIN_CALL, { mapid: map.Active.Map.id, id: map.Active.Mapper.id }) @@ -73,8 +73,8 @@ export const joinCall = (self, map) => () => { map.ChatView.mapperJoinedCall(map.Active.Mapper.id) } -export const leaveCall = (self, map) => () => { - self.socket.emit(LEAVE_CALL, { +export const leaveCall = (socket, self, map) => () => { + socket.emit(LEAVE_CALL, { mapid: map.Active.Map.id, id: map.Active.Mapper.id }) @@ -92,9 +92,9 @@ export const leaveCall = (self, map) => () => { } } -export const acceptCall = (self, map) => userid => { +export const acceptCall = (socket, self, map) => userid => { map.ChatView.sound.stop(self.soundId) - self.socket.emit(ACCEPT_CALL, { + socket.emit(ACCEPT_CALL, { mapid: map.Active.Map.id, invited: map.Active.Mapper.id, inviter: userid @@ -104,9 +104,9 @@ export const acceptCall = (self, map) => userid => { GlobalUI.clearNotify() } -export const denyCall = (self, map) => userid => { +export const denyCall = (socket, self, map) => userid => { map.ChatView.sound.stop(self.soundId) - self.socket.emit(DENY_CALL, { + socket.emit(DENY_CALL, { mapid: map.Active.Map.id, invited: map.Active.Mapper.id, inviter: userid @@ -114,9 +114,9 @@ export const denyCall = (self, map) => userid => { GlobalUI.clearNotify() } -export const denyInvite = (self, map) => userid => { +export const denyInvite = (socket, self, map) => userid => { map.ChatView.sound.stop(self.soundId) - self.socket.emit(DENY_INVITE, { + socket.emit(DENY_INVITE, { mapid: map.Active.Map.id, invited: map.Active.Mapper.id, inviter: userid @@ -124,8 +124,8 @@ export const denyInvite = (self, map) => userid => { GlobalUI.clearNotify() } -export const inviteACall = (self, map) => userid => { - self.socket.emit(INVITE_A_CALL, { +export const inviteACall = (socket, self, map) => userid => { + socket.emit(INVITE_A_CALL, { mapid: map.Active.Map.id, inviter: map.Active.Mapper.id, invited: userid @@ -134,8 +134,8 @@ export const inviteACall = (self, map) => userid => { GlobalUI.clearNotify() } -export const inviteToJoin = (self, map) => userid => { - self.socket.emit(INVITE_TO_JOIN, { +export const inviteToJoin = (socket, self, map) => userid => { + socket.emit(INVITE_TO_JOIN, { mapid: map.Active.Map.id, inviter: map.Active.Mapper.id, invited: userid @@ -143,22 +143,22 @@ export const inviteToJoin = (self, map) => userid => { map.ChatView.invitationPending(userid) } -export const sendCoords = (self, map) => coords => { - var map = map.Active.Map +export const sendCoords = (socket, self, map) => coords => { + var m = map.Active.Map var mapper = map.Active.Mapper - if (map && map.authorizeToEdit(mapper)) { + if (m && m.authorizeToEdit(mapper)) { var update = { usercoords: coords, userid: map.Active.Mapper.id, - mapid: map.Active.Map.id + mapid: m.id } - self.socket.emit(SEND_COORDS, update) + socket.emit(SEND_COORDS, update) } } -export const dragTopic = (self, map) => positions => { +export const dragTopic = (socket, self, map) => positions => { if (map.Active.Map) { positions.mapid = map.Active.Map.id - self.socket.emit(DRAG_TOPIC, positions) + socket.emit(DRAG_TOPIC, positions) } } diff --git a/frontend/src/Metamaps/Map/Views/Room.js b/frontend/src/Metamaps/Map/Views/Room.js index 03548c7f..5d2008d1 100644 --- a/frontend/src/Metamaps/Map/Views/Room.js +++ b/frontend/src/Metamaps/Map/Views/Room.js @@ -7,7 +7,6 @@ import VideoView from './VideoView' const Room = function(opts = {}) { this.isActiveRoom = false - this.socket = opts.socket this.webrtc = opts.webrtc this.room = opts.room this.config = opts.config diff --git a/frontend/src/Metamaps/Map/Views/index.js b/frontend/src/Metamaps/Map/Views/index.js index a76b6527..5bf34cf8 100644 --- a/frontend/src/Metamaps/Map/Views/index.js +++ b/frontend/src/Metamaps/Map/Views/index.js @@ -2,13 +2,8 @@ import VideoView from './VideoView' import Room from './Room' -import { JUNTO_UPDATED } from '../Realtime/events' const Views = { - init: (serverData) => { - $(document).on(JUNTO_UPDATED, () => ExploreMaps.render()) - //map.ChatView.init([serverData['sounds/MM_sounds.mp3'], serverData['sounds/MM_sounds.ogg']]) - }, VideoView, Room } diff --git a/frontend/src/Metamaps/Map/index.js b/frontend/src/Metamaps/Map/index.js index 41552dd7..87045073 100644 --- a/frontend/src/Metamaps/Map/index.js +++ b/frontend/src/Metamaps/Map/index.js @@ -91,8 +91,8 @@ const mapControl = { newMap.Active.Mapper = ReactApp.currentUser newMap.DataModel.Mappers = new MapperCollection(data.mappers) newMap.DataModel.Collaborators = new MapperCollection(data.collaborators) - newMap.DataModel.Topics = new TopicCollection(data.topics) - newMap.DataModel.Synapses = new SynapseCollection(data.synapses) + //newMap.DataModel.Topics = new TopicCollection(data.topics) + //newMap.DataModel.Synapses = new SynapseCollection(data.synapses) newMap.DataModel.Mappings = new MappingCollection(data.mappings) newMap.DataModel.Messages = data.messages newMap.DataModel.Stars = data.stars @@ -127,15 +127,15 @@ const mapControl = { end: function(map) { $('.main').removeClass('compressed') $('.rightclickmenu').remove() - map.AutoLayout.resetSpiral() - map.TopicCard.hideCard() - map.map.SynapseCard.hideCard() + //map.AutoLayout.resetSpiral() + //map.TopicCard.hideCard() + map.SynapseCard.hideCard() map.Create.newTopic.hide(true) // true means force (and override pinned) map.Create.newSynapse.hide() map.InfoBox.close() + //map.Map.requests = [] + //map.Map.hasLearnedTopicCreation = true map.Realtime.endActiveMap() - map.Map.requests = [] - map.Map.hasLearnedTopicCreation = true } } export { mapControl } @@ -442,6 +442,9 @@ const Map = (map) => { Map.events = { editedByActiveMapper: 'Metamaps:Map:events:editedByActiveMapper' } +Map.init = (serverData) => { + ChatView.init([serverData['sounds/MM_sounds.mp3'], serverData['sounds/MM_sounds.ogg']]) +} export { CheatSheet } export default Map diff --git a/frontend/src/Metamaps/index.js b/frontend/src/Metamaps/index.js index 8769071d..7cd8f768 100644 --- a/frontend/src/Metamaps/index.js +++ b/frontend/src/Metamaps/index.js @@ -58,7 +58,6 @@ Metamaps.Synapse = Synapse Metamaps.SynapseCard = SynapseCard Metamaps.Topic = Topic Metamaps.Util = Util -Metamaps.Views = Views Metamaps.Visualize = Visualize Metamaps.GlobalUI = GlobalUI @@ -76,7 +75,6 @@ document.addEventListener('DOMContentLoaded', function() { Metamaps[prop].hasOwnProperty('init') && typeof (Metamaps[prop].init) === 'function' ) { - console.log(prop) Metamaps[prop].init(Metamaps.ServerData) } } diff --git a/frontend/src/components/MapView/MapChat/index.js b/frontend/src/components/MapView/MapChat/index.js index 64b7b10e..a78d6eb8 100644 --- a/frontend/src/components/MapView/MapChat/index.js +++ b/frontend/src/components/MapView/MapChat/index.js @@ -59,7 +59,7 @@ class MapChat extends Component { scroll = () => { // hack: figure out how to do this right - this.messagesDiv.scrollTop = this.messagesDiv.scrollHeight + 100 + if (this.messagesDiv) this.messagesDiv.scrollTop = this.messagesDiv.scrollHeight + 100 } toggleDrawer = () => { diff --git a/realtime/global.js b/realtime/global.js index f7d372e6..aaba0753 100644 --- a/realtime/global.js +++ b/realtime/global.js @@ -7,7 +7,7 @@ const { LEAVE_CALL, JOIN_MAP, LEAVE_MAP -} = require('../frontend/src/Metamaps/Realtime/events') +} = require('../frontend/src/Metamaps/Map/Realtime/events') module.exports = function(io, store) { store.subscribe(() => { diff --git a/realtime/junto.js b/realtime/junto.js index a361c1e3..c05649aa 100644 --- a/realtime/junto.js +++ b/realtime/junto.js @@ -17,7 +17,7 @@ const { INVITE_A_CALL, JOIN_CALL, LEAVE_CALL -} = require('../frontend/src/Metamaps/Realtime/events') +} = require('../frontend/src/Metamaps/Map/Realtime/events') const { mapRoom, userMapRoom } = require('./rooms') diff --git a/realtime/map.js b/realtime/map.js index 9d8a1582..c0ded25c 100644 --- a/realtime/map.js +++ b/realtime/map.js @@ -10,7 +10,7 @@ const { SEND_COORDS, SEND_MAPPER_INFO, DRAG_TOPIC -} = require('../frontend/src/Metamaps/Realtime/events') +} = require('../frontend/src/Metamaps/Map/Realtime/events') const { mapRoom, userMapRoom } = require('./rooms') diff --git a/realtime/reducer.js b/realtime/reducer.js index 9b50d3a0..423f8dd5 100644 --- a/realtime/reducer.js +++ b/realtime/reducer.js @@ -4,7 +4,7 @@ const { LEAVE_MAP, JOIN_CALL, LEAVE_CALL -} = require('../frontend/src/Metamaps/Realtime/events') +} = require('../frontend/src/Metamaps/Map/Realtime/events') const NOT_IN_CONVERSATION = 0 const IN_CONVERSATION = 1