Compare commits

...

38 commits

Author SHA1 Message Date
Connor Turland f49a674566 tweaks for developing on this branch 2017-02-06 03:41:28 +00:00
Connor Turland 7951f08e45 Merge branch 'develop' into feature/convo.algo 2017-02-06 03:36:26 +00:00
Connor Turland 9c65d2df09 for special conversation url 2017-02-05 07:05:25 +00:00
Connor Turland a84815aaed tiny merge with develop 2017-02-05 06:39:55 +00:00
Connor Turland 9b0ead24a3 more merges with develop 2017-02-05 06:37:37 +00:00
Connor Turland 3a4fa90c49 Merge branch 'develop' into feature/convo.algo 2017-02-05 06:28:49 +00:00
Connor Turland 0a52de714e make it match develop 2017-02-05 05:41:29 +00:00
Connor Turland 597efb36e1 add this back in 2017-02-05 05:31:37 +00:00
Connor Turland 72fd2717b6 Merge branch 'develop' into feature/convo.algo 2017-02-05 05:26:58 +00:00
Connor Turland 500a74bd5f don't include the controller yet 2017-02-05 04:15:51 +00:00
Connor Turland 2423608fd3 actually optimize the island layout 2017-02-05 04:08:36 +00:00
Connor Turland 0929380e91 divide up the islands to the north and south 2017-02-05 03:34:59 +00:00
Connor Turland 6e347dc33a fixup synapse creation 2017-02-05 03:26:18 +00:00
Connor Turland 96f66a2f8c fix up topic creator 2017-02-04 04:27:08 +00:00
Connor Turland 74dd20f02e got topic and synapse creation working 2017-02-04 04:13:59 +00:00
Connor Turland 04036882ab do the bounds and translate 2017-02-03 20:58:49 +00:00
Connor Turland fd54eb718a its workking 2017-02-03 19:58:15 +00:00
Connor Turland ba230e1eed only find parents for parents and children for children 2017-02-01 21:30:48 +00:00
Connor Turland 274b86532a its vorkingg.... 2017-02-01 18:08:02 +00:00
Connor Turland 6ed9796e05 no more matter 2017-02-01 17:49:16 +00:00
Connor Turland 08d2cbb00d running algo each time an edge is added 2017-02-01 17:47:45 +00:00
Connor Turland fc2604376a add the gitignore 2017-01-31 03:23:01 +00:00
Connor Turland e7a52dc14e bugs 2017-01-12 14:11:01 -05:00
Connor Turland 149b7ecbd6 include matter-js in package json 2017-01-12 12:37:16 -05:00
Connor Turland b2b5090b28 still make it work for logged out users 2017-01-12 12:31:08 -05:00
Connor Turland 38c01c4e8f fix the carousel 2017-01-12 12:19:54 -05:00
Connor Turland b13ac98c9f chat iss working 2017-01-12 04:55:57 -05:00
Connor Turland 4a17d00123 stuff with metacodes working well again 2017-01-12 04:00:52 -05:00
Connor Turland 9c13f5a281 metacode selector working nicely 2017-01-12 00:04:49 -05:00
Connor Turland 616a489ae4 Merge branch 'feature/better.select' into feature/particles 2017-01-11 23:18:26 -05:00
Connor Turland 816815d1b5 awesome 2017-01-11 23:17:57 -05:00
Connor Turland 3ff102b228 initial particle test 2017-01-05 00:05:18 -05:00
Connor Turland bf9b25da9f merge develop 2017-01-04 10:50:59 -05:00
Connor Turland 5ba7ba9355 don't commit swp 2016-09-20 12:28:41 -04:00
Connor Turland ce7c88c78c add more space between nodes using auto-layout 2016-09-20 12:27:46 -04:00
Connor Turland 74630c2631 messed up comment 2016-09-20 12:04:42 -04:00
Connor Turland 966dd79187 Merge branch 'develop' into feature/better.select 2016-09-20 11:55:25 -04:00
Connor Turland 5d04d16590 create MetacodeSelect component 2016-09-18 15:22:51 +00:00
23 changed files with 812 additions and 580 deletions

View file

@ -22,7 +22,7 @@ class MapPolicy < ApplicationPolicy
end
def conversation?
show? && %w(connorturland@gmail.com devin@callysto.com chessscholar@gmail.com solaureum@gmail.com ishanshapiro@gmail.com).include?(user.email)
show? && %w(admin@admin.com user@user.com connorturland@gmail.com devin@callysto.com chessscholar@gmail.com solaureum@gmail.com ishanshapiro@gmail.com).include?(user.email)
end
def create?

View file

@ -0,0 +1,58 @@
// if we've placed a node into an island, we need to NOT place it in any other islands
// Every node should only appear in one island
// the top level array represents islands
// every island has some sort of 'focal' node
/*
var example = [
// the island that contains the focal node
{
id: 21,
parents: [
{
id: 25,
parents: []
},
{
id: 25,
parents: []
}
],
children: [{
id: 26,
children: []
}]
},
// all other islands should not contain children on the top level node
{
id: 21,
// parents may contain children
parents: [
{
id: 100,
parents: [
{
id: 101,
parents: [],
children: [
{
id: 103,
children: []
}
]
}
]
},
{
id: 102,
parents: []
}
]
},
{
id: 21,
parents: []
},
]
*/

View file

