Skip to content

Commit

Permalink
feat(create): show connection preview during creation
Browse files Browse the repository at this point in the history
  • Loading branch information
nikku authored and barmac committed May 3, 2019
1 parent 681c10a commit abc86fe
Show file tree
Hide file tree
Showing 3 changed files with 330 additions and 1 deletion.
152 changes: 152 additions & 0 deletions lib/features/create/CreateConnectPreview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import {
append as svgAppend,
attr as svgAttr,
classes as svgClasses,
create as svgCreate,
remove as svgRemove,
clear as svgClear
} from 'tiny-svg';


var LOW_PRIORITY = 740;

/**
* Shows a connection preview during element creation.
*
* @param {didi.Injector} injector
* @param {EventBus} eventBus
* @param {Canvas} canvas
* @param {GraphicsFactory} graphicsFactory
* @param {ElementFactory} elementFactory
*/
export default function CreateConnectPreview(injector, eventBus, canvas, graphicsFactory, elementFactory) {

// optional components

var connectionDocking = injector.get('connectionDocking', false);
var layouter = injector.get('layouter', false);


// visual helpers

function createConnectVisual() {

var visual = svgCreate('g');
svgAttr(visual, {
'pointer-events': 'none'
});

svgClasses(visual).add('djs-dragger');

svgAppend(canvas.getDefaultLayer(), visual);

return visual;
}

// move integration

eventBus.on('create.move', LOW_PRIORITY, function(event) {

var context = event.context,
source = context.source,
shape = context.shape,
connectVisual = context.connectVisual,
canExecute = context.canExecute,
connect = canExecute && canExecute.connect,
getConnection = context.getConnection,
connection;


if (!getConnection) {
getConnection = context.getConnection = Cacher(function(attrs) {
return elementFactory.create('connection', attrs);
});
}

if (!connectVisual) {
connectVisual = context.connectVisual = createConnectVisual();
}

svgClear(connectVisual);

if (!connect) {
return;
}

connection = getConnection(connectionAttrs(connect));

// monkey patch shape position for intersection to work
shape.x = Math.round(event.x - shape.width / 2);
shape.y = Math.round(event.y - shape.height / 2);

if (layouter) {
connection.waypoints = [];

connection.waypoints = layouter.layoutConnection(connection, {
source: source,
target: shape
});
} else {
connection.waypoints = [
{ x: source.x + source.width / 2, y: source.y + source.height / 2 },
{ x: event.x, y: event.y }
];
}

if (connectionDocking) {
connection.waypoints = connectionDocking.getCroppedWaypoints(connection, source, shape);
}

graphicsFactory.drawConnection(connectVisual, connection);
});


eventBus.on('create.cleanup', function(event) {
var context = event.context;

if (context.connectVisual) {
svgRemove(context.connectVisual);
}
});

}

CreateConnectPreview.$inject = [
'injector',
'eventBus',
'canvas',
'graphicsFactory',
'elementFactory'
];



// helpers //////////////

function connectionAttrs(connect) {

if (typeof connect === 'boolean') {
return {};
} else {
return connect;
}
}


function Cacher(createFn) {

var entries = {};

return function(attrs) {

var key = JSON.stringify(attrs);

var e = entries[key];

if (!e) {
e = entries[key] = createFn(attrs);
}

return e;
};
}
7 changes: 6 additions & 1 deletion lib/features/create/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import SelectionModule from '../selection';
import RulesModule from '../rules';

import Create from './Create';
import CreateConnectPreview from './CreateConnectPreview';


export default {
Expand All @@ -11,5 +12,9 @@ export default {
SelectionModule,
RulesModule
],
create: [ 'type', Create ]
__init__: [
'createConnectPreview'
],
create: [ 'type', Create ],
createConnectPreview: [ 'type', CreateConnectPreview ]
};
172 changes: 172 additions & 0 deletions test/spec/features/create/CreateConnectPreviewSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import {
bootstrapDiagram,
inject
} from 'test/TestHelper';

import {
createCanvasEvent as canvasEvent
} from '../../../util/MockEvents';

