Compare commits
38 commits
develop
...
feature/co
Author | SHA1 | Date | |
---|---|---|---|
|
f49a674566 | ||
|
7951f08e45 | ||
|
9c65d2df09 | ||
|
a84815aaed | ||
|
9b0ead24a3 | ||
|
3a4fa90c49 | ||
|
0a52de714e | ||
|
597efb36e1 | ||
|
72fd2717b6 | ||
|
500a74bd5f | ||
|
2423608fd3 | ||
|
0929380e91 | ||
|
6e347dc33a | ||
|
96f66a2f8c | ||
|
74dd20f02e | ||
|
04036882ab | ||
|
fd54eb718a | ||
|
ba230e1eed | ||
|
274b86532a | ||
|
6ed9796e05 | ||
|
08d2cbb00d | ||
|
fc2604376a | ||
|
e7a52dc14e | ||
|
149b7ecbd6 | ||
|
b2b5090b28 | ||
|
38c01c4e8f | ||
|
b13ac98c9f | ||
|
4a17d00123 | ||
|
9c13f5a281 | ||
|
616a489ae4 | ||
|
816815d1b5 | ||
|
3ff102b228 | ||
|
bf9b25da9f | ||
|
5ba7ba9355 | ||
|
ce7c88c78c | ||
|
74630c2631 | ||
|
966dd79187 | ||
|
5d04d16590 |
23 changed files with 812 additions and 580 deletions
|
@ -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?
|
||||
|
|
58
frontend/src/ConvoAlgo/exampleObject.js
Normal file
58
frontend/src/ConvoAlgo/exampleObject.js
Normal 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: []
|
||||
},
|
||||
]
|
||||
*/
|
223
frontend/src/ConvoAlgo/index.js
Normal file
223
frontend/src/ConvoAlgo/index.js
Normal 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)
|
||||
}
|
|
@ -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 => {
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Active from '../Active'
|
||||
import Engine from '../Engine'
|
||||
import Filter from '../Filter'
|
||||
import { InfoBox } from '../Map'
|
||||
|
||||
|
|
68
frontend/src/Metamaps/Engine.js
Normal file
68
frontend/src/Metamaps/Engine.js
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
125
frontend/src/components/MetacodeSelect.js
Normal file
125
frontend/src/components/MetacodeSelect.js
Normal 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
|
||||
|
|
@ -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) {
|
||||
|
|
|
@ -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]'
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue