-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4a68a2a
commit a164d69
Showing
6 changed files
with
443 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import { | ||
asTRBL, | ||
getMid | ||
} from 'diagram-js/lib/layout/LayoutUtil'; | ||
|
||
import { getDistancePointPoint } from 'diagram-js/lib/features/bendpoints/GeometricUtil'; | ||
|
||
import { | ||
is, | ||
isAny | ||
} from '../../util/ModelUtil'; | ||
|
||
const AUTO_CONNECT_PADDING = 200; | ||
|
||
const IGNORED_SOURCES = [ | ||
'bpmn:Participant' | ||
]; | ||
|
||
const IGNORED_TARGETS = [ | ||
'bpmn:Participant' | ||
]; | ||
|
||
export default function AutoConnect(elementDetection, modeling, rules) { | ||
this._elementDetection = elementDetection; | ||
this._modeling = modeling; | ||
this._rules = rules; | ||
} | ||
|
||
AutoConnect.prototype.canConnect = function(element) { | ||
if (isAny(element, IGNORED_SOURCES)) { | ||
return false; | ||
} | ||
|
||
const elementTrbl = asTRBL(element); | ||
|
||
const rects = [ | ||
{ | ||
top: elementTrbl.top - AUTO_CONNECT_PADDING, | ||
right: elementTrbl.right + AUTO_CONNECT_PADDING, | ||
bottom: elementTrbl.bottom + AUTO_CONNECT_PADDING, | ||
left: elementTrbl.right + 10 | ||
}, | ||
{ | ||
top: elementTrbl.top - AUTO_CONNECT_PADDING, | ||
right: elementTrbl.right + AUTO_CONNECT_PADDING, | ||
bottom: elementTrbl.bottom + AUTO_CONNECT_PADDING, | ||
left: elementTrbl.left - 10 | ||
} | ||
]; | ||
|
||
return rects.reduce((target, rect) => { | ||
return target || this._findTarget(element, rect); | ||
}, false); | ||
}; | ||
|
||
AutoConnect.prototype._findTarget = function(element, rect) { | ||
const possibleTargets = this._elementDetection.detectAt(rect).filter(possibleTarget => { | ||
return possibleTarget !== element | ||
&& !isConnection(possibleTarget) | ||
&& !isLabel(possibleTarget) | ||
&& !isAny(possibleTarget, IGNORED_TARGETS); | ||
}); | ||
|
||
console.log('possible targets', possibleTargets); | ||
|
||
// find closest element to connect to | ||
return possibleTargets.reduce((target, possibleTarget) => { | ||
if (isConnected(element, possibleTarget) | ||
|| hasMaxOutgoingConnections(element) | ||
|| hasMaxIncomingConnections(possibleTarget)) { | ||
return target; | ||
} | ||
|
||
const canConnect = this.getConnectionType(element, possibleTarget); | ||
|
||
if (!canConnect) { | ||
return target; | ||
} | ||
|
||
const distance = getDistancePointPoint(getMid(element), getMid(possibleTarget)); | ||
|
||
if (!target || distance < getDistancePointPoint(getMid(element), getMid(target))) { | ||
return possibleTarget; | ||
} | ||
|
||
return target; | ||
}, null); | ||
}; | ||
|
||
AutoConnect.prototype.getConnectionType = function(source, target) { | ||
return this._rules.allowed('connection.create', { | ||
source, | ||
target | ||
}); | ||
}; | ||
|
||
AutoConnect.prototype.connect = function(element) { | ||
const target = this.canConnect(element); | ||
|
||
if (!target) { | ||
return; | ||
} | ||
|
||
this._modeling.connect(element, target); | ||
}; | ||
|
||
AutoConnect.$inject = [ | ||
'elementDetection', | ||
'modeling', | ||
'rules' | ||
]; | ||
|
||
function isConnection(element) { | ||
return element.waypoints; | ||
} | ||
|
||
function isLabel(element) { | ||
return element.labelTarget; | ||
} | ||
|
||
function isConnected(source, target) { | ||
return source.incoming.some(connection => connection.source === target) | ||
|| source.outgoing.some(connection => connection.target === target); | ||
} | ||
|
||
function hasMaxIncomingConnections(element) { | ||
return !is(element, 'bpmn:Gateway') && element.incoming.length; | ||
} | ||
|
||
function hasMaxOutgoingConnections(element) { | ||
return !is(element, 'bpmn:Gateway') && element.outgoing.length; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
import { | ||
isNumber, | ||
isUndefined, | ||
some | ||
} from 'min-dash'; | ||
|
||
import { | ||
append as svgAppend, | ||
attr as svgAttr, | ||
clear as svgClear, | ||
create as svgCreate | ||
} from 'tiny-svg'; | ||
|
||
const THRESHOLD = 2; | ||
|
||
|
||
export default class ElementDetection { | ||
constructor(canvas, elementRegistry) { | ||
this._canvas = canvas; | ||
this._elementRegistry = elementRegistry; | ||
} | ||
|
||
/** | ||
* Detect elements intersecting point or rect. | ||
* Uses element registry. | ||
* | ||
* @param {Object} pointOrRect | ||
* | ||
* @return {Array} | ||
*/ | ||
detectAt(pointOrRect) { | ||
if (isPoint(pointOrRect)) { | ||
pointOrRect = { | ||
...pointOrRect, | ||
width: 0, | ||
height: 0 | ||
}; | ||
} | ||
|
||
if (!isTRBL(pointOrRect)) { | ||
pointOrRect = toTRBL(pointOrRect); | ||
} | ||
|
||
// drawRect(pointOrRect, this._canvas); | ||
|
||
const elements = this._elementRegistry.filter(elementIntersects(pointOrRect)); | ||
|
||
return elements; | ||
} | ||
} | ||
|
||
ElementDetection.$inject = [ | ||
'canvas', | ||
'elementRegistry' | ||
]; | ||
|
||
// helpers ////////// | ||
|
||
function elementIntersects(rect) { | ||
return function(element) { | ||
if (isConnection(element)) { | ||
return connectionIntersects(element, rect); | ||
} | ||
|
||
return element.x <= rect.right && | ||
element.x + element.width >= rect.left && | ||
element.y <= rect.bottom && | ||
element.y + element.height >= rect.top; | ||
} | ||
} | ||
|
||
function isConnection(element) { | ||
return !!element.waypoints; | ||
} | ||
|
||
function connectionIntersects(connection, rect) { | ||
const segments = getSegments(connection); | ||
|
||
return some(segments, segmentIntersects(rect)); | ||
} | ||
|
||
function getSegments({ waypoints }) { | ||
return waypoints.reduce((segments, waypoint, index) => { | ||
if (isLastIndex(index, waypoints)) { | ||
return segments; | ||
} | ||
|
||
return [ | ||
...segments, | ||
{ | ||
start: waypoint, | ||
end: waypoints[ index + 1] | ||
} | ||
]; | ||
}, []); | ||
} | ||
|
||
function segmentIntersects(rect) { | ||
return function(segment) { | ||
let { start, end } = segment; | ||
|
||
if (isHorizontal(segment)) { | ||
if (start.x > end.x) { | ||
start = segment.end; | ||
end = segment.start; | ||
} | ||
|
||
return start.y >= rect.top && | ||
start.y <= rect.bottom && | ||
start.x <= rect.right && | ||
end.x >= rect.left; | ||
} else { | ||
if (start.y > end.y) { | ||
start = segment.end; | ||
end = segment.start; | ||
} | ||
|
||
return start.x >= rect.left && | ||
start.x <= rect.right && | ||
start.y <= rect.bottom && | ||
end.y >= rect.top; | ||
} | ||
}; | ||
} | ||
|
||
function isHorizontal({ start, end }) { | ||
return Math.abs(start.y - end.y) <= THRESHOLD; | ||
} | ||
|
||
function isLastIndex(index, array) { | ||
return index + 1 === array.length; | ||
} | ||
|
||
function isPoint(pointOrRect) { | ||
return isNumber(pointOrRect.x) && | ||
isNumber(pointOrRect.y) && | ||
isUndefined(pointOrRect.width) && | ||
isUndefined(pointOrRect.height); | ||
} | ||
|
||
function isTRBL(pointOrRect) { | ||
return isNumber(pointOrRect.top) && | ||
isNumber(pointOrRect.right) && | ||
isNumber(pointOrRect.bottom) && | ||
isNumber(pointOrRect.left); | ||
} | ||
|
||
function toTRBL(rect) { | ||
return { | ||
top: rect.y, | ||
right: rect.x + rect.width, | ||
bottom: rect.y + rect.height, | ||
left: rect.x | ||
}; | ||
} | ||
|
||
let counter = 0; | ||
|
||
const colors = [ 'red', /*'green', 'blue'*/ ]; | ||
|
||
function drawRect(rect, canvas) { | ||
const layer = canvas.getLayer('element-detection'); | ||
|
||
svgClear(layer); | ||
|
||
const gfx = svgCreate('rect'); | ||
|
||
gfx.style.pointerEvents = 'none'; | ||
|
||
svgAttr(gfx, { | ||
fill: colors[ counter % colors.length ], | ||
fillOpacity: 0.25, | ||
stroke: colors[ counter % colors.length ], | ||
strokeOpacity: 0.25, | ||
strokeWidth: 2, | ||
x: rect.left, | ||
y: rect.top, | ||
width: Math.max(rect.right - rect.left, 2), | ||
height: Math.max(rect.bottom - rect.top, 2) | ||
}); | ||
|
||
svgAppend(layer, gfx); | ||
|
||
setTimeout(() => { | ||
// svgClear(layer); | ||
}, 1000); | ||
|
||
counter++; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import AutoConnect from './AutoConnect'; | ||
import ElementDetection from './ElementDetection'; | ||
|
||
export default { | ||
_init_: [ 'autoConnect' ], | ||
autoConnect: [ 'type', AutoConnect ], | ||
elementDetection: [ 'type', ElementDetection ] | ||
}; |
Oops, something went wrong.