Skip to content

Commit

Permalink
feat(create): show connection preview during creation
Browse files Browse the repository at this point in the history
Diagram will display connection preview when appending
a shape.

BREAKING CHANGES:

When appending a shape to existing element, the layouter will
receive a connection without waypoints, source, target and with
only { source, target } hints. Make sure your layouter handles
such case.
  • Loading branch information
nikku authored and merge-me[bot] committed May 8, 2019
1 parent f5b5e45 commit 16a7047
Show file tree
Hide file tree
Showing 3 changed files with 249 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 ]
};
91 changes: 91 additions & 0 deletions test/spec/features/create/CreateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,97 @@ describe('features/create - Create', function() {

});

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;
}));


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.childNodes).to.be.have.lengthOf(0);
})
);
});

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() {

Expand Down

0 comments on commit 16a7047

Please sign in to comment.