import modelingModule from 'lib/features/modeling';
import moveModule from 'lib/features/move';
import dragModule from 'lib/features/dragging';
import createModule from 'lib/features/create';
import attachSupportModule from 'lib/features/attach-support';
import rulesModule from './rules';

import {
classes as svgClasses
} from 'tiny-svg';


describe('features/create - CreateConnectPreview', function() {

beforeEach(bootstrapDiagram({
modules: [
createModule,
rulesModule,
attachSupportModule,
modelingModule,
moveModule,
dragModule
]
}));

beforeEach(inject(function(dragging) {
dragging.setOptions({ manual: true });
}));

var rootShape,
parentShape,
childShape,
newShape;

beforeEach(inject(function(elementFactory, canvas) {

rootShape = elementFactory.createRoot({
id: 'root'
});

canvas.setRootElement(rootShape);

parentShape = elementFactory.createShape({
id: 'parent',
x: 100, y: 100, width: 200, height: 200
});

canvas.addShape(parentShape, rootShape);


childShape = elementFactory.createShape({
id: 'childShape',
x: 150, y: 350, width: 100, height: 100
});

canvas.addShape(childShape, rootShape);


newShape = elementFactory.createShape({
id: 'newShape',
x: 0, y: 0, width: 50, height: 50
});
}));


describe('display', function() {

it('should display connection preview', inject(function(create, elementRegistry, dragging) {

// given
var parentGfx = elementRegistry.getGraphics('parentShape');

// when
create.start(canvasEvent({ x: 0, y: 0 }), newShape, childShape);

dragging.move(canvasEvent({ x: 175, y: 175 }));
dragging.hover({ element: parentShape, gfx: parentGfx });
dragging.move(canvasEvent({ x: 400, y: 200 }));

var ctx = dragging.context();

// then
expect(ctx.data.context.connectVisual).to.exist;
expect(svgClasses(ctx.data.context.connectVisual).has('djs-dragger')).to.be.true;
}));
});

describe('cleanup', function() {

it('should remove connection preview on dragging end', inject(function(create, elementRegistry, dragging) {

// given
var parentGfx = elementRegistry.getGraphics('parentShape');

// when
create.start(canvasEvent({ x: 0, y: 0 }), newShape, childShape);

dragging.move(canvasEvent({ x: 175, y: 175 }));
dragging.hover({ element: parentShape, gfx: parentGfx });
dragging.move(canvasEvent({ x: 400, y: 200 }));

var ctx = dragging.context();

dragging.end();

// then
expect(ctx.data.context.connectVisual.parentNode).not.to.exist;
}));


it('should remove connection preview on dragging cancel', inject(function(create, elementRegistry, dragging) {

// given
var parentGfx = elementRegistry.getGraphics('parentShape');

// when
create.start(canvasEvent({ x: 0, y: 0 }), newShape, childShape);

dragging.move(canvasEvent({ x: 175, y: 175 }));
dragging.hover({ element: parentShape, gfx: parentGfx });
dragging.move(canvasEvent({ x: 400, y: 200 }));

var ctx = dragging.context();

dragging.cancel();

// then
expect(ctx.data.context.connectVisual.parentNode).not.to.exist;
}));
});


describe('rules', function() {

it('should not display preview if connection is disallowed',
inject(function(create, elementRegistry, dragging, createRules) {

// given
createRules.addRule('connection.create', 8000, function() {
return false;
});

var parentGfx = elementRegistry.getGraphics('parentShape');

// when
create.start(canvasEvent({ x: 0, y: 0 }), newShape, childShape);

dragging.move(canvasEvent({ x: 175, y: 175 }));
dragging.hover({ element: parentShape, gfx: parentGfx });
dragging.move(canvasEvent({ x: 400, y: 200 }));

var ctx = dragging.context();

// then
expect(ctx.data.context.connectVisual.children).to.satisfy(function(children) {
// children is undefined in phantomjs
return !children || !children.length;
});
})
);

});
});

0 comments on commit abc86fe

Please sign in to comment.