Commit c281d62c authored by Patrik Meijer's avatar Patrik Meijer
Browse files

Can create Connections and Pointers (and almost sets members) in GraphEditor

parent a4ef4759
/* globals document */
/**
* TODO: Consider moving this outside of the GraphEditor and let it be passed as child instead.
* @author pmeijer / https://github.com/pmeijer
*/
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
export default class ContextMenu extends Component {
static propTypes = {
gmeClient: PropTypes.object.isRequired,
nodeId: PropTypes.string.isRequired, // FIXME: should be nodeIds
eventX: PropTypes.number.isRequired,
eventY: PropTypes.number.isRequired,
onClose: PropTypes.func.isRequired,
setActiveNode: PropTypes.func.isRequired,
createPointer: PropTypes.func.isRequired,
readOnly: PropTypes.bool,
};
static defaultProps = {
readOnly: false,
}
setActiveNode = () => {
const {nodeId, onClose, setActiveNode} = this.props;
setActiveNode(nodeId);
onClose();
}
deleteNode = () => {
const {gmeClient, nodeId, onClose} = this.props;
gmeClient.deleteNode(nodeId);
onClose();
}
getCreatePointerFunc = (ptrName) => {
const {createPointer, nodeId} = this.props;
return () => {
createPointer(nodeId, ptrName);
};
}
render() {
const {
gmeClient,
nodeId,
eventX,
eventY,
readOnly,
onClose,
} = this.props;
// console.log(data);
const menuItems = [];
const nodeObj = gmeClient.getNode(nodeId);
const metaNodeObj = gmeClient.getNode(nodeObj.getMetaTypeId());
const pointerNames = metaNodeObj.getValidPointerNames();
menuItems.push((
<MenuItem key="meta-type" onClick={onClose} disabled={readOnly}>
{`<<${metaNodeObj.getAttribute('name')}>>`}
</MenuItem>));
menuItems.push((
<MenuItem key="open-sub-system" onClick={this.setActiveNode} >
Open Subsystem ...
</MenuItem>));
pointerNames.forEach((ptr) => {
menuItems.push((
<MenuItem key={`create-pointer-${ptr}`} onClick={this.getCreatePointerFunc(ptr)} >
{`Create pointer ${ptr} from here ...`}
</MenuItem>));
});
return (
<Menu
id="simple-menu"
anchorEl={document.body}
style={{
position: 'absolute',
top: eventY - (document.body.clientHeight / 2),
left: eventX,
}}
open
onClose={onClose}
>
{menuItems}
</Menu>
);
}
}
......@@ -29,9 +29,10 @@ const DEFAULT_STYLES = [
},
{
selector: 'node.aux-node',
css: {
style: {
width: 6,
height: 6,
'background-color': 'rgb(174, 219, 255)',
},
},
{
......@@ -89,9 +90,15 @@ const DEFAULT_STYLES = [
},
},
{
selector: 'node.valid-pointer-target',
selector: 'node.valid-action-target',
style: {
'background-color': 'rgba(185, 236, 185, 0.64)',
},
},
{
selector: 'node.invalid-action-target',
style: {
'background-color': 'rgb(66, 220, 244)',
'background-color': 'rgba(236, 150, 185, 0.64)',
},
},
{
......
......@@ -19,7 +19,7 @@ import FilterIcon from '@material-ui/icons/FilterList';
import ViewModuleIcon from '@material-ui/icons/ViewModule';
import ReactCytoscape from './ReactCytoscape';
import ContextMenu from './ContextMenu';
import SimpleActionMenu from '../SimpleActionMenu';
import FilterSelector from './FilterSelector';
import DEFAULT_STYLES from './DefaultStyles';
......@@ -138,7 +138,7 @@ export default class GraphEditor extends Component {
return res;
})(),
showFilterSelector: false,
createPointer: null,
creatingNew: null,
};
componentWillReceiveProps({activeNode}) {
......@@ -167,7 +167,7 @@ export default class GraphEditor extends Component {
activeSelection,
nodes,
} = this.props;
const {activeFilters, createPointer} = this.state;
const {activeFilters, creatingNew} = this.state;
// https://github.com/ybarukh/react-cytoscape/blob/master/sample/app/Graph.js
// http://js.cytoscape.org/#notation/elements-json
const result = {
......@@ -241,8 +241,8 @@ export default class GraphEditor extends Component {
${activeSelection.includes(id) ? ' in-active-selection' : ''}`,
};
if (createPointer && createPointer.target === id) {
edgeData.classes += ' valid-pointer-target';
if (creatingNew && creatingNew.target === id) {
edgeData.classes += creatingNew.isValid ? ' valid-action-target' : ' invalid-action-target';
}
if (hasEdgeTargets(childData.pointers.src, childData.pointers.dst)) {
......@@ -285,8 +285,8 @@ ${activeSelection.includes(id) ? 'in-active-selection' : ''}`,
});
}
if (createPointer && createPointer.target === id) {
cytoData.classes += ' valid-pointer-target';
if (creatingNew && creatingNew.target === id) {
cytoData.classes += creatingNew.isValid ? ' valid-action-target' : ' invalid-action-target';
}
}
......@@ -414,10 +414,25 @@ ${activeSelection.includes(edgeId) ? ' in-active-selection' : ''}`,
startNewPointer = (nodeId, ptrName) => {
this.setState({
showNodeMenu: false,
createPointer: {
creatingNew: {
type: 'pointer',
nodeId,
ptrName,
id: ptrName,
target: null,
isValid: null,
},
});
};
startNewConnection = (nodeId, connTypeId) => {
this.setState({
showNodeMenu: false,
creatingNew: {
type: 'connection',
nodeId,
id: connTypeId,
target: null,
isValid: null,
},
});
};
......@@ -459,22 +474,50 @@ ${activeSelection.includes(edgeId) ? ' in-active-selection' : ''}`,
this.cy.on('vclick', 'node', (e) => {
const {setActiveSelection, gmeClient} = this.props;
const {createPointer} = this.state;
const {creatingNew} = this.state;
// This is async :+1:
this.setState({creatingNew: null});
if (typeof e.target.id === 'function') {
if (createPointer && createPointer.target === e.target.id()) {
this.setState({createPointer: null});
gmeClient.setPointer(createPointer.nodeId, createPointer.ptrName, createPointer.target);
const nodeId = e.target.id();
if (creatingNew && creatingNew.target === nodeId && creatingNew.isValid) {
if (creatingNew.type === 'pointer') {
gmeClient.setPointer(creatingNew.nodeId, creatingNew.id, creatingNew.target);
} else if (creatingNew.type === 'set') {
// FIXME: This uses the shuffled client arguments for addMember!
gmeClient.addMember(creatingNew.nodeId, creatingNew.target, creatingNew.id);
} else if (creatingNew.type === 'connection') {
const gmeNode = gmeClient.getNode(nodeId);
if (gmeNode) {
const commonParentId = gmeNode.getCommonParentId(creatingNew.nodeId);
gmeClient.startTransaction();
const connId = gmeClient.createNode({
baseId: creatingNew.id,
parentId: commonParentId,
});
gmeClient.setPointer(connId, 'src', creatingNew.nodeId);
gmeClient.setPointer(connId, 'dst', creatingNew.target);
gmeClient.completeTransaction();
setTimeout(() => {
setActiveSelection([connId]);
});
}
} else {
console.error('Unexpected createNew type', JSON.stringy(creatingNew));
}
} else if (e.target.hasClass('aux-node') === false) {
this.setState({
showNodeMenu: {
id: e.target.id(),
data: e.target.data(),
position: {
x: e.originalEvent.clientX,
y: e.originalEvent.clientY,
},
},
createPointer: null,
});
setTimeout(() => {
......@@ -487,22 +530,34 @@ ${activeSelection.includes(edgeId) ? ' in-active-selection' : ''}`,
});
this.cy.on('mouseover', 'node', (e) => {
const {createPointer} = this.state;
const {creatingNew} = this.state;
const {gmeClient} = this.props;
if (createPointer
&& typeof e.target.id === 'function'
&& e.target.hasClass('aux-node') === false) {
const gmeNode = gmeClient.getNode(e.target.id());
if (gmeNode && gmeNode.isValidTargetOf(createPointer.nodeId, createPointer.ptrName)
&& createPointer.target !== e.target.id()) {
this.setState({
createPointer: {
nodeId: createPointer.nodeId,
ptrName: createPointer.ptrName,
target: e.target.id(),
},
});
const nodeId = e.target.id();
const gmeNode = gmeClient.getNode(nodeId);
if (!gmeNode) {
return;
}
if (creatingNew && creatingNew.target !== nodeId) {
let isValid = false;
if (creatingNew.type === 'pointer') {
isValid = gmeNode.isValidTargetOf(creatingNew.nodeId, creatingNew.id);
} else if (creatingNew.type === 'set') {
isValid = gmeNode.isValidMemberOf(creatingNew.nodeId, creatingNew.id);
} else if (creatingNew.type === 'connection') {
isValid = gmeNode.isValidTargetOf(creatingNew.id, 'dst');
}
this.setState({
creatingNew: {
type: creatingNew.type,
id: creatingNew.id, // connection meta-id, ptr-name or set-name.
nodeId: creatingNew.nodeId,
target: nodeId,
isValid,
},
});
}
});
}
......@@ -615,17 +670,17 @@ ${activeSelection.includes(edgeId) ? ' in-active-selection' : ''}`,
/>
{showNodeMenu && showNodeMenu.id === activeSelection[0]
? (
<ContextMenu
<SimpleActionMenu
gmeClient={gmeClient}
nodeId={showNodeMenu.id}
data={showNodeMenu.data}
eventX={showNodeMenu.position.x}
eventY={showNodeMenu.position.y}
onClose={() => {
this.setState({showNodeMenu: null});
}}
createPointer={this.startNewPointer}
setActiveNode={setActiveNode}
onSetPointerTarget={this.startNewPointer}
onCreateConnection={this.startNewConnection}
onSetActiveNode={setActiveNode}
/>) : null}
<FilterSelector
......
/* globals document */
/**
* TODO: Consider moving this outside of the GraphEditor and let it be passed as child instead.
* @author pmeijer / https://github.com/pmeijer
*/
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import OpenIcon from '@material-ui/icons/ArrowDownward';
import DeleteIcon from '@material-ui/icons/Clear';
import PointerIcon from '@material-ui/icons/TrendingFlat';
import SetIcon from '@material-ui/icons/CallSplit';
import ConnectionIcon from '@material-ui/icons/SettingsEthernet';
import NodeIcon from '@material-ui/icons/FiberManualRecord';
function noop() {}
export default class SimpleActionMenu extends Component {
static propTypes = {
gmeClient: PropTypes.object.isRequired,
nodeId: PropTypes.string.isRequired,
eventX: PropTypes.number.isRequired,
eventY: PropTypes.number.isRequired,
onClose: PropTypes.func.isRequired,
allowDeletion: PropTypes.bool,
onSetActiveNode: PropTypes.func,
onSetPointerTarget: PropTypes.func,
onAddSetMember: PropTypes.func,
onCreateConnection: PropTypes.func,
onCreateChild: PropTypes.func,
};
static defaultProps = {
allowDeletion: true,
onSetActiveNode: noop,
onSetPointerTarget: noop,
onAddSetMember: noop,
onCreateConnection: noop,
onCreateChild: noop,
}
setActiveNode = () => {
const {nodeId, onSetActiveNode} = this.props;
onSetActiveNode(nodeId);
}
deleteNode = () => {
const {gmeClient, nodeId, onClose} = this.props;
gmeClient.deleteNode(nodeId);
onClose();
}
getSetPointerFunc = (ptrName) => {
const {onSetPointerTarget, nodeId} = this.props;
return () => {
onSetPointerTarget(nodeId, ptrName);
};
}
getAddMemberFunc = (setName) => {
const {onAddSetMember, nodeId} = this.props;
return () => {
onAddSetMember(nodeId, setName);
};
}
getCreateConnectionFunc = (connectionTypeId) => {
const {onCreateConnection, nodeId} = this.props;
return () => {
onCreateConnection(nodeId, connectionTypeId);
};
}
render() {
const {
gmeClient,
nodeId,
eventX,
eventY,
allowDeletion,
onSetActiveNode,
onSetPointerTarget,
onAddSetMember,
onCreateConnection,
onCreateChild,
onClose,
} = this.props;
const menuItems = [];
const nodeObj = gmeClient.getNode(nodeId);
const metaNodeObj = gmeClient.getNode(nodeObj.getMetaTypeId());
const isReadOnly = nodeObj.isReadOnly();
if (onSetActiveNode !== noop) {
menuItems.push((
<MenuItem key="open-sub-system" onClick={this.setActiveNode}>
<ListItemIcon>
<OpenIcon/>
</ListItemIcon>
<ListItemText>
{`Open ${nodeObj.getAttribute('name')}`}
</ListItemText>
</MenuItem>));
}
if (onSetPointerTarget !== noop && isReadOnly === false) {
const pointerNames = metaNodeObj.getValidPointerNames();
pointerNames.forEach((name) => {
menuItems.push((
<MenuItem key={`create-pointer-${name}`} onClick={this.getSetPointerFunc(name)}>
<ListItemIcon>
<PointerIcon/>
</ListItemIcon>
<ListItemText>
{`Create pointer ${name} from here ...`}
</ListItemText>
</MenuItem>));
});
}
if (onAddSetMember !== noop && isReadOnly === false) {
const setNames = metaNodeObj.getValidSetNames();
setNames.forEach((name) => {
menuItems.push((
<MenuItem key={`add-set-member-${name}`} onClick={this.getAddMemberFunc(name)}>
<ListItemIcon>
<SetIcon/>
</ListItemIcon>
<ListItemText>
{`Add new member to set ${name} ...`}
</ListItemText>
</MenuItem>));
});
}
if (onCreateConnection !== noop && isReadOnly === false) {
gmeClient.getAllMetaNodes()
.filter(metaNode => metaNode.isConnection() && nodeObj.isValidTargetOf(metaNode.getId(), 'src'))
.map(metaNode => ({name: metaNode.getFullyQualifiedName(), id: metaNode.getId()}))
.forEach((info) => {
menuItems.push((
<MenuItem key={`create-new-conn-${info}`} onClick={this.getCreateConnectionFunc(info.id)}>
<ListItemIcon>
<ConnectionIcon/>
</ListItemIcon>
<ListItemText>
{`Create connection ${info.name} from here...`}
</ListItemText>
</MenuItem>));
});
}
return (
<Menu
id="simple-menu"
anchorEl={document.body}
style={{
position: 'absolute',
top: eventY - (document.body.clientHeight / 2),
left: eventX,
}}
open
onClose={onClose}
>
{menuItems}
</Menu>
);
}
}
export {default} from './SimpleActionMenu';
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment