diff --git a/frontend/src/Metamaps/GlobalUI/ReactApp.js b/frontend/src/Metamaps/GlobalUI/ReactApp.js
index db850091..5cfbf255 100644
--- a/frontend/src/Metamaps/GlobalUI/ReactApp.js
+++ b/frontend/src/Metamaps/GlobalUI/ReactApp.js
@@ -4,12 +4,13 @@ import React from 'react'
import ReactDOM from 'react-dom'
import { Router, browserHistory } from 'react-router'
import { merge } from 'lodash'
+import apply from 'async/apply'
import { notifyUser } from './index.js'
import ImportDialog from './ImportDialog'
import Active from '../Active'
import DataModel from '../DataModel'
-import { ExploreMaps, ChatView, TopicCard } from '../Views'
+import { ExploreMaps, ChatView, TopicCard, ContextMenu } from '../Views'
import Filter from '../Filter'
import JIT from '../JIT'
import Realtime from '../Realtime'
@@ -113,6 +114,7 @@ const ReactApp = {
self.getFilterProps(),
self.getCommonProps(),
self.getMapsProps(),
+ self.getContextMenuProps(),
self.getTopicCardProps(),
self.getChatProps())
},
@@ -155,6 +157,28 @@ const ReactApp = {
onTopicFollow: Topic.onTopicFollow
}
},
+ getContextMenuProps: function() {
+ const { render } = ReactApp
+ return {
+ // values
+ contextMenu: !!(ContextMenu.clickedNode || ContextMenu.clickedEdge),
+ contextNode: ContextMenu.clickedNode,
+ contextEdge: ContextMenu.clickedEdge,
+ contextPos: ContextMenu.pos,
+ contextFetchingSiblingsData: ContextMenu.fetchingSiblingsData,
+ contextSiblingsData: ContextMenu.siblingsData,
+ // functions
+ contextDelete: apply(ContextMenu.delete, render),
+ contextRemove: apply(ContextMenu.remove, render),
+ contextHide: apply(ContextMenu.hide, render),
+ contextCenterOn: apply(ContextMenu.centerOn, render),
+ contextPopoutTopic: apply(ContextMenu.popoutTopic, render),
+ contextUpdatePermissions: apply(ContextMenu.updatePermissions, render),
+ contextOnMetacodeSelect: apply(ContextMenu.onMetacodeSelect, render),
+ contextFetchSiblings: apply(ContextMenu.fetchSiblings, render),
+ contextPopulateSiblings: apply(ContextMenu.populateSiblings, render)
+ }
+ },
getTopicProps: function() {
const self = ReactApp
return {
diff --git a/frontend/src/Metamaps/JIT.js b/frontend/src/Metamaps/JIT.js
index ffaab923..b0aee7ea 100644
--- a/frontend/src/Metamaps/JIT.js
+++ b/frontend/src/Metamaps/JIT.js
@@ -1,16 +1,12 @@
-/* global $, Image, CanvasLoader */
+/* global $, Image */
import _ from 'lodash'
-import outdent from 'outdent'
import clipboard from 'clipboard-js'
-import React from 'react'
-import ReactDOM from 'react-dom'
import $jit from '../patched/JIT'
-import MetacodeSelect from '../components/MetacodeSelect'
-
import Active from './Active'
+import ContextMenu from './Views/ContextMenu'
import Control from './Control'
import Create from './Create'
import DataModel from './DataModel'
@@ -349,7 +345,7 @@ const JIT = {
// Add also a click handler to nodes
onClick: function(node, eventInfo, e) {
// remove the rightclickmenu
- $('.rightclickmenu').remove()
+ ContextMenu.reset(ReactApp.render)
if (Mouse.boxStartCoordinates) {
if (e.ctrlKey) {
@@ -390,7 +386,7 @@ const JIT = {
// Add also a click handler to nodes
onRightClick: function(node, eventInfo, e) {
// remove the rightclickmenu
- $('.rightclickmenu').remove()
+ ContextMenu.reset(ReactApp.render)
if (Mouse.boxStartCoordinates) {
Create.newSynapse.hide()
@@ -1006,7 +1002,7 @@ const JIT = {
TopicCard.hideCard()
SynapseCard.hideCard()
Create.newTopic.hide()
- $('.rightclickmenu').remove()
+ ContextMenu.reset(ReactApp.render)
// reset the draw synapse positions to false
Mouse.synapseStartCoordinates = []
Mouse.synapseEndCoordinates = null
@@ -1346,230 +1342,12 @@ const JIT = {
selectNodeOnRightClickHandler: function(node, e) {
// the 'node' variable is a JIT node, the one that was clicked on
// the 'e' variable is the click event
-
e.preventDefault()
e.stopPropagation()
-
if (Visualize.mGraph.busy) return
-
- // select the node
Control.selectNode(node, e)
-
- // delete old right click menu
- $('.rightclickmenu').remove()
- // create new menu for clicked on node
- const rightclickmenu = document.createElement('div')
- rightclickmenu.className = 'rightclickmenu'
- // prevent the custom context menu from immediately opening the default context menu as well
- rightclickmenu.setAttribute('oncontextmenu', 'return false')
-
- // add the proper options to the menu
- let menustring = '
'
-
- const authorized = Active.Map && Active.Map.authorizeToEdit(Active.Mapper)
-
- const disabled = authorized ? '' : 'disabled'
-
- if (Active.Map) menustring += '- Hide until refresh
Ctrl+H
'
- if (Active.Map && Active.Mapper) menustring += '- Remove from map
Ctrl+M
'
- if (Active.Topic) menustring += '- Remove from view
Ctrl+M
'
- if (Active.Map && Active.Mapper) menustring += '- Delete
Ctrl+D
'
-
- if (Active.Topic) {
- menustring += '- Center this topic
Alt+E
'
- }
-
- menustring += '- Open in new tab
'
-
- if (Active.Mapper) {
- const options = outdent`
-
- - commons
- - public
- - private
-
`
-
- menustring += ''
-
- menustring += outdent`
- -
-
- Change permissions
- ${options}
-
-
`
-
- menustring += '- Change metacode
'
- }
- if (Active.Topic) {
- if (!Active.Mapper) {
- menustring += ''
- }
-
- // set up the get sibling menu as a "lazy load"
- // only fill in the submenu when they hover over the get siblings list item
- const siblingMenu = outdent`
- `
- menustring += '- Reveal siblings' + siblingMenu + '
'
- }
-
- menustring += '
'
- rightclickmenu.innerHTML = menustring
-
- // position the menu where the click happened
- const position = {}
- const RIGHTCLICK_WIDTH = 300
- const RIGHTCLICK_HEIGHT = 144 // this does vary somewhat, but we can use static
- const SUBMENUS_WIDTH = 256
- const MAX_SUBMENU_HEIGHT = 270
- const windowWidth = $(window).width()
- const windowHeight = $(window).height()
-
- if (windowWidth - e.clientX < SUBMENUS_WIDTH) {
- position.right = windowWidth - e.clientX
- $(rightclickmenu).addClass('moveMenusToLeft')
- } else if (windowWidth - e.clientX < RIGHTCLICK_WIDTH) {
- position.right = windowWidth - e.clientX
- } else if (windowWidth - e.clientX < RIGHTCLICK_WIDTH + SUBMENUS_WIDTH) {
- position.left = e.clientX
- $(rightclickmenu).addClass('moveMenusToLeft')
- } else {
- position.left = e.clientX
- }
-
- if (windowHeight - e.clientY < MAX_SUBMENU_HEIGHT) {
- position.bottom = windowHeight - e.clientY
- $(rightclickmenu).addClass('moveMenusUp')
- } else if (windowHeight - e.clientY < RIGHTCLICK_HEIGHT + MAX_SUBMENU_HEIGHT) {
- position.top = e.clientY
- $(rightclickmenu).addClass('moveMenusUp')
- } else {
- position.top = e.clientY
- }
-
- $(rightclickmenu).css(position)
- // add the menu to the page
- $('#wrapper').append(rightclickmenu)
-
- ReactDOM.render(
- React.createElement(MetacodeSelect, {
- onMetacodeSelect: metacodeId => {
- if (Selected.Nodes.length > 1) {
- // batch update multiple topics
- Control.updateSelectedMetacodes(metacodeId)
- } else {
- const topic = DataModel.Topics.get(node.id)
- topic.save({
- metacode_id: metacodeId
- })
- }
- $(rightclickmenu).remove()
- },
- metacodeSets: ReactApp.metacodeSets
- }),
- document.getElementById('metacodeOptionsWrapper')
- )
-
- // attach events to clicks on the list items
-
- // delete the selected things from the database
- if (authorized) {
- $('.rc-delete').click(function() {
- $('.rightclickmenu').remove()
- Control.deleteSelected()
- })
- }
-
- // remove the selected things from the map
- if (Active.Topic || authorized) {
- $('.rc-remove').click(function() {
- $('.rightclickmenu').remove()
- Control.removeSelectedEdges()
- Control.removeSelectedNodes()
- })
- }
-
- // hide selected nodes and synapses until refresh
- $('.rc-hide').click(function() {
- $('.rightclickmenu').remove()
- Control.hideSelectedEdges()
- Control.hideSelectedNodes()
- })
-
- // when in radial, center on the topic you picked
- $('.rc-center').click(function() {
- $('.rightclickmenu').remove()
- Topic.centerOn(node.id)
- })
-
- // open the entity in a new tab
- $('.rc-popout').click(function() {
- $('.rightclickmenu').remove()
- const win = window.open('/topics/' + node.id, '_blank')
- win.focus()
- })
-
- // change the permission of all the selected nodes and synapses that you were the originator of
- $('.rc-permission li').click(function() {
- $('.rightclickmenu').remove()
- // $(this).text() will be 'commons' 'public' or 'private'
- Control.updateSelectedPermissions($(this).text())
- })
-
- // fetch relatives
- let fetchSent = false
- $('.rc-siblings').hover(function() {
- if (!fetchSent) {
- JIT.populateRightClickSiblings(node)
- fetchSent = true
- }
- })
- $('.rc-siblings .fetchAll').click(function() {
- $('.rightclickmenu').remove()
- // data-id is a metacode id
- Topic.fetchRelatives(node)
- })
+ ContextMenu.selectNode(ReactApp.render, node, {x: e.clientX, y: e.clientY})
}, // selectNodeOnRightClickHandler,
- populateRightClickSiblings: function(node) {
- // depending on how many topics are selected, do different things
- const topic = node.getData('topic')
-
- // add a loading icon for now
- const loader = new CanvasLoader('loadingSiblings')
- loader.setColor('#4FC059') // default is '#000000'
- loader.setDiameter(15) // default is 40
- loader.setDensity(41) // default is 40
- loader.setRange(0.9) // default is 1.3
- loader.show() // Hidden by default
-
- const topics = DataModel.Topics.map(function(t) { return t.id })
- const topicsString = topics.join()
-
- const successCallback = function(data) {
- $('#loadingSiblings').remove()
-
- for (var key in data) {
- const string = `${DataModel.Metacodes.get(key).get('name')} (${data[key]})`
- $('#fetchSiblingList').append(`${string}`)
- }
-
- $('.rc-siblings .getSiblings').click(function() {
- $('.rightclickmenu').remove()
- // data-id is a metacode id
- Topic.fetchRelatives(node, $(this).attr('data-id'))
- })
- }
-
- $.ajax({
- type: 'GET',
- url: '/topics/' + topic.id + '/relative_numbers.json?network=' + topicsString,
- success: successCallback,
- error: function() {}
- })
- },
selectEdgeOnClickHandler: function(adj, e) {
if (Visualize.mGraph.busy) return
@@ -1611,113 +1389,14 @@ const JIT = {
}
}, // selectEdgeOnClickHandler
selectEdgeOnRightClickHandler: function(adj, e) {
- // the 'node' variable is a JIT node, the one that was clicked on
+ // the 'adj' variable is a JIT adjacency, the one that was clicked on
// the 'e' variable is the click event
-
if (adj.getData('alpha') === 0) return // don't do anything if the edge is filtered
-
e.preventDefault()
e.stopPropagation()
-
if (Visualize.mGraph.busy) return
-
Control.selectEdge(adj)
-
- // delete old right click menu
- $('.rightclickmenu').remove()
- // create new menu for clicked on node
- const rightclickmenu = document.createElement('div')
- rightclickmenu.className = 'rightclickmenu'
- // prevent the custom context menu from immediately opening the default context menu as well
- rightclickmenu.setAttribute('oncontextmenu', 'return false')
-
- // add the proper options to the menu
- let menustring = ''
-
- const authorized = Active.Map && Active.Map.authorizeToEdit(Active.Mapper)
-
- const disabled = authorized ? '' : 'disabled'
-
- if (Active.Map) menustring += '- Hide until refresh
Ctrl+H
'
- if (Active.Map && Active.Mapper) menustring += '- Remove from map
Ctrl+M
'
- if (Active.Topic) menustring += '- Remove from view
Ctrl+M
'
- if (Active.Map && Active.Mapper) menustring += '- Delete
Ctrl+D
'
-
- if (Active.Map && Active.Mapper) menustring += ''
-
- if (Active.Mapper) {
- const permOptions = outdent`
-
- - commons
- - public
- private
`
-
- menustring += '- Change permissions' + permOptions + '
'
- }
-
- menustring += '
'
- rightclickmenu.innerHTML = menustring
-
- // position the menu where the click happened
- const position = {}
- const RIGHTCLICK_WIDTH = 300
- const RIGHTCLICK_HEIGHT = 144 // this does vary somewhat, but we can use static
- const SUBMENUS_WIDTH = 256
- const MAX_SUBMENU_HEIGHT = 270
- const windowWidth = $(window).width()
- const windowHeight = $(window).height()
-
- if (windowWidth - e.clientX < SUBMENUS_WIDTH) {
- position.right = windowWidth - e.clientX
- $(rightclickmenu).addClass('moveMenusToLeft')
- } else if (windowWidth - e.clientX < RIGHTCLICK_WIDTH) {
- position.right = windowWidth - e.clientX
- } else position.left = e.clientX
-
- if (windowHeight - e.clientY < MAX_SUBMENU_HEIGHT) {
- position.bottom = windowHeight - e.clientY
- $(rightclickmenu).addClass('moveMenusUp')
- } else if (windowHeight - e.clientY < RIGHTCLICK_HEIGHT + MAX_SUBMENU_HEIGHT) {
- position.top = e.clientY
- $(rightclickmenu).addClass('moveMenusUp')
- } else position.top = e.clientY
-
- $(rightclickmenu).css(position)
-
- // add the menu to the page
- $('#wrapper').append(rightclickmenu)
-
- // attach events to clicks on the list items
-
- // delete the selected things from the database
- if (authorized) {
- $('.rc-delete').click(function() {
- $('.rightclickmenu').remove()
- Control.deleteSelected()
- })
- }
-
- // remove the selected things from the map
- if (authorized) {
- $('.rc-remove').click(function() {
- $('.rightclickmenu').remove()
- Control.removeSelectedEdges()
- Control.removeSelectedNodes()
- })
- }
-
- // hide selected nodes and synapses until refresh
- $('.rc-hide').click(function() {
- $('.rightclickmenu').remove()
- Control.hideSelectedEdges()
- Control.hideSelectedNodes()
- })
-
- // change the permission of all the selected nodes and synapses that you were the originator of
- $('.rc-permission li').click(function() {
- $('.rightclickmenu').remove()
- // $(this).text() will be 'commons' 'public' or 'private'
- Control.updateSelectedPermissions($(this).text())
- })
+ ContextMenu.selectEdge(ReactApp.render, adj, {x: e.clientX, y: e.clientY})
}, // selectEdgeOnRightClickHandler
SmoothPanning: function() {
const sx = Visualize.mGraph.canvas.scaleOffsetX
diff --git a/frontend/src/Metamaps/Listeners.js b/frontend/src/Metamaps/Listeners.js
index 352b5f7f..a5dc630f 100644
--- a/frontend/src/Metamaps/Listeners.js
+++ b/frontend/src/Metamaps/Listeners.js
@@ -152,12 +152,12 @@ const Listeners = {
var node = nodes[nodes.length - 1]
if (opts.center && opts.reveal) {
Topic.centerOn(node.id, function() {
- Topic.fetchRelatives(nodes)
+ Topic.fetchSiblings(nodes)
})
} else if (opts.center) {
Topic.centerOn(node.id)
} else if (opts.reveal) {
- Topic.fetchRelatives(nodes)
+ Topic.fetchSiblings(nodes)
}
}
}
diff --git a/frontend/src/Metamaps/Map/index.js b/frontend/src/Metamaps/Map/index.js
index 2db12263..f337d1d0 100644
--- a/frontend/src/Metamaps/Map/index.js
+++ b/frontend/src/Metamaps/Map/index.js
@@ -16,6 +16,7 @@ import Loading from '../Loading'
import Realtime from '../Realtime'
import Selected from '../Selected'
import SynapseCard from '../SynapseCard'
+import ContextMenu from '../Views/ContextMenu'
import TopicCard from '../Views/TopicCard'
import Visualize from '../Visualize'
@@ -137,7 +138,7 @@ const Map = {
if (Active.Map) {
$('.main').removeClass('compressed')
AutoLayout.resetSpiral()
- $('.rightclickmenu').remove()
+ ContextMenu.reset(ReactApp.render)
TopicCard.hideCard()
SynapseCard.hideCard()
Create.newTopic.hide(true) // true means force (and override pinned)
diff --git a/frontend/src/Metamaps/Topic.js b/frontend/src/Metamaps/Topic.js
index 0054ad55..1b1abe88 100644
--- a/frontend/src/Metamaps/Topic.js
+++ b/frontend/src/Metamaps/Topic.js
@@ -15,6 +15,7 @@ import Selected from './Selected'
import Settings from './Settings'
import SynapseCard from './SynapseCard'
import TopicCard from './Views/TopicCard'
+import ContextMenu from './Views/ContextMenu'
import Util from './Util'
import Visualize from './Visualize'
@@ -68,13 +69,13 @@ const Topic = {
},
end: function() {
if (Active.Topic) {
- $('.rightclickmenu').remove()
+ ContextMenu.reset(ReactApp.render)
TopicCard.hideCard()
SynapseCard.hideCard()
}
},
centerOn: function(nodeid, callback) {
- // don't clash with fetchRelatives
+ // don't clash with fetchSiblings
if (!Visualize.mGraph.busy) {
Visualize.mGraph.onClick(nodeid, {
hideLabels: false,
@@ -100,10 +101,10 @@ const Topic = {
}
ReactApp.render()
},
- fetchRelatives: function(nodes, metacodeId) {
+ fetchSiblings: function(nodes, metacodeId) {
var self = this
- var node = $.isArray(nodes) ? nodes[0] : nodes
+ var node = Array.isArray(nodes) ? nodes[0] : nodes
var topics = DataModel.Topics.map(function(t) { return t.id })
var topicsString = topics.join()
@@ -155,8 +156,8 @@ const Topic = {
}
})
})
- if ($.isArray(nodes) && nodes.length > 1) {
- self.fetchRelatives(nodes.slice(1), metacodeId)
+ if (Array.isArray(nodes) && nodes.length > 1) {
+ self.fetchSiblings(nodes.slice(1), metacodeId)
}
}
diff --git a/frontend/src/Metamaps/Views/ContextMenu.js b/frontend/src/Metamaps/Views/ContextMenu.js
new file mode 100644
index 00000000..f1553706
--- /dev/null
+++ b/frontend/src/Metamaps/Views/ContextMenu.js
@@ -0,0 +1,108 @@
+/* global $ */
+import Control from '../Control'
+import DataModel from '../DataModel'
+import Selected from '../Selected'
+import Topic from '../Topic'
+
+const ContextMenu = {
+ clickedNode: null,
+ clickedEdge: null,
+ pos: {x: 0, y: 0},
+ fetchingSiblingsData: false,
+ siblingsData: null,
+ selectNode: (render, node, pos) => {
+ ContextMenu.pos = pos
+ ContextMenu.clickedNode = node
+ ContextMenu.clickedEdge = null
+ ContextMenu.fetchingSiblingsData = false
+ ContextMenu.siblingsData = null
+ render()
+ },
+ selectEdge: (render, edge, pos) => {
+ ContextMenu.pos = pos
+ ContextMenu.clickedNode = null
+ ContextMenu.clickedEdge = edge
+ ContextMenu.fetchingSiblingsData = false
+ ContextMenu.siblingsData = null
+ render()
+ },
+ reset: (render) => {
+ ContextMenu.fetchingSiblingsData = false
+ ContextMenu.siblingsData = null
+ ContextMenu.clickedNode = null
+ ContextMenu.clickedEdge = null
+ render()
+ },
+ delete: (render) => {
+ Control.deleteSelected()
+ ContextMenu.reset(render)
+ },
+ remove: (render) => {
+ Control.removeSelectedEdges()
+ Control.removeSelectedNodes()
+ ContextMenu.reset(render)
+ },
+ hide: (render) => {
+ Control.hideSelectedEdges()
+ Control.hideSelectedNodes()
+ ContextMenu.reset(render)
+ },
+ centerOn: (render, id) => {
+ Topic.centerOn(id)
+ ContextMenu.reset(render)
+ },
+ popoutTopic: (render, id) => {
+ ContextMenu.reset(render)
+ const win = window.open(`/topics/${id}`, '_blank')
+ win.focus()
+ },
+ updatePermissions: (render, permission) => {
+ // will be 'commons' 'public' or 'private'
+ Control.updateSelectedPermissions(permission)
+ ContextMenu.reset(render)
+ },
+ onMetacodeSelect: (render, id, metacodeId) => {
+ if (Selected.Nodes.length > 1) {
+ // batch update multiple topics
+ Control.updateSelectedMetacodes(metacodeId)
+ } else {
+ const topic = DataModel.Topics.get(id)
+ topic.save({
+ metacode_id: metacodeId
+ })
+ }
+ ContextMenu.reset(render)
+ },
+ fetchSiblings: (render, node, metacodeId) => {
+ Topic.fetchSiblings(node, metacodeId)
+ ContextMenu.reset(render)
+ },
+ populateSiblings: (render, id) => {
+ // depending on how many topics are selected, do different things
+ ContextMenu.fetchingSiblingsData = true
+ render()
+
+ const topics = DataModel.Topics.map(function(t) { return t.id })
+ const topicsString = topics.join()
+
+ const successCallback = function(data) {
+ ContextMenu.fetchingSiblingsData = false
+
+ // adjust the data for consumption by react
+ for (var key in data) {
+ data[key] = `${DataModel.Metacodes.get(key).get('name')} (${data[key]})`
+ }
+ ContextMenu.siblingsData = data
+ render()
+ }
+
+ $.ajax({
+ type: 'GET',
+ url: `/topics/${id}/relative_numbers.json?network=${topicsString}`,
+ success: successCallback,
+ error: function() {}
+ })
+ }
+}
+
+export default ContextMenu
diff --git a/frontend/src/Metamaps/Views/index.js b/frontend/src/Metamaps/Views/index.js
index 0f7cf566..de9d5aab 100644
--- a/frontend/src/Metamaps/Views/index.js
+++ b/frontend/src/Metamaps/Views/index.js
@@ -1,5 +1,6 @@
/* global $ */
+import ContextMenu from './ContextMenu'
import ExploreMaps from './ExploreMaps'
import ChatView from './ChatView'
import VideoView from './VideoView'
@@ -12,6 +13,7 @@ const Views = {
$(document).on(JUNTO_UPDATED, () => ExploreMaps.render())
ChatView.init([serverData['sounds/MM_sounds.mp3'], serverData['sounds/MM_sounds.ogg']])
},
+ ContextMenu,
ExploreMaps,
ChatView,
VideoView,
@@ -19,5 +21,5 @@ const Views = {
TopicCard
}
-export { ExploreMaps, ChatView, VideoView, Room, TopicCard }
+export { ContextMenu, ExploreMaps, ChatView, VideoView, Room, TopicCard }
export default Views
diff --git a/frontend/src/components/MapView/index.js b/frontend/src/components/MapView/index.js
index fe8c23b1..d2cc90fd 100644
--- a/frontend/src/components/MapView/index.js
+++ b/frontend/src/components/MapView/index.js
@@ -1,6 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
+import ContextMenu from '../common/ContextMenu'
import DataVis from '../common/DataVis'
import UpperOptions from '../common/UpperOptions'
import InfoAndHelp from '../common/InfoAndHelp'
@@ -12,6 +13,7 @@ import TopicCard from '../TopicCard'
export default class MapView extends Component {
static propTypes = {
+ contextMenu: PropTypes.bool,
mobile: PropTypes.bool,
mapId: PropTypes.string,
map: PropTypes.object,
@@ -79,7 +81,8 @@ export default class MapView extends Component {
filterAllMappers, filterAllSynapses, filterData,
openImportLightbox, forkMap, openHelpLightbox,
mapIsStarred, onMapStar, onMapUnstar, openTopic,
- onZoomExtents, onZoomIn, onZoomOut, hasLearnedTopicCreation } = this.props
+ onZoomExtents, onZoomIn, onZoomOut, hasLearnedTopicCreation,
+ contextMenu } = this.props
const { chatOpen } = this.state
const onChatOpen = () => {
this.setState({chatOpen: true})
@@ -109,6 +112,7 @@ export default class MapView extends Component {
filterAllSynapses={filterAllSynapses} />
{openTopic && }
+ {contextMenu && }
{currentUser && }
{currentUser && this.mapChat = x} />}
this.upperOptions = x}
@@ -73,6 +75,7 @@ export default class TopicView extends Component {
filterAllSynapses={filterAllSynapses} />
+ {contextMenu && }
{
+ const { contextPos } = this.props
+ let extraClasses = []
+ const position = {}
+ // TODO: make these dynamic values so that the ContextMenu can
+ // change height and still work properly
+ const RIGHTCLICK_WIDTH = 300
+ const RIGHTCLICK_HEIGHT = 144 // this does vary somewhat, but we can use static
+ const SUBMENUS_WIDTH = 256
+ const MAX_SUBMENU_HEIGHT = 270
+ const windowWidth = document.documentElement.clientWidth
+ const windowHeight = document.documentElement.clientHeight
+
+ if (windowWidth - contextPos.x < SUBMENUS_WIDTH) {
+ position.right = windowWidth - contextPos.x
+ extraClasses.push('moveMenusToLeft')
+ } else if (windowWidth - contextPos.x < RIGHTCLICK_WIDTH) {
+ position.right = windowWidth - contextPos.x
+ } else if (windowWidth - contextPos.x < RIGHTCLICK_WIDTH + SUBMENUS_WIDTH) {
+ position.left = contextPos.x
+ extraClasses.push('moveMenusToLeft')
+ } else {
+ position.left = contextPos.x
+ }
+
+ if (windowHeight - contextPos.y < MAX_SUBMENU_HEIGHT) {
+ position.bottom = windowHeight - contextPos.y
+ extraClasses.push('moveMenusUp')
+ } else if (windowHeight - contextPos.y < RIGHTCLICK_HEIGHT + MAX_SUBMENU_HEIGHT) {
+ position.top = contextPos.y
+ extraClasses.push('moveMenusUp')
+ } else {
+ position.top = contextPos.y
+ }
+ return {
+ pos: {
+ top: position.top && position.top + 'px',
+ bottom: position.bottom && position.bottom + 'px',
+ left: position.left && position.left + 'px',
+ right: position.right && position.right + 'px'
+ },
+ extraClasses
+ }
+ }
+
+ hide = () => {
+ const { contextHide } = this.props
+ return
+
+ Hide until refresh
+ Ctrl+H
+
+ }
+
+ remove = () => {
+ const { contextRemove, map, currentUser } = this.props
+ const canEditMap = map && map.authorizeToEdit(currentUser)
+ if (!canEditMap) {
+ return null
+ }
+ return
+
+ Remove from map
+ Ctrl+M
+
+ }
+
+ delete = () => {
+ const { contextDelete, map, currentUser } = this.props
+ const canEditMap = map && map.authorizeToEdit(currentUser)
+ if (!canEditMap) {
+ return null
+ }
+ return
+
+ Delete
+ Ctrl+D
+
+ }
+
+ center = () => {
+ const { contextCenterOn, contextNode, topicId } = this.props
+ if (!(contextNode && topicId)) {
+ return null
+ }
+ return contextCenterOn(contextNode.id)}>
+
+ Center this topic
+ Alt+E
+
+ }
+
+ popout = () => {
+ const { contextPopoutTopic, contextNode } = this.props
+ if (!contextNode) {
+ return null
+ }
+ return contextPopoutTopic(contextNode.id)}>
+
+ Open in new tab
+
+ }
+
+ permission = () => {
+ const { currentUser, contextUpdatePermissions } = this.props
+ if (!currentUser) {
+ return null
+ }
+ return
+
+ Change permissions
+
+ - contextUpdatePermissions('commons')}>
+
+ commons
+
+ - contextUpdatePermissions('public')}>
+
+ public
+
+ - contextUpdatePermissions('private')}>
+
+ private
+
+
+
+
+ }
+
+ metacode = () => {
+ const { metacodeSets, contextOnMetacodeSelect,
+ currentUser, contextNode } = this.props
+ if (!currentUser) {
+ return null
+ }
+ return
+
+ Change metacode
+
+ {
+ contextOnMetacodeSelect(contextNode && contextNode.id, id)
+ }}
+ metacodeSets={metacodeSets} />
+
+
+
+ }
+
+ siblings = () => {
+ const { contextPopulateSiblings, contextFetchSiblings,
+ contextSiblingsData, contextFetchingSiblingsData,
+ topicId, contextNode } = this.props
+ const populateSiblings = () => {
+ if (!this.state.populateSiblingsSent) {
+ contextPopulateSiblings(contextNode.id)
+ this.setState({populateSiblingsSent: true})
+ }
+ }
+ if (!(contextNode && topicId)) {
+ return null
+ }
+ return
+
+ Reveal siblings
+
+ - contextFetchSiblings(contextNode)}>
+ All
+
Alt+R
+
+ {contextSiblingsData && Object.keys(contextSiblingsData).map(key => {
+ return - contextFetchSiblings(contextNode, key)}>
+ {contextSiblingsData[key]}
+
+ })}
+ {contextFetchingSiblingsData && - loading...
}
+
+
+
+ }
+
+ render() {
+ const { contextNode, currentUser, topicId } = this.props
+ const positionData = this.getPositionData()
+ const style = Object.assign({}, {position: 'absolute'}, positionData.pos)
+ const showSpacer = currentUser || (contextNode && topicId)
+
+ return
+
+ {this.hide()}
+ {this.remove()}
+ {this.delete()}
+ {this.center()}
+ {this.popout()}
+ {showSpacer && }
+ {this.permission()}
+ {this.metacode()}
+ {this.siblings()}
+
+
+ }
+}
+
+export default ContextMenu
diff --git a/package.json b/package.json
index f0afc5f7..3be728c4 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"homepage": "https://github.com/metamaps/metamaps#readme",
"dependencies": {
"ajaxq": "0.0.7",
+ "async": "2.5.0",
"attachmediastream": "2.0.0",
"autolinker": "1.4.3",
"babel-cli": "6.26.0",