@ -0,0 +1,223 @@
import { findIndex, orderBy } from 'lodash'
/*
step 1
generate an object/array that represents the intended layout
step 2
generate x,y coordinates for every topic in the layout object
*/
// synapses = [{ topic1_id: 4, topic2_id: 5, direction: 'from-to', desc: 'has reply' }]
const isEven = n => n % 2 === 0
const isOdd = n => Math.abs(n % 2) === 1
export const X_GRID_SPACE = 250
export const Y_GRID_SPACE = 200
export const ISLAND_SPACING = 300
export const generateLayoutObject = (topics, synapses, focalTopicId) => {
let layout = [] // will be the final output
const usedTopics = {} // will store the topics that have been placed into islands
let newRoot
let currentTopic
const addParentsAndChildren = (topic, getParents, getChildren, degreeFromFocus) => {
if (!topic.id) return topic
usedTopics[topic.id] = true
topic.degreeFromFocus = degreeFromFocus
const nextDegree = degreeFromFocus + 1
if (getChildren) {
topic.children = []
synapses.filter(synapse => {
return synapse.topic1_id === topic.id
&& !usedTopics[synapse.topic2_id]
&& synapse.category === 'from-to'
})
.map(synapse => synapse.topic2_id)
.forEach(childId => topic.children.push(addParentsAndChildren({id: childId}, false, true, nextDegree)))
topic.children = orderBy(topic.children, 'maxDescendants', 'desc')
topic.maxDescendants = topic.children.length ? topic.children[0].maxDescendants + 1 : 0
}
if (getParents) {
topic.parents = []
synapses.filter(synapse => {
return synapse.topic2_id === topic.id
&& !usedTopics[synapse.topic1_id]
&& synapse.category === 'from-to'
})
.map(synapse => synapse.topic1_id)
.forEach(parentId => topic.parents.push(addParentsAndChildren({id: parentId}, true, false, nextDegree)))
topic.parents = orderBy(topic.parents, 'maxAncestors', 'desc')
topic.maxAncestors = topic.parents.length ? topic.parents[0].maxAncestors + 1 : 0
}
if (getParents && getChildren) {
topic.longestThread = topic.maxDescendants + topic.maxAncestors + 1
}
return topic
}
// start with the focal node, and build its island
currentTopic = topics.find(t => t.id === focalTopicId)
if (!currentTopic) {
console.log('you didnt pass a valid focalTopicId')
return layout
}
newRoot = {
id: currentTopic.id
}
layout.push(addParentsAndChildren(newRoot, true, true, 0))
// right now there's no reasoning going on about the selection of focal topics
// its just whichever ones happen to be found in the array first
topics.forEach(topic => {
if (topic && topic.id && !usedTopics[topic.id]) {
newRoot = {
id: topic.id
}
layout.push(addParentsAndChildren(newRoot, true, true, 0))
}
})
return layout
}
export const generateObjectCoordinates = (layoutObject, focalTopicId, focalCoords) => {
const coords = {}
const traverseIsland = (island, func, parent, child) => {
func(island, parent, child)
if (island.parents) {
island.parents.forEach(p => traverseIsland(p, func, null, island))
}
if (island.children) {
island.children.forEach(c => traverseIsland(c, func, island, null))
}
}
// const myFunction = n => n*5
// myFunction(2) === 10
const positionTopic = tempPosStore => (topic, parent, child) => {
let pos = {}
const getYValueForX = (x, attempt = 0) => {
tempPosStore[x] = tempPosStore[x] || {}
let yValue
let relationSign
let indexOfTopic
let relation = parent || child
let arrayOfTopics = parent ? parent.children : (child ? child.parents : [])
// first figure out what you'd like it to be
// then figure out if that spot's taken
// and if it is then call this function again with another attempt
// after the focal topic only, ODD indexes will move negatively on the Y axis
// and EVEN indexes will move positively on the Y axis
// for everything beyond the direct parents and children of the focal topic
// maintain the positivity or negativity on the Y axis of its parent or child
if (!relation) yValue = 0
else if (attempt === 0) yValue = coords[relation.id].y
else if (attempt > 0) {
// if the relations sign is 0, alternate between putting this topic into the upper and lower quadrants
if (coords[relation.id].y === 0) {
indexOfTopic = findIndex(arrayOfTopics, t => t.id === topic.id)
relationSign = isOdd(indexOfTopic) ? 1 : -1
} else {
// if the quadrant of the related topic is already decided, make sure to keep it
relationSign = coords[relation.id].y > 0 ? 1 : -1
}
yValue = coords[relation.id].y + (Y_GRID_SPACE * attempt * relationSign)
}
if (tempPosStore[x][yValue]) yValue = getYValueForX(x, attempt + 1)
tempPosStore[x][yValue] = true
return yValue
}
pos.x = topic.degreeFromFocus * X_GRID_SPACE * (parent ? 1 : -1),
pos.y = getYValueForX(pos.x)
coords[topic.id] = pos
}
// lay all of them out as if there were no other ones
layoutObject.forEach((island, index) => {
const tempPosStore = {}
if (index === 0) {
tempPosStore[X_GRID_SPACE] = {
0: true
}
}
traverseIsland(island, positionTopic(tempPosStore))
})
// calculate the bounds of each island
const islandBoundArray= []
const adjustBounds = islandBounds => (topic, parent, child) => {
const relation = parent || child
if (!relation) return
islandBounds.minX = Math.min(islandBounds.minX, coords[topic.id].x)
islandBounds.maxX = Math.max(islandBounds.maxX, coords[topic.id].x)
islandBounds.minY = Math.min(islandBounds.minY, coords[topic.id].y)
islandBounds.maxY = Math.max(islandBounds.maxY, coords[topic.id].y)
}
layoutObject.forEach(island => {
const islandBounds = {
minX: coords[island.id].x,
maxX: coords[island.id].x,
minY: coords[island.id].y,
maxY: coords[island.id].y
}
islandBoundArray.push(islandBounds)
traverseIsland(island, adjustBounds(islandBounds))
})
// reposition the islands according to the bounds
const translateIsland = (island, x, y) => {
const adjustTopicPos = topic => {
coords[topic.id].x = coords[topic.id].x + x
coords[topic.id].y = coords[topic.id].y + y
}
traverseIsland(island, adjustTopicPos)
}
let maxYForIslands = 0 // the highest Y value that has thus been placed
let minYForIslands = 0 // the lowest Y value that has thus been placed
layoutObject.forEach((island, index) => {
let translateY
const islandHeight = islandBoundArray[index].maxY - islandBoundArray[index].minY
if (index === 0) {
translateIsland(island, focalCoords.x, focalCoords.y) // position the selected island to where the user has it already
maxYForIslands = focalCoords.y + islandBoundArray[0].maxY
minYForIslands = focalCoords.y + islandBoundArray[0].minY
}
else if (isOdd(index)) {
translateIsland(island, focalCoords.x - islandBoundArray[index].maxX, maxYForIslands + ISLAND_SPACING + Math.abs(islandBoundArray[index].minY))
maxYForIslands = maxYForIslands + ISLAND_SPACING + islandHeight
}
else {
translateIsland(island, focalCoords.x - islandBoundArray[index].maxX, minYForIslands - ISLAND_SPACING - islandBoundArray[index].maxY)
minYForIslands = minYForIslands - ISLAND_SPACING - islandHeight
}
})
return coords
}
export const getLayoutForData = (topics, synapses, focalTopicId, focalCoords) => {
return generateObjectCoordinates(generateLayoutObject(topics, synapses, focalTopicId), focalTopicId, focalCoords)
}

View file

