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 = '' - 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 = '' - 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",