diff --git a/app/policies/map_policy.rb b/app/policies/map_policy.rb index 6325b256..09ce5cca 100644 --- a/app/policies/map_policy.rb +++ b/app/policies/map_policy.rb @@ -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? diff --git a/frontend/src/ConvoAlgo/exampleObject.js b/frontend/src/ConvoAlgo/exampleObject.js new file mode 100644 index 00000000..0f84380b --- /dev/null +++ b/frontend/src/ConvoAlgo/exampleObject.js @@ -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: [] + }, +] +*/ \ No newline at end of file diff --git a/frontend/src/ConvoAlgo/index.js b/frontend/src/ConvoAlgo/index.js new file mode 100644 index 00000000..f5ebbe46 --- /dev/null +++ b/frontend/src/ConvoAlgo/index.js @@ -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) +} \ No newline at end of file diff --git a/frontend/src/Metamaps/Cable.js b/frontend/src/Metamaps/Cable.js index a5809df5..d8b7e488 100644 --- a/frontend/src/Metamaps/Cable.js +++ b/frontend/src/Metamaps/Cable.js @@ -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 => { diff --git a/frontend/src/Metamaps/Control.js b/frontend/src/Metamaps/Control.js index 4699a6d5..e5c0c92d 100644 --- a/frontend/src/Metamaps/Control.js +++ b/frontend/src/Metamaps/Control.js @@ -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' diff --git a/frontend/src/Metamaps/Create.js b/frontend/src/Metamaps/Create.js index 466900e5..3b4c6e34 100644 --- a/frontend/src/Metamaps/Create.js +++ b/frontend/src/Metamaps/Create.js @@ -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') + } } } } diff --git a/frontend/src/Metamaps/DataModel/Mapping.js b/frontend/src/Metamaps/DataModel/Mapping.js index 2cd2b0b8..2987a2a5 100644 --- a/frontend/src/Metamaps/DataModel/Mapping.js +++ b/frontend/src/Metamaps/DataModel/Mapping.js @@ -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) + } } }) diff --git a/frontend/src/Metamaps/DataModel/Synapse.js b/frontend/src/Metamaps/DataModel/Synapse.js index e5cb0ae8..32f337c2 100644 --- a/frontend/src/Metamaps/DataModel/Synapse.js +++ b/frontend/src/Metamaps/DataModel/Synapse.js @@ -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) } diff --git a/frontend/src/Metamaps/DataModel/Topic.js b/frontend/src/Metamaps/DataModel/Topic.js index 8eb09fdf..8011160b 100644 --- a/frontend/src/Metamaps/DataModel/Topic.js +++ b/frontend/src/Metamaps/DataModel/Topic.js @@ -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' diff --git a/frontend/src/Metamaps/DataModel/index.js b/frontend/src/Metamaps/DataModel/index.js index 272f1717..e68db57a 100644 --- a/frontend/src/Metamaps/DataModel/index.js +++ b/frontend/src/Metamaps/DataModel/index.js @@ -1,4 +1,5 @@ import Active from '../Active' +import Engine from '../Engine' import Filter from '../Filter' import { InfoBox } from '../Map' diff --git a/frontend/src/Metamaps/Engine.js b/frontend/src/Metamaps/Engine.js new file mode 100644 index 00000000..dde2e022 --- /dev/null +++ b/frontend/src/Metamaps/Engine.js @@ -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 diff --git a/frontend/src/Metamaps/JIT.js b/frontend/src/Metamaps/JIT.js index d91a2a93..d75cb3f9 100644 --- a/frontend/src/Metamaps/JIT.js +++ b/frontend/src/Metamaps/JIT.js @@ -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) diff --git a/frontend/src/Metamaps/Listeners.js b/frontend/src/Metamaps/Listeners.js index c3b644df..dbebfce8 100644 --- a/frontend/src/Metamaps/Listeners.js +++ b/frontend/src/Metamaps/Listeners.js @@ -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() diff --git a/frontend/src/Metamaps/Map/index.js b/frontend/src/Metamaps/Map/index.js index 3df5a3e2..e835d42f 100644 --- a/frontend/src/Metamaps/Map/index.js +++ b/frontend/src/Metamaps/Map/index.js @@ -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') } }, diff --git a/frontend/src/Metamaps/Mouse.js b/frontend/src/Metamaps/Mouse.js index 9989bc20..2aac0135 100644 --- a/frontend/src/Metamaps/Mouse.js +++ b/frontend/src/Metamaps/Mouse.js @@ -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 diff --git a/frontend/src/Metamaps/Realtime/index.js b/frontend/src/Metamaps/Realtime/index.js index b73cf52c..90d1db67 100644 --- a/frontend/src/Metamaps/Realtime/index.js +++ b/frontend/src/Metamaps/Realtime/index.js @@ -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, diff --git a/frontend/src/Metamaps/Synapse.js b/frontend/src/Metamaps/Synapse.js index 48791637..81735815 100644 --- a/frontend/src/Metamaps/Synapse.js +++ b/frontend/src/Metamaps/Synapse.js @@ -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() }) } } diff --git a/frontend/src/Metamaps/Topic.js b/frontend/src/Metamaps/Topic.js index 7cdcf3a7..7d5f1644 100644 --- a/frontend/src/Metamaps/Topic.js +++ b/frontend/src/Metamaps/Topic.js @@ -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 diff --git a/frontend/src/Metamaps/Visualize.js b/frontend/src/Metamaps/Visualize.js index 2ccb08ed..d0df3ccd 100644 --- a/frontend/src/Metamaps/Visualize.js +++ b/frontend/src/Metamaps/Visualize.js @@ -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) diff --git a/frontend/src/Metamaps/index.js b/frontend/src/Metamaps/index.js index 61f5e18a..a0ea5683 100644 --- a/frontend/src/Metamaps/index.js +++ b/frontend/src/Metamaps/index.js @@ -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 diff --git a/frontend/src/components/MetacodeSelect.js b/frontend/src/components/MetacodeSelect.js new file mode 100644 index 00000000..4891876f --- /dev/null +++ b/frontend/src/components/MetacodeSelect.js @@ -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 ( +
  • onClick(m.id) } className={ underCursor ? 'keySelect' : '' }> + + { m.get('name') } +
  • + ) +} + +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
    +
    + + +
    +
    +
    + } +} + +MetacodeSelect.propTypes = { + onClick: PropTypes.func.isRequired, + close: PropTypes.func.isRequired, + metacodes: PropTypes.array.isRequired, +} + +export default MetacodeSelect + diff --git a/frontend/src/patched/JIT.js b/frontend/src/patched/JIT.js index e780604e..4675d4d9 100644 --- a/frontend/src/patched/JIT.js +++ b/frontend/src/patched/JIT.js @@ -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) { diff --git a/webpack.config.js b/webpack.config.js index 13cf6a65..f468b1d1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -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]' }