@ -4,7 +4,9 @@ import { indexOf } from 'lodash'
import Active from './Active'
import Control from './Control'
import Create from './Create'
import DataModel from './DataModel'
import Engine from './Engine'
import Map from './Map'
import Mapper from './Mapper'
import Synapse from './Synapse'
@ -28,7 +30,7 @@ const Cable = {
},
unsubscribeFromMap: () => {
let self = Cable
self.sub.unsubscribe()
self.sub && self.sub.unsubscribe()
delete self.sub
},
synapseAdded: event => {
@ -44,15 +46,15 @@ const Cable = {
if (t1.authorizeToShow(m) && t2.authorizeToShow(m) && s.authorizeToShow(m) && !DataModel.Synapses.get(event.synapse.id)) {
// refactor the heck outta this, its adding wicked wait time
var topic1, topic2, node1, node2, synapse, mapping, cancel, mapper
const waitThenRenderSynapse = () => {
if (synapse && mapping && mapper) {
if (synapse && mapping && mapper && synapse.getTopic1() && synapse.getTopic2()) {
topic1 = synapse.getTopic1()
node1 = topic1.get('node')
topic2 = synapse.getTopic2()
node2 = topic2.get('node')
Synapse.renderSynapse(mapping, synapse, node1, node2, false)
Synapse.renderSynapse(mapping, synapse, node1, node2, true)
Engine.runLayout()
} else if (!cancel) {
setTimeout(waitThenRenderSynapse, 10)
}
@ -119,6 +121,7 @@ const Cable = {
}
DataModel.Synapses.remove(synapse)
DataModel.Mappings.remove(mapping)
Engine.runLayout()
}
},
topicAdded: event => {
@ -134,7 +137,8 @@ const Cable = {
const waitThenRenderTopic = () => {
if (topic && mapping && mapper) {
Topic.renderTopic(mapping, topic, false, false)
Topic.renderTopic(mapping, topic, true)
Engine.runLayout()
} else if (!cancel) {
setTimeout(waitThenRenderTopic, 10)
}
@ -185,7 +189,7 @@ const Cable = {
},
topicMoved: event => {
var topic, node, mapping
if (Active.Map) {
/*if (Active.Map) {
topic = DataModel.Topics.get(event.id)
mapping = DataModel.Mappings.get(event.mapping_id)
mapping.set('xloc', event.x)
@ -193,7 +197,7 @@ const Cable = {
if (topic) node = topic.get('node')
if (node) node.pos.setc(event.x, event.y)
Visualize.mGraph.plot()
}
}*/
},
topicRemoved: event => {
var topic = DataModel.Topics.get(event.id)
@ -203,6 +207,7 @@ const Cable = {
Control.hideNode(node.id)
DataModel.Topics.remove(topic)
DataModel.Mappings.remove(mapping)
Engine.runLayout()
}
},
messageCreated: event => {

View file

@ -3,6 +3,7 @@ import outdent from 'outdent'
import Active from './Active'
import DataModel from './DataModel'
import Engine from './Engine'
import Filter from './Filter'
import GlobalUI from './GlobalUI'
import Mouse from './Mouse'

View file

@ -1,10 +1,16 @@
/* global $, Hogan, Bloodhound */
/* global Metamaps, $, Hogan, Bloodhound */
import React from 'react'
import ReactDOM from 'react-dom'
import DataModel from './DataModel'
import Engine from './Engine'
import MetacodeSelect from '../components/MetacodeSelect'
import Mouse from './Mouse'
import Selected from './Selected'
import Synapse from './Synapse'
import Topic from './Topic'
import Util from './Util'
import Visualize from './Visualize'
import GlobalUI from './GlobalUI'
@ -16,9 +22,11 @@ const Create = {
newSelectedMetacodeNames: [],
selectedMetacodes: [],
newSelectedMetacodes: [],
init: function() {
recentMetacodes: [],
mostUsedMetacodes: [],
init: function (serverData) {
var self = Create
self.newTopic.init()
self.newTopic.init(serverData)
self.newSynapse.init()
// // SWITCHING METACODE SETS
@ -58,10 +66,11 @@ const Create = {
if (!custom) {
codesToSwitchToIds = $('#metacodeSwitchTabs' + set).attr('data-metacodes').split(',')
$('.customMetacodeList li').addClass('toggledOff')
Create.selectedMetacodes = []
Create.selectedMetacodeNames = []
Create.newSelectedMetacodes = []
Create.newSelectedMetacodeNames = []
console.log(codesToSwitchToIds)
Create.selectedMetacodes = codesToSwitchToIds
Create.selectedMetacodeNames = DataModel.Metacodes.filter(m => codesToSwitchToIds.indexOf(m.id) > -1).map(m => m.get('name'))
Create.newSelectedMetacodes = codesToSwitchToIds
Create.newSelectedMetacodeNames = DataModel.Metacodes.filter(m => codesToSwitchToIds.indexOf(m.id) > -1).map(m => m.get('name'))
} else if (custom) {
// uses .slice to avoid setting the two arrays to the same actual array
Create.selectedMetacodes = Create.newSelectedMetacodes.slice(0)
@ -70,12 +79,13 @@ const Create = {
}
// sort by name
for (var i = 0; i < codesToSwitchToIds.length; i++) {
metacodeModels.add(DataModel.Metacodes.get(codesToSwitchToIds[i]))
}
codesToSwitchToIds.forEach(id => {
const metacode = DataModel.Metacodes.get(id)
metacodeModels.add(metacode)
$('.customMetacodeList #' + id).removeClass('toggledOff')
})
metacodeModels.sort()
$('#metacodeImg, #metacodeImgTitle').empty()
$('#metacodeImg').removeData('cloudcarousel')
var newMetacodes = ''
metacodeModels.each(function(metacode) {
@ -83,16 +93,16 @@ const Create = {
})
$('#metacodeImg').empty().append(newMetacodes).CloudCarousel({
titleBox: $('#metacodeImgTitle'),
yRadius: 40,
xRadius: 190,
xPos: 170,
yPos: 40,
speed: 0.3,
mouseWheel: true,
bringToFront: true
})
Create.newTopic.setMetacode(metacodeModels.models[0].id)
GlobalUI.closeLightbox()
$('#topic_name').focus()
@ -119,13 +129,7 @@ const Create = {
var self = Create
self.isSwitchingSet = false
if (self.selectedMetacodeSet !== 'metacodeset-custom') {
$('.customMetacodeList li').addClass('toggledOff')
self.selectedMetacodes = []
self.selectedMetacodeNames = []
self.newSelectedMetacodes = []
self.newSelectedMetacodeNames = []
} else { // custom set is selected
if (self.selectedMetacodeSet === 'metacodeset-custom') {
// reset it to the current actual selection
$('.customMetacodeList li').addClass('toggledOff')
for (var i = 0; i < self.selectedMetacodes.length; i++) {
@ -139,27 +143,33 @@ const Create = {
$('#topic_name').focus()
},
newTopic: {
init: function() {
$('#topic_name').keyup(function(e) {
const ESC = 27
init: function (serverData) {
const DOWN_ARROW = 40
const ESC = 27
if (!serverData.ActiveMapper) return
$('#topic_name').keyup(function (e) {
Create.newTopic.name = $(this).val()
if (e.which == DOWN_ARROW && !Create.newTopic.name.length) {
Create.newTopic.openSelector()
}
if (e.keyCode === ESC) {
Create.newTopic.hide()
} // if
Create.newTopic.name = $(this).val()
})
$('.pinCarousel').click(function() {
if (Create.newTopic.pinned) {
$('.pinCarousel').removeClass('isPinned')
Create.newTopic.pinned = false
} else {
$('.pinCarousel').addClass('isPinned')
Create.newTopic.pinned = true
}
$('.selectedMetacode').click(function() {
if (Create.newTopic.metacodeSelectorOpen) {
Create.newTopic.hideSelector()
$('#topic_name').focus()
} else Create.newTopic.openSelector()
})
Create.newTopic.initSelector()
var topicBloodhound = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
@ -200,52 +210,86 @@ const Create = {
})
}
})
$('#topic_name').click(function() { Create.newTopic.hideSelector() })
// initialize metacode spinner and then hide it
$('#metacodeImg').CloudCarousel({
titleBox: $('#metacodeImgTitle'),
yRadius: 40,
xRadius: 190,
xPos: 170,
yPos: 40,
speed: 0.3,
mouseWheel: true,
bringToFront: true
})
$('.new_topic').hide()
$('#new_topic').attr('oncontextmenu', 'return false') // prevents the mouse up event from opening the default context menu on this element
$('#new_topic').hide()
.css({ left: '50%', top: '50%' })
.attr('oncontextmenu', 'return false') // prevents the mouse up event from opening the default context menu on this element
},
name: null,
newId: 1,
beingCreated: false,
metacodeSelectorOpen: false,
metacode: null,
x: null,
y: null,
addSynapse: false,
pinned: false,
open: function() {
$('#new_topic').fadeIn('fast', function() {
$('#topic_name').focus()
})
Create.newTopic.beingCreated = true
Create.newTopic.name = ''
GlobalUI.hideDiv('#instructions')
initSelector: function () {
ReactDOM.render(
React.createElement(MetacodeSelect, {
onClick: function (id) {
Create.newTopic.setMetacode(id)
Create.newTopic.hideSelector()
$('#topic_name').focus()
},
close: function () {
Create.newTopic.hideSelector()
$('#topic_name').focus()
},
metacodes: DataModel.Metacodes.filter(m => Create.selectedMetacodes.indexOf(m.id.toString()) > -1)
}),
document.getElementById('metacodeSelector')
)
},
hide: function(force) {
if (force || !Create.newTopic.pinned) {
$('#new_topic').fadeOut('fast')
}
if (force) {
$('.pinCarousel').removeClass('isPinned')
Create.newTopic.pinned = false
}
if (DataModel.Topics.length === 0) {
GlobalUI.showDiv('#instructions')
}
Create.newTopic.beingCreated = false
openSelector: function () {
Create.newTopic.initSelector()
$('#metacodeSelector').show()
Create.newTopic.metacodeSelectorOpen = true
$('.metacodeFilterInput').focus()
$('.selectedMetacode').addClass('isBeingSelected')
},
hideSelector: function () {
ReactDOM.unmountComponentAtNode(document.getElementById('metacodeSelector'))
$('#metacodeSelector').hide()
Create.newTopic.metacodeSelectorOpen = false
$('.selectedMetacode').removeClass('isBeingSelected')
},
setMetacode: function (id) {
Create.newTopic.metacode = id
var metacode = DataModel.Metacodes.get(id)
$('.selectedMetacode img').attr('src', metacode.get('icon'))
$('.selectedMetacode span').html(metacode.get('name'))
$.ajax({
type: 'POST',
dataType: 'json',
url: '/user/update_metacode_focus',
data: { value: id },
success: function (data) {},
error: function () {
console.log('failed to save metacode focus')
}
})
},
reset: function() {
$('#topic_name').typeahead('val', '')
Create.newTopic.hideSelector()
},
position: function() {
const pixels = Util.coordsToPixels(Visualize.mGraph, Mouse.newNodeCoords)
$('#new_topic').css({
left: pixels.x,
top: pixels.y
})
}
},
newSynapse: {
@ -317,7 +361,9 @@ const Create = {
$('#synapse_desc').focusout(function() {
if (Create.newSynapse.beingCreated) {
Synapse.createSynapseLocally()
Synapse.createSynapseLocally(Create.newSynapse.topic1id, Create.newSynapse.topic2id)
Engine.runLayout()
Create.newSynapse.hide()
}
})
@ -325,7 +371,9 @@ const Create = {
const TAB = 9
if (Create.newSynapse.beingCreated && e.keyCode === TAB) {
e.preventDefault()
Synapse.createSynapseLocally()
Synapse.createSynapseLocally(Create.newSynapse.topic1id, Create.newSynapse.topic2id)
Engine.runLayout()
Create.newSynapse.hide()
}
})
@ -334,10 +382,13 @@ const Create = {
Synapse.getSynapseFromAutocomplete(datum.id)
} else {
Create.newSynapse.description = datum.value
Synapse.createSynapseLocally()
Synapse.createSynapseLocally(Create.newSynapse.topic1id, Create.newSynapse.topic2id)
Engine.runLayout()
Create.newSynapse.hide()
}
})
},
focusNode: null,
beingCreated: false,
description: null,
topic1id: null,
@ -356,8 +407,26 @@ const Create = {
Create.newTopic.addSynapse = false
Create.newSynapse.topic1id = 0
Create.newSynapse.topic2id = 0
Create.newSynapse.node1 = null
Create.newSynapse.node2 = null
Mouse.synapseStartCoordinates = []
Mouse.synapseEndCoordinates = null
if (Visualize.mGraph) Visualize.mGraph.plot()
},
updateForm: function() {
let pixelPos, midpoint = {}
if (Create.newSynapse.beingCreated) {
Mouse.synapseEndCoordinates = {
x: Create.newSynapse.node2.pos.getc().x,
y: Create.newSynapse.node2.pos.getc().y
}
// position the form
midpoint.x = Create.newSynapse.node1.pos.getc().x + (Create.newSynapse.node2.pos.getc().x - Create.newSynapse.node1.pos.getc().x) / 2
midpoint.y = Create.newSynapse.node1.pos.getc().y + (Create.newSynapse.node2.pos.getc().y - Create.newSynapse.node1.pos.getc().y) / 2
pixelPos = Util.coordsToPixels(Visualize.mGraph, midpoint)
$('#new_synapse').css('left', pixelPos.x + 'px')
$('#new_synapse').css('top', pixelPos.y + 'px')
}
}
}
}

View file

@ -21,16 +21,16 @@ const Mapping = Backbone.Model.extend({
})
}
},
getMap: function() {
return Map.get(this.get('map_id'))
getMap: function(callback) {
Map.get(this.get('map_id'), callback)
},
getTopic: function() {
if (this.get('mappable_type') !== 'Topic') return false
return Topic.get(this.get('mappable_id'))
},
getSynapse: function() {
if (this.get('mappable_type') !== 'Synapse') return false
return Synapse.get(this.get('mappable_id'))
getMappable: function(callback) {
if (this.get('mappable_type') === 'Topic') {
Topic.get(this.get('mappable_id'), callback)
}
else if (this.get('mappable_type') === 'Synapse') {
Synapse.get(this.get('mappable_id'), callback)
}
}
})

View file

@ -84,8 +84,8 @@ const Synapse = Backbone.Model.extend({
}
}
if (Active.Map) {
mapping = providedMapping || this.getMapping()
if (Active.Map && providedMapping) {
mapping = providedMapping
mappingID = mapping.isNew() ? mapping.cid : mapping.id
edge.data.$mappings = []
edge.data.$mappingIDs = [mappingID]
@ -96,10 +96,12 @@ const Synapse = Backbone.Model.extend({
updateEdge: function() {
var mapping
var edge = this.get('edge')
edge.data.$synapses = edge.data.$synapses || []
edge.getData('synapses').push(this)
if (Active.Map) {
mapping = this.getMapping()
edge.data.$mappings = edge.data.$mappings || []
edge.getData('mappings').push(mapping)
}

View file

@ -3,6 +3,7 @@ import Backbone from 'backbone'
try { Backbone.$ = window.$ } catch (err) {}
import Active from '../Active'
import Engine from '../Engine'
import Filter from '../Filter'
import TopicCard from '../TopicCard'
import Visualize from '../Visualize'

View file

@ -1,4 +1,5 @@
import Active from '../Active'
import Engine from '../Engine'
import Filter from '../Filter'
import { InfoBox } from '../Map'

View file

@ -0,0 +1,68 @@
//import Matter, { Vector, Sleeping, World, Constraint, Composite, Runner, Common, Body, Bodies, Events } from 'matter-js'
import { last, sortBy, values } from 'lodash'
import $jit from '../patched/JIT'
import { getLayoutForData, X_GRID_SPACE } from '../ConvoAlgo'
import Active from './Active'
import Create from './Create'
import DataModel from './DataModel'
import Mouse from './Mouse'
import JIT from './JIT'
import Visualize from './Visualize'
const Engine = {
init: (serverData) => {
},
run: init => {
if (init) {
if (Active.Mapper && Object.keys(Visualize.mGraph.graph.nodes).length) {
Engine.setFocusNode(Engine.findFocusNode(Visualize.mGraph.graph.nodes), true)
}
}
},
endActiveMap: () => {
},
runLayout: init => {
Visualize.mGraph.busy = true
const synapses = DataModel.Synapses.map(s => s.attributes)
const topics = DataModel.Topics.map(t => t.attributes)
const focalNodeId = Create.newSynapse.focusNode.getData('topic').id
const focalCoords = init ? { x: 0, y: 0 } : Create.newSynapse.focusNode.pos
const layout = getLayoutForData(topics, synapses, focalNodeId, focalCoords)
Visualize.mGraph.graph.eachNode(n => {
let calculatedCoords = layout[n.getData('topic').id]
const endPos = new $jit.Complex(calculatedCoords.x, calculatedCoords.y)
n.setPos(endPos, 'end')
})
Visualize.mGraph.animate({
modes: ['linear'],
transition: $jit.Trans.Quart.easeOut,
duration: 500,
onComplete: () => {
Visualize.mGraph.busy = false
Create.newSynapse.updateForm()
Create.newTopic.position()
}
})
},
findFocusNode: nodes => {
return last(sortBy(values(nodes), n => new Date(n.getData('topic').get('created_at'))))
},
setFocusNode: (node, init, dontRun) => {
if (!Active.Mapper) return
Create.newSynapse.focusNode = node
Mouse.focusNodeCoords = node.pos
Mouse.newNodeCoords = {
x: node.pos.x + X_GRID_SPACE,
y: node.pos.y
}
Create.newSynapse.updateForm()
Create.newTopic.position()
if (!dontRun) Engine.runLayout(init)
}
}
export default Engine

View file

@ -9,6 +9,7 @@ import Active from './Active'
import Control from './Control'
import Create from './Create'
import DataModel from './DataModel'
import Engine from './Engine'
import Filter from './Filter'
import GlobalUI from './GlobalUI'
import Map from './Map'
@ -392,7 +393,6 @@ const JIT = {
Visualize.mGraph.busy = false
Mouse.boxEndCoordinates = eventInfo.getPos()
JIT.selectWithBox(e)
return
}
}
@ -404,6 +404,7 @@ const JIT = {
JIT.selectEdgeOnClickHandler(node, e)
} else if (node && !node.nodeFrom) {
JIT.selectNodeOnClickHandler(node, e)
Engine.setFocusNode(node)
} else {
JIT.canvasClickHandler(eventInfo.getPos(), e)
} // if
@ -415,7 +416,6 @@ const JIT = {
if (Mouse.boxStartCoordinates) {
Create.newSynapse.hide()
Create.newTopic.hide()
Visualize.mGraph.busy = false
Mouse.boxEndCoordinates = eventInfo.getPos()
JIT.selectWithBox(e)
@ -432,7 +432,6 @@ const JIT = {
} else {
// right click open space
Create.newSynapse.hide()
Create.newTopic.hide()
}
}
},
@ -721,14 +720,16 @@ const JIT = {
$('canvas').css('cursor', 'default')
}
}, // onMouseMoveHandler
enterKeyHandler: function() {
enterKeyHandler: function(e) {
const creatingMap = GlobalUI.lightbox
if (creatingMap === 'newmap' || creatingMap === 'forkmap') {
GlobalUI.CreateMap.submit()
} else if (Create.newTopic.beingCreated) {
} else if (e.target.id === 'topic_name' && !Create.newTopic.metacodeSelectorOpen) {
Topic.createTopicLocally()
} else if (Create.newSynapse.beingCreated) {
Synapse.createSynapseLocally()
Synapse.createSynapseLocally(Create.newSynapse.topic1id, Create.newSynapse.topic2id)
Engine.runLayout()
Create.newSynapse.hide()
}
}, // enterKeyHandler
escKeyHandler: function() {
@ -741,131 +742,28 @@ const JIT = {
var authorized = Active.Map && Active.Map.authorizeToEdit(Active.Mapper)
if (node && !node.nodeFrom) {
self.handleSelectionBeforeDragging(node, e)
const pos = eventInfo.getPos()
const EDGE_THICKNESS = 30
const SHIFT = 2 / 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 = Visualize.mGraph.canvas.getSize().width
const height = Visualize.mGraph.canvas.getSize().height
const xPix = Util.coordsToPixels(Visualize.mGraph, pos).x
const yPix = Util.coordsToPixels(Visualize.mGraph, pos).y
if (self.dragFlag === 0) {
self.mouseDownPix = Util.coordsToPixels(Visualize.mGraph, eventInfo.getPos())
self.dragFlag = 1
}
if (Util.getDistance(Util.coordsToPixels(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(Visualize.mGraph, { x: EDGE_THICKNESS, y: yPix }).x - SHIFT, y: pos.y }
Visualize.mGraph.canvas.translate(SHIFT, 0)
self.updateTopicPositions(node, self.virtualPointer)
Visualize.mGraph.plot()
self.dragLeftEdge = setInterval(function() {
self.virtualPointer = { x: Util.pixelsToCoords(Visualize.mGraph, { x: EDGE_THICKNESS, y: yPix }).x - SHIFT, y: pos.y }
Visualize.mGraph.canvas.translate(SHIFT, 0)
self.updateTopicPositions(node, self.virtualPointer)
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(Visualize.mGraph, { x: width - EDGE_THICKNESS, y: yPix }).x + SHIFT, y: pos.y }
Visualize.mGraph.canvas.translate(-SHIFT, 0)
self.updateTopicPositions(node, self.virtualPointer)
Visualize.mGraph.plot()
self.dragRightEdge = setInterval(function() {
self.virtualPointer = { x: Util.pixelsToCoords(Visualize.mGraph, { x: width - EDGE_THICKNESS, y: yPix }).x + SHIFT, y: pos.y }
Visualize.mGraph.canvas.translate(-SHIFT, 0)
self.updateTopicPositions(node, self.virtualPointer)
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(Visualize.mGraph, { x: xPix, y: EDGE_THICKNESS }).y - SHIFT }
Visualize.mGraph.canvas.translate(0, SHIFT)
self.updateTopicPositions(node, self.virtualPointer)
Visualize.mGraph.plot()
self.dragTopEdge = setInterval(function() {
self.virtualPointer = { x: pos.x, y: Util.pixelsToCoords(Visualize.mGraph, { x: xPix, y: EDGE_THICKNESS }).y - SHIFT }
Visualize.mGraph.canvas.translate(0, SHIFT)
self.updateTopicPositions(node, self.virtualPointer)
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(Visualize.mGraph, { x: xPix, y: height - EDGE_THICKNESS }).y + SHIFT }
Visualize.mGraph.canvas.translate(0, -SHIFT)
self.updateTopicPositions(node, self.virtualPointer)
Visualize.mGraph.plot()
self.dragBottomEdge = setInterval(function() {
self.virtualPointer = { x: pos.x, y: Util.pixelsToCoords(Visualize.mGraph, { x: xPix, y: height - EDGE_THICKNESS }).y + SHIFT }
Visualize.mGraph.canvas.translate(0, -SHIFT)
self.updateTopicPositions(node, self.virtualPointer)
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.updateTopicPositions(node, pos)
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 ((e.button === 0 || e.buttons === 0) && authorized) {
// start synapse creation ->second option is for firefox
if (JIT.tempInit === false) {
JIT.tempNode = node
JIT.tempInit = true
Create.newTopic.hide()
Create.newSynapse.hide()
// set the draw synapse start positions
var l = Selected.Nodes.length
if (l > 0) {
for (let i = l - 1; i >= 0; i -= 1) {
const n = Selected.Nodes[i]
Mouse.synapseStartCoordinates = []
if (Selected.Nodes.length) {
Selected.Nodes.forEach(n => {
Mouse.synapseStartCoordinates.push({
x: n.pos.getc().x,
y: n.pos.getc().y
})
}
} else {
})
}
else {
Mouse.synapseStartCoordinates = [{
x: JIT.tempNode.pos.getc().x,
y: JIT.tempNode.pos.getc().y
x: node.pos.getc().x,
y: node.pos.getc().y
}]
}
Mouse.synapseEndCoordinates = {
@ -877,43 +775,28 @@ const JIT = {
let temp = eventInfo.getNode()
if (temp !== false && temp.id !== node.id && Selected.Nodes.indexOf(temp) === -1) { // this means a Node has been returned
JIT.tempNode2 = temp
Mouse.synapseEndCoordinates = {
x: JIT.tempNode2.pos.getc().x,
y: JIT.tempNode2.pos.getc().y
}
// before making the highlighted one bigger, make sure all the others are regular size
Visualize.mGraph.graph.eachNode(function(n) {
n.setData('dim', 25, 'current')
})
temp.setData('dim', 35, 'current')
Visualize.mGraph.plot()
} else if (!temp) {
JIT.tempNode2 = null
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')
Create.newTopic.x = eventInfo.getPos().x
Create.newTopic.y = eventInfo.getPos().y
Visualize.mGraph.plot()
Mouse.synapseEndCoordinates = {
x: pos.x,
y: pos.y
}
Visualize.mGraph.graph.eachNode(function(n) {
n.setData('dim', 25, 'current')
})
}
} else if ((e.button === 2 || (e.button === 0 && e.altKey) || e.buttons === 2) && 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.')
}
}
Visualize.mGraph.plot()
}, // onDragMoveTopicHandler
onDragCancelHandler: function(node, eventInfo, e) {
JIT.tempNode = null
@ -931,30 +814,15 @@ const JIT = {
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 (JIT.tempInit && JIT.tempNode2 === null) {
// this means you want to add a new topic, and then a synapse
Create.newTopic.addSynapse = true
Create.newTopic.open()
Mouse.synapseEndCoordinates = null
} else if (JIT.tempInit && JIT.tempNode2 !== null) {
// this means you want to create a synapse between two existing topics
Create.newTopic.addSynapse = false
Create.newSynapse.topic1id = JIT.tempNode.getData('topic').id
Create.newSynapse.topic2id = JIT.tempNode2.getData('topic').id
Create.newSynapse.node1 = JIT.tempNode
Create.newSynapse.node2 = JIT.tempNode2
JIT.tempNode2.setData('dim', 25, 'current')
Visualize.mGraph.plot()
midpoint.x = JIT.tempNode.pos.getc().x + (JIT.tempNode2.pos.getc().x - JIT.tempNode.pos.getc().x) / 2
midpoint.y = JIT.tempNode.pos.getc().y + (JIT.tempNode2.pos.getc().y - JIT.tempNode.pos.getc().y) / 2
pixelPos = Util.coordsToPixels(Visualize.mGraph, midpoint)
@ -964,36 +832,8 @@ const JIT = {
JIT.tempNode = null
JIT.tempNode2 = null
JIT.tempInit = false
} else if (!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 = Active.Map
if (!map) return false
return map.authorizeToEdit(Active.Mapper)
}
if (checkWhetherToSave()) {
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 = Selected.Nodes.length
for (var i = l - 1; i >= 0; i -= 1) {
const n = Selected.Nodes[i]
if (n !== node) {
mapping = n.getData('mapping')
mapping.save({
xloc: n.getPos().x,
yloc: n.getPos().y
})
}
}
}
}
Visualize.mGraph.plot()
}, // onDragEndTopicHandler
canvasClickHandler: function(canvasLoc, e) {
// grab the location and timestamp of the click
@ -1004,27 +844,12 @@ const JIT = {
const authorized = Active.Map && Active.Map.authorizeToEdit(Active.Mapper)
if (now - storedTime < Mouse.DOUBLE_CLICK_TOLERANCE && !Mouse.didPan) {
if (Active.Map && !authorized) {
GlobalUI.notifyUser('Cannot edit Public map.')
return
} else if (Active.Topic) {
GlobalUI.notifyUser('Cannot create in Topic view.')
return
}
// DOUBLE CLICK
// pop up node creation :)
Create.newTopic.addSynapse = false
Create.newTopic.x = canvasLoc.x
Create.newTopic.y = canvasLoc.y
$('#new_topic').css('left', e.clientX + 'px')
$('#new_topic').css('top', e.clientY + 'px')
Create.newTopic.open()
} else if (!Mouse.didPan) {
// SINGLE CLICK, no pan
Filter.close()
TopicCard.hideCard()
SynapseCard.hideCard()
Create.newTopic.hide()
$('.rightclickmenu').remove()
// reset the draw synapse positions to false
Mouse.synapseStartCoordinates = []
@ -1038,7 +863,6 @@ const JIT = {
}
} else {
// SINGLE CLICK, resulting from pan
Create.newTopic.hide()
}
}, // canvasClickHandler
updateTopicPositions: function(node, pos) {
@ -1272,26 +1096,6 @@ const JIT = {
Mouse.boxEndCoordinates = false
Visualize.mGraph.plot()
}, // selectWithBox
drawSelectBox: function(eventInfo, e) {
const ctx = Visualize.mGraph.canvas.getCtx()
const startX = Mouse.boxStartCoordinates.x
const startY = Mouse.boxStartCoordinates.y
const currX = eventInfo.getPos().x
const currY = eventInfo.getPos().y
Visualize.mGraph.canvas.clear()
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 (Visualize.mGraph.busy) return
@ -1320,43 +1124,19 @@ const JIT = {
// wait a certain length of time, then check again, then run this code
setTimeout(function() {
if (!JIT.nodeWasDoubleClicked()) {
var nodeAlreadySelected = node.selected
if (e.button === 1 && !e.ctrlKey) {
var len = Selected.Nodes.length
if (e.button !== 1) {
if (!e.shiftKey) {
Control.deselectAllNodes()
Control.deselectAllEdges()
}
for (let i = 0; i < len; i += 1) {
let n = Selected.Nodes[i]
let result = Util.openLink(DataModel.Topics.get(n.id).attributes.link)
if (nodeAlreadySelected) {
Control.deselectNode(node)
} else {
Control.selectNode(node, e)
}
// trigger animation to final styles
Visualize.mGraph.fx.animate({
modes: ['edge-property:lineWidth:color:alpha'],
duration: 500
})
Visualize.mGraph.plot()
} else {
if (!e.ctrlKey) {
var len = Selected.Nodes.length
for (let i = 0; i < len; i += 1) {
let n = Selected.Nodes[i]
let result = Util.openLink(DataModel.Topics.get(n.id).attributes.link)
if (!result) { // if link failed to open
break
}
}
if (!node.selected) {
Util.openLink(DataModel.Topics.get(node.id).attributes.link)
if (!result) { // if link failed to open
break
}
}
if (!node.selected) Util.openLink(DataModel.Topics.get(node.id).attributes.link)
}
}
}, Mouse.DOUBLE_CLICK_TOLERANCE)

View file

@ -2,6 +2,7 @@
import Active from './Active'
import Control from './Control'
import Create from './Create'
import DataModel from './DataModel'
import JIT from './JIT'
import Mobile from './Mobile'
@ -24,7 +25,7 @@ const Listeners = {
case 13: // if enter key is pressed
// prevent topic creation if sending a message
if (e.target.className !== 'chat-input') {
JIT.enterKeyHandler()
JIT.enterKeyHandler(e)
}
break
case 27: // if esc key is pressed
@ -130,6 +131,8 @@ const Listeners = {
$(window).resize(function() {
if (Visualize && Visualize.mGraph) {
Util.resizeCanvas(Visualize.mGraph.canvas)
Create.newSynapse.updateForm()
Create.newTopic.position()
}
if (Active.Map && Realtime.inConversation) Realtime.positionVideos()

View file

@ -8,6 +8,7 @@ import AutoLayout from '../AutoLayout'
import Create from '../Create'
import DataModel from '../DataModel'
import DataModelMap from '../DataModel/Map'
import Engine from '../Engine'
import Filter from '../Filter'
import GlobalUI from '../GlobalUI'
import JIT from '../JIT'
@ -146,11 +147,12 @@ const Map = {
$('.rightclickmenu').remove()
TopicCard.hideCard()
SynapseCard.hideCard()
Create.newTopic.hide(true) // true means force (and override pinned)
$('#new_topic').hide()
Create.newSynapse.hide()
Filter.close()
InfoBox.close()
Realtime.endActiveMap()
Engine.endActiveMap()
$('.viewOnly').removeClass('isViewOnly')
}
},

View file

@ -6,11 +6,13 @@ const Mouse = {
edgeHoveringOver: false,
boxStartCoordinates: false,
boxEndCoordinates: false,
focusNodeCoords: null,
newNodeCoords: { x: 100, y: 0 },
synapseStartCoordinates: [],
synapseEndCoordinates: null,
lastNodeClick: 0,
lastCanvasClick: 0,
DOUBLE_CLICK_TOLERANCE: 300
DOUBLE_CLICK_TOLERANCE: 501
}
export default Mouse

View file

@ -5,6 +5,7 @@ import SocketIoConnection from 'simplewebrtc/socketioconnection'
import Active from '../Active'
import Cable from '../Cable'
import Create from '../Create'
import DataModel from '../DataModel'
import JIT from '../JIT'
import Util from '../Util'
@ -233,8 +234,13 @@ let Realtime = {
setupLocalEvents: function() {
var self = Realtime
// local event listeners that trigger events
$(document).on(JIT.events.zoom + '.map', self.positionPeerIcons)
$(document).on(JIT.events.pan + '.map', self.positionPeerIcons)
const panOrZoom = () => {
self.positionPeerIcons()
Create.newSynapse.updateForm()
Create.newTopic.position()
}
$(document).on(JIT.events.zoom + '.map', panOrZoom)
$(document).on(JIT.events.pan + '.map', panOrZoom)
$(document).on('mousemove.map', function(event) {
var pixels = {
x: event.pageX,

View file

@ -4,6 +4,8 @@ import Active from './Active'
import Control from './Control'
import Create from './Create'
import DataModel from './DataModel'
import Engine from './Engine'
import JIT from './JIT'
import Map from './Map'
import Selected from './Selected'
import Settings from './Settings'
@ -27,91 +29,48 @@ const Synapse = {
} else callback(DataModel.Synapses.get(id))
},
renderSynapse: function(mapping, synapse, node1, node2, createNewInDB) {
var edgeOnViz
var newedge = synapse.createEdge(mapping)
renderSynapse: function(mapping, synapse, node1, node2, fromRemote) {
const newedge = synapse.createEdge(mapping)
Visualize.mGraph.graph.addAdjacence(node1, node2, newedge.data)
edgeOnViz = Visualize.mGraph.graph.getAdjacence(node1.id, node2.id)
const edgeOnViz = Visualize.mGraph.graph.getAdjacence(node1.id, node2.id)
synapse.set('edge', edgeOnViz)
synapse.updateEdge() // links the synapse and the mapping to the edge
Control.selectEdge(edgeOnViz)
var synapseSuccessCallback = function(synapseModel, response) {
if (Active.Map) {
mapping.save({ mappable_id: synapseModel.id }, {
error: function(model, response) {
console.log('error saving mapping to database')
}
})
}
}
if (!Settings.sandbox && createNewInDB) {
if (synapse.isNew()) {
synapse.save(null, {
success: synapseSuccessCallback,
error: function(model, response) {
console.log('error saving synapse to database')
}
})
} else if (!synapse.isNew() && Active.Map) {
mapping.save(null, {
error: function(model, response) {
console.log('error saving mapping to database')
}
})
}
if (!fromRemote && synapse.isNew()) {
synapse.save(null, {
success: synapseModel => Active.Map && mapping.save({ mappable_id: synapseModel.id })
})
} else if (!fromRemote && !synapse.isNew() && Active.Map) {
mapping.save()
}
},
createSynapseLocally: function() {
createSynapseLocally: function(topic1id, topic2id) {
var self = Synapse
let topic1
let topic2
let node1
let node2
let synapse
let mapping
$(document).trigger(Map.events.editedByActiveMapper)
// for each node in this array we will create a synapse going to the position2 node.
var synapsesToCreate = []
topic2 = DataModel.Topics.get(Create.newSynapse.topic2id)
node2 = topic2.get('node')
var len = Selected.Nodes.length
if (len === 0) {
topic1 = DataModel.Topics.get(Create.newSynapse.topic1id)
synapsesToCreate[0] = topic1.get('node')
} else if (len > 0) {
synapsesToCreate = Selected.Nodes
const synapsesToCreate = []
const topic2 = DataModel.Topics.get(topic2id)
const node2 = topic2.get('node')
if (Selected.Nodes.length === 0) {
synapsesToCreate.push(DataModel.Topics.get(topic1id).get('node'))
} else {
synapsesToCreate.concat(Selected.Nodes)
}
for (var i = 0; i < synapsesToCreate.length; i++) {
node1 = synapsesToCreate[i]
topic1 = node1.getData('topic')
synapse = new DataModel.Synapse({
desc: Create.newSynapse.description,
topic1_id: topic1.isNew() ? topic1.cid : topic1.id,
topic2_id: topic2.isNew() ? topic2.cid : topic2.id
synapsesToCreate.forEach(node1 => {
const topic1 = node1.getData('topic')
const synapse = new DataModel.Synapse({
desc: Create.newSynapse.description || '',
topic1_id: topic1.id,
topic2_id: topic2.id
})
DataModel.Synapses.add(synapse)
mapping = new DataModel.Mapping({
const mapping = new DataModel.Mapping({
mappable_type: 'Synapse',
mappable_id: synapse.cid
})
DataModel.Mappings.add(mapping)
// this function also includes the creation of the synapse in the database
self.renderSynapse(mapping, synapse, node1, node2, true)
} // for each in synapsesToCreate
Create.newSynapse.hide()
self.renderSynapse(mapping, synapse, node1, node2)
}) // for each in synapsesToCreate
},
getSynapseFromAutocomplete: function(id) {
var self = Synapse
@ -127,7 +86,8 @@ const Synapse = {
const topic2 = DataModel.Topics.get(Create.newSynapse.topic2id)
const node2 = topic2.get('node')
Create.newSynapse.hide()
self.renderSynapse(mapping, synapse, node1, node2, true)
self.renderSynapse(mapping, synapse, node1, node2)
Engine.runLayout()
})
}
}

View file

@ -6,13 +6,16 @@ import Active from './Active'
import AutoLayout from './AutoLayout'
import Create from './Create'
import DataModel from './DataModel'
import Engine from './Engine'
import Filter from './Filter'
import GlobalUI from './GlobalUI'
import JIT from './JIT'
import Map from './Map'
import Mouse from './Mouse'
import Router from './Router'
import Selected from './Selected'
import Settings from './Settings'
import Synapse from './Synapse'
import SynapseCard from './SynapseCard'
import TopicCard from './TopicCard'
import Util from './Util'
@ -165,192 +168,86 @@ const Topic = {
})
},
// opts is additional options in a hash
// TODO: move createNewInDB and permitCreateSynapseAfter into opts
renderTopic: function(mapping, topic, createNewInDB, permitCreateSynapseAfter, opts = {}) {
var nodeOnViz, tempPos
var newnode = topic.createNode()
var midpoint = {}
var pixelPos
renderTopic: function(mapping, topic, fromRemote) {
let nodeOnViz
const newnode = topic.createNode()
const createSynapse = !!Create.newSynapse.focusNode && !fromRemote
const connectToId = createSynapse ? Create.newSynapse.focusNode.getData('topic').id : null
if (!$.isEmptyObject(Visualize.mGraph.graph.nodes)) {
Visualize.mGraph.graph.addNode(newnode)
nodeOnViz = Visualize.mGraph.graph.getNode(newnode.id)
topic.set('node', nodeOnViz, {silent: true})
topic.updateNode() // links the topic and the mapping to the node
nodeOnViz.setData('dim', 1, 'start')
nodeOnViz.setData('dim', 25, 'end')
if (Visualize.type === 'RGraph') {
tempPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc'))
tempPos = tempPos.toPolar()
nodeOnViz.setPos(tempPos, 'current')
nodeOnViz.setPos(tempPos, 'start')
nodeOnViz.setPos(tempPos, 'end')
} else if (Visualize.type === 'ForceDirected') {
nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'current')
nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'start')
nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'end')
}
if (Create.newTopic.addSynapse && permitCreateSynapseAfter) {
Create.newSynapse.topic1id = JIT.tempNode.getData('topic').id
// position the form
midpoint.x = JIT.tempNode.pos.getc().x + (nodeOnViz.pos.getc().x - JIT.tempNode.pos.getc().x) / 2
midpoint.y = JIT.tempNode.pos.getc().y + (nodeOnViz.pos.getc().y - JIT.tempNode.pos.getc().y) / 2
pixelPos = Util.coordsToPixels(Visualize.mGraph, midpoint)
$('#new_synapse').css('left', pixelPos.x + 'px')
$('#new_synapse').css('top', pixelPos.y + 'px')
// show the form
Create.newSynapse.open()
Visualize.mGraph.fx.animate({
modes: ['node-property:dim'],
duration: 500,
onComplete: function() {
JIT.tempNode = null
JIT.tempNode2 = null
JIT.tempInit = false
}
})
} else {
Visualize.mGraph.fx.plotNode(nodeOnViz, Visualize.mGraph.canvas)
Visualize.mGraph.fx.animate({
modes: ['node-property:dim'],
duration: 500,
onComplete: function() {}
})
}
} else {
Visualize.mGraph.loadJSON(newnode)
nodeOnViz = Visualize.mGraph.graph.getNode(newnode.id)
topic.set('node', nodeOnViz, {silent: true})
topic.updateNode() // links the topic and the mapping to the node
nodeOnViz.setData('dim', 1, 'start')
nodeOnViz.setData('dim', 25, 'end')
nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'current')
nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'start')
nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'end')
Visualize.mGraph.fx.plotNode(nodeOnViz, Visualize.mGraph.canvas)
Visualize.mGraph.fx.animate({
modes: ['node-property:dim'],
duration: 500,
onComplete: function() {}
}
nodeOnViz = Visualize.mGraph.graph.getNode(newnode.id)
topic.set('node', nodeOnViz, {silent: true})
topic.updateNode() // links the topic and the mapping to the node
nodeOnViz.setData('dim', 1, 'start')
nodeOnViz.setData('dim', 25, 'end')
nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'current')
nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'start')
nodeOnViz.setPos(new $jit.Complex(mapping.get('xloc'), mapping.get('yloc')), 'end')
Visualize.mGraph.fx.plotNode(nodeOnViz, Visualize.mGraph.canvas)
Visualize.mGraph.fx.animate({
modes: ['node-property:dim'],
duration: 200
})
if (!fromRemote && topic.isNew()) {
topic.save(null, {
success: topicModel => {
Active.Map && mapping.save({ mappable_id: topicModel.id })
createSynapse && Synapse.createSynapseLocally(connectToId, topicModel.id)
}
})
}
var mappingSuccessCallback = function(mappingModel, response, topicModel) {
// call a success callback if provided
if (opts.success) {
opts.success(topicModel)
}
}
var topicSuccessCallback = function(topicModel, response) {
if (Active.Map) {
mapping.save({ mappable_id: topicModel.id }, {
success: function(model, response) {
mappingSuccessCallback(model, response, topicModel)
},
error: function(model, response) {
console.log('error saving mapping to database')
}
})
}
if (Create.newTopic.addSynapse) {
Create.newSynapse.topic2id = topicModel.id
}
}
if (!Settings.sandbox && createNewInDB) {
if (topic.isNew()) {
topic.save(null, {
success: topicSuccessCallback,
error: function(model, response) {
console.log('error saving topic to database')
}
})
} else if (!topic.isNew() && Active.Map) {
mapping.save(null, {
success: mappingSuccessCallback
})
}
} else if (!fromRemote && !topic.isNew()) {
Active.Map && mapping.save()
createSynapse && Synapse.createSynapseLocally(connectToId, topic.id)
}
},
createTopicLocally: function() {
var self = Topic
if (Create.newTopic.name === '') {
GlobalUI.notifyUser('Please enter a topic title...')
return
}
// hide the 'double-click to add a topic' message
GlobalUI.hideDiv('#instructions')
$(document).trigger(Map.events.editedByActiveMapper)
var metacode = DataModel.Metacodes.get(Create.newTopic.metacode)
var topic = new DataModel.Topic({
name: Create.newTopic.name,
metacode_id: metacode.id,
defer_to_map_id: Active.Map.id
})
DataModel.Topics.add(topic)
if (Create.newTopic.pinned) {
var nextCoords = AutoLayout.getNextCoord({ mappings: DataModel.Mappings })
}
var mapping = new DataModel.Mapping({
xloc: nextCoords ? nextCoords.x : Create.newTopic.x,
yloc: nextCoords ? nextCoords.y : Create.newTopic.y,
xloc: Mouse.newNodeCoords.x,
yloc: Mouse.newNodeCoords.y,
mappable_id: topic.cid,
mappable_type: 'Topic'
})
DataModel.Mappings.add(mapping)
// these can't happen until the value is retrieved, which happens in the line above
if (!Create.newTopic.pinned) Create.newTopic.hide()
// these can't happen until the new topic values are retrieved
Create.newTopic.reset()
self.renderTopic(mapping, topic, true, true) // this function also includes the creation of the topic in the database
self.renderTopic(mapping, topic)
Engine.setFocusNode(topic.get('node'), false, true)
},
getTopicFromAutocomplete: function(id) {
var self = Topic
// hide the 'double-click to add a topic' message
GlobalUI.hideDiv('#instructions')
$(document).trigger(Map.events.editedByActiveMapper)
if (!Create.newTopic.pinned) Create.newTopic.hide()
Create.newTopic.reset()
self.get(id, (topic) => {
if (Create.newTopic.pinned) {
var nextCoords = AutoLayout.getNextCoord({ mappings: DataModel.Mappings })
}
var mapping = new DataModel.Mapping({
xloc: nextCoords ? nextCoords.x : Create.newTopic.x,
yloc: nextCoords ? nextCoords.y : Create.newTopic.y,
xloc: Mouse.newNodeCoords.x,
yloc: Mouse.newNodeCoords.y,
mappable_type: 'Topic',
mappable_id: topic.id
})
DataModel.Mappings.add(mapping)
self.renderTopic(mapping, topic, true, true)
// this blocked the enterKeyHandler from creating a new topic as well
if (Create.newTopic.pinned) Create.newTopic.beingCreated = true
self.renderTopic(mapping, topic)
Engine.setFocusNode(topic.get('node'), false, true)
})
},
getMapFromAutocomplete: function(data) {
var self = Topic
$(document).trigger(Map.events.editedByActiveMapper)
var metacode = DataModel.Metacodes.findWhere({ name: 'Metamap' })
var topic = new DataModel.Topic({
name: data.name,
@ -359,28 +256,20 @@ const Topic = {
link: window.location.origin + '/maps/' + data.id
})
DataModel.Topics.add(topic)
var mapping = new DataModel.Mapping({
xloc: Create.newTopic.x,
yloc: Create.newTopic.y,
xloc: Mouse.newNodeCoords.x,
yloc: Mouse.newNodeCoords.y,
mappable_id: topic.cid,
mappable_type: 'Topic'
})
DataModel.Mappings.add(mapping)
// these can't happen until the value is retrieved, which happens in the line above
if (!Create.newTopic.pinned) Create.newTopic.hide()
Create.newTopic.reset()
self.renderTopic(mapping, topic, true, true) // this function also includes the creation of the topic in the database
// this blocked the enterKeyHandler from creating a new topic as well
if (Create.newTopic.pinned) Create.newTopic.beingCreated = true
self.renderTopic(mapping, topic)
Engine.setFocusNode(topic.get('node'), false, true)
},
getTopicFromSearch: function(event, id) {
var self = Topic
$(document).trigger(Map.events.editedByActiveMapper)
self.get(id, (topic) => {
var nextCoords = AutoLayout.getNextCoord({ mappings: DataModel.Mappings })
var mapping = new DataModel.Mapping({
@ -390,10 +279,10 @@ const Topic = {
mappable_id: topic.id
})
DataModel.Mappings.add(mapping)
self.renderTopic(mapping, topic, true, true)
GlobalUI.notifyUser('Topic was added to your map!')
self.renderTopic(mapping, topic)
Engine.runLayout()
GlobalUI.notifyUser('Topic was added to your map')
})
event.stopPropagation()
event.preventDefault()
return false

View file

@ -6,6 +6,7 @@ import $jit from '../patched/JIT'
import Active from './Active'
import DataModel from './DataModel'
import Engine from './Engine'
import JIT from './JIT'
import Loading from './Loading'
import Router from './Router'
@ -94,10 +95,14 @@ const Visualize = {
}
})
const startPos = new $jit.Complex(0, 0)
const endPos = new $jit.Complex(mapping.get('xloc'), mapping.get('yloc'))
n.setPos(startPos, 'start')
//const startPos = new $jit.Complex(0, 0)
const endPos = new $jit.Complex(0, 0)
//n.setPos(startPos, 'start')
//n.setPos(endPos, 'end')
n.setPos(endPos, 'current')
n.setPos(endPos, 'end')
n.setData('dim', 1, 'start')
n.setData('dim', 25, 'end')
})
} else if (self.type === 'ForceDirected3D') {
self.mGraph.compute()
@ -151,6 +156,8 @@ const Visualize = {
function runAnimation() {
Loading.hide()
$('#new_topic').show()
$('#topic_name').focus()
// load JSON data, if it's not empty
if (!self.loadLater) {
// load JSON data.
@ -168,7 +175,8 @@ const Visualize = {
if (self.type === 'RGraph') {
self.mGraph.fx.animate(JIT.RGraph.animate)
} else if (self.type === 'ForceDirected') {
self.mGraph.animate(JIT.ForceDirected.animateSavedLayout)
self.mGraph.plot()
Engine.run(true)
} else if (self.type === 'ForceDirected3D') {
self.mGraph.animate(JIT.ForceDirected.animateFDLayout)
}
@ -204,6 +212,8 @@ const Visualize = {
Router.timeoutId = setTimeout(function() {
var m = Active.Map
var t = Active.Topic
if (m && window.location.pathname === '/maps/' + m.id + '/conversation') return
if (m && window.location.pathname !== '/maps/' + m.id) {
Router.navigateAndTrack('/maps/' + m.id)

View file

@ -7,6 +7,7 @@ import Control from './Control'
import Create from './Create'
import DataModel from './DataModel'
import Debug from './Debug'
import Engine from './Engine'
import Filter from './Filter'
import GlobalUI, {
Search, CreateMap, ImportDialog, Account as GlobalUIAccount,
@ -44,6 +45,7 @@ Metamaps.Control = Control
Metamaps.Create = Create
Metamaps.DataModel = DataModel
Metamaps.Debug = Debug
Metamaps.Engine = Engine
Metamaps.Filter = Filter
Metamaps.GlobalUI = GlobalUI
Metamaps.GlobalUI.Search = Search

View file

@ -0,0 +1,125 @@
/* global $ */
import React, { Component, PropTypes } from 'react'
const ENTER_KEY = 13
const LEFT_ARROW = 37
const UP_ARROW = 38
const RIGHT_ARROW = 39
const DOWN_ARROW = 40
const Metacode = (props) => {
const { m, onClick, underCursor } = props
return (
<li onClick={() => onClick(m.id) } className={ underCursor ? 'keySelect' : '' }>
<img src={ m.get('icon') } />
<span>{ m.get('name') }</span>
</li>
)
}
class MetacodeSelect extends Component {
constructor (props) {
super(props)
this.state = {
filterText: '',
selectingSection: true,
underCursor: 0
}
}
componentDidMount() {
const self = this
setTimeout(function() {
$(document.body).on('keyup.metacodeSelect', self.handleKeyUp.bind(self))
}, 10)
}
componentWillUnmount() {
$(document.body).off('.metacodeSelect')
}
changeFilterText (e) {
this.setState({ filterText: e.target.value, underCursor: 0 })
}
getSelectMetacodes () {
const { metacodes, recent, mostUsed } = this.props
const { filterText, activeTab } = this.state
let selectMetacodes = metacodes
if (filterText.length > 1) { // search
selectMetacodes = filterText.length > 1 ? metacodes.filter(m => {
return m.get('name').toLowerCase().search(filterText.toLowerCase()) > -1
}) : []
}
return selectMetacodes
}
handleKeyUp (e) {
const { close } = this.props
const { underCursor } = this.state
const selectMetacodes = this.getSelectMetacodes()
let nextIndex
switch (e.which) {
case ENTER_KEY:
if (selectMetacodes.length) this.resetAndClick(selectMetacodes[underCursor].id)
break
case UP_ARROW:
if (underCursor == 0) {
close()
break
}
nextIndex = underCursor == 0 ? selectMetacodes.length - 1 : underCursor - 1
this.setState({ underCursor: nextIndex })
break
case DOWN_ARROW:
nextIndex = underCursor == selectMetacodes.length - 1 ? 0 : underCursor + 1
this.setState({ underCursor: nextIndex })
break
}
}
resetAndClick (id) {
const { onClick } = this.props
this.setState({ filterText: '', underCursor: 0 })
onClick(id)
}
render () {
const { onClick, close } = this.props
const { filterText, underCursor } = this.state
const selectMetacodes = this.getSelectMetacodes()
return <div className='metacodeSelect'>
<div className='tabList'>
<input type='text'
className='metacodeFilterInput'
placeholder='Search...'
ref='input'
value={ filterText }
onChange={ this.changeFilterText.bind(this) } />
<ul className='metacodeList'>
{ selectMetacodes.map((m, index) => {
return <Metacode underCursor={underCursor == index}
key={m.id}
m={m}
onClick={this.resetAndClick.bind(this)} />
})}
</ul>
<div className='clearfloat'></div>
</div>
</div>
}
}
MetacodeSelect.propTypes = {
onClick: PropTypes.func.isRequired,
close: PropTypes.func.isRequired,
metacodes: PropTypes.array.isRequired,
}
export default MetacodeSelect

View file

@ -2560,7 +2560,11 @@ Extras.Classes.Navigation = new Class({
}
if (Metamaps.Mouse.boxStartCoordinates && ((e.button == 0 && e.shiftKey) || (e.button == 0 && e.ctrlKey) || rightClick)) {
Metamaps.Visualize.mGraph.busy = true;
Metamaps.JIT.drawSelectBox(eventInfo,e);
Metamaps.Mouse.boxEndCoordinates = {
x: eventInfo.getPos().x,
y: eventInfo.getPos().y
}
Metamaps.Visualize.mGraph.plot()
//console.log('mouse move');
return;
}
@ -2606,9 +2610,7 @@ Extras.Classes.Navigation = new Class({
this.pressed = false;
// START METAMAPS CODE
if (Metamaps.Mouse.didPan) Metamaps.JIT.SmoothPanning();
if (Metamaps.Mouse.didPan) Metamaps.JIT.SmoothPanning();
// END METAMAPS CODE
},
@ -2651,7 +2653,10 @@ Extras.Classes.Navigation = new Class({
}
if (Metamaps.Mouse.boxStartCoordinates && ((e.button == 0 && e.shiftKey) || (e.button == 0 && e.ctrlKey) || rightClick)) {
Metamaps.Visualize.mGraph.busy = true;
Metamaps.JIT.drawSelectBox(eventInfo,e);
Metamaps.Mouse.boxEndCoordinates = {
x: eventInfo.getPos().x,
y: eventInfo.getPos().y
}
return;
}
if (rightClick){
@ -7225,7 +7230,7 @@ Graph.Plot = {
var T = !!root.visited;
//START METAMAPS CODE
if (Metamaps.Mouse.synapseStartCoordinates.length > 0) {
if (Metamaps.Mouse.synapseStartCoordinates.length > 0 && Metamaps.Mouse.synapseEndCoordinates) {
ctx.save();
var start;
var end = Metamaps.Mouse.synapseEndCoordinates;
@ -7238,6 +7243,26 @@ Graph.Plot = {
}
ctx.restore();
}
if (Metamaps.Mouse.focusNodeCoords) {
ctx.save();
Metamaps.JIT.renderMidArrow(Metamaps.Mouse.focusNodeCoords, Metamaps.Mouse.newNodeCoords, 13, false, canvas, 0.3, true);
Metamaps.JIT.renderMidArrow(Metamaps.Mouse.focusNodeCoords, Metamaps.Mouse.newNodeCoords, 13, false, canvas, 0.7, true);
ctx.restore();
}
if (Metamaps.Mouse.boxStartCoordinates && Metamaps.Mouse.boxEndCoordinates) {
ctx.save();
ctx.beginPath()
ctx.moveTo(Metamaps.Mouse.boxStartCoordinates.x, Metamaps.Mouse.boxStartCoordinates.y)
ctx.lineTo(Metamaps.Mouse.boxStartCoordinates.x, Metamaps.Mouse.boxEndCoordinates.y)
ctx.lineTo(Metamaps.Mouse.boxEndCoordinates.x, Metamaps.Mouse.boxEndCoordinates.y)
ctx.lineTo(Metamaps.Mouse.boxEndCoordinates.x, Metamaps.Mouse.boxStartCoordinates.y)
ctx.lineTo(Metamaps.Mouse.boxStartCoordinates.x, Metamaps.Mouse.boxStartCoordinates.y)
ctx.strokeStyle = 'black'
ctx.stroke()
ctx.restore()
}
//END METAMAPS CODE
aGraph.eachNode(function(node) {

View file

@ -40,10 +40,10 @@ module.exports = {
]
},
entry: {
'metamaps.bundle': './frontend/src/index.js'
'metamaps.secret.bundle': './frontend/src/index.js'
},
output: {
path: './app/assets/javascripts/webpacked',
path: './app/assets/javascripts',
filename: '[name].js',
devtoolModuleFilenameTemplate: '[absolute-resource-path]